Responsive icon sizes for phones, tablets and desktops, closes #214 (#217)

This commit is contained in:
Huang Xin
2025-01-22 18:33:00 +01:00
committed by GitHub
parent c8bfb64ad3
commit 533324e558
31 changed files with 170 additions and 108 deletions
+1
View File
@@ -60,6 +60,7 @@
"react-dom": "19.0.0",
"react-i18next": "^15.2.0",
"react-icons": "^5.4.0",
"react-responsive": "^10.0.0",
"tinycolor2": "^1.6.0",
"zustand": "5.0.1"
},
+3 -3
View File
@@ -45,7 +45,7 @@ const ProviderLogin: React.FC<ProviderLoginProp> = ({ provider, handleSignIn, Ic
'bg-base-100 border-gray-300 shadow-sm transition hover:bg-gray-50',
)}
>
<Icon size={20} />
<Icon />
<span className='text-neutral-content/70 px-2 text-sm'>{label}</span>
</button>
);
@@ -194,7 +194,7 @@ export default function AuthPage() {
onClick={handleGoBack}
className='btn btn-ghost fixed left-3 top-11 h-8 min-h-8 w-8 p-0'
>
<IoArrowBack size={20} className='text-base-content' />
<IoArrowBack className='text-base-content' />
</button>
<div style={{ maxWidth: '420px', margin: 'auto', padding: '2rem' }}>
<ProviderLogin
@@ -238,7 +238,7 @@ export default function AuthPage() {
onClick={handleGoBack}
className='btn btn-ghost fixed left-10 top-6 h-8 min-h-8 w-8 p-0'
>
<IoArrowBack size={20} className='text-base-content' />
<IoArrowBack className='text-base-content' />
</button>
<Auth
supabaseClient={supabase}
@@ -16,6 +16,7 @@ import { useEnv } from '@/context/EnvContext';
import { useLibraryStore } from '@/store/libraryStore';
import { useSettingsStore } from '@/store/settingsStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
import { navigateToReader } from '@/utils/nav';
import { getOSPlatform } from '@/utils/misc';
import { getFilename } from '@/utils/book';
@@ -73,6 +74,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, isSelectMode, onImp
const [importBookUrl] = useState(searchParams?.get('url') || '');
const isImportingBook = useRef(false);
const [showDetailsBook, setShowDetailsBook] = useState<Book | null>(null);
const iconSize15 = useResponsiveSize(15);
const showBookDetailsModal = (book: Book) => {
setShowDetailsBook(book);
@@ -232,12 +234,9 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, isSelectMode, onImp
{isSelectMode && (
<div className='absolute bottom-1 right-1'>
{selectedBooks.includes(item.hash) ? (
<MdCheckCircle size={20} className='fill-blue-500' />
<MdCheckCircle className='fill-blue-500' />
) : (
<MdCheckCircleOutline
size={20}
className='fill-gray-300 drop-shadow-sm'
/>
<MdCheckCircleOutline className='fill-gray-300 drop-shadow-sm' />
)}
</div>
)}
@@ -268,7 +267,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, isSelectMode, onImp
}
}}
>
<CiCircleMore size={15} />
<CiCircleMore size={iconSize15} />
</button>
)}
</div>
@@ -285,7 +284,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({ libraryBooks, isSelectMode, onImp
}
}}
>
<CiCircleMore size={15} />
<CiCircleMore size={iconSize15} />
</button>
</div>
)}
@@ -101,7 +101,7 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
<Dropdown
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
toggleButton={<MdOutlineMenu size={16} />}
toggleButton={<MdOutlineMenu />}
>
<SettingsMenu />
</Dropdown>
@@ -75,7 +75,7 @@ const SettingsMenu: React.FC<BookMenuProps> = ({ setIsDropdownOpen }) => {
height={20}
/>
) : (
<PiUserCircleCheck size={20} />
<PiUserCircleCheck />
)
}
>
@@ -84,11 +84,7 @@ const SettingsMenu: React.FC<BookMenuProps> = ({ setIsDropdownOpen }) => {
</ul>
</MenuItem>
) : (
<MenuItem
label={_('Sign In')}
icon={<PiUserCircle size={20} />}
onClick={handleUserLogin}
></MenuItem>
<MenuItem label={_('Sign In')} icon={<PiUserCircle />} onClick={handleUserLogin}></MenuItem>
)}
<hr className='border-base-200 my-1' />
{isWebApp && <MenuItem label={_('Download Readest')} onClick={downloadReadest} />}
@@ -97,9 +97,9 @@ const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
<Button
icon={
isBookmarked ? (
<MdOutlineBookmark size={20} className='text-base-content' />
<MdOutlineBookmark className='text-base-content' />
) : (
<MdOutlineBookmarkAdd size={20} className='text-base-content' />
<MdOutlineBookmarkAdd className='text-base-content' />
)
}
onClick={toggleBookmark}
@@ -70,19 +70,15 @@ const FooterBar: React.FC<FooterBarProps> = ({ bookKey, pageinfo, isHoveredAnim
onMouseEnter={() => setHoveredBookKey(bookKey)}
onMouseLeave={() => setHoveredBookKey('')}
>
<Button icon={<RiArrowLeftWideLine />} onClick={handleGoPrev} tooltip={_('Go Left')} />
<Button
icon={<RiArrowLeftWideLine size={20} />}
onClick={handleGoPrev}
tooltip={_('Go Left')}
/>
<Button
icon={<RiArrowGoBackLine size={20} />}
icon={<RiArrowGoBackLine />}
onClick={handleGoBack}
tooltip={_('Go Back')}
disabled={!view?.history.canGoBack}
/>
<Button
icon={<RiArrowGoForwardLine size={20} />}
icon={<RiArrowGoForwardLine />}
onClick={handleGoForward}
tooltip={_('Go Forward')}
disabled={!view?.history.canGoForward}
@@ -98,12 +94,8 @@ const FooterBar: React.FC<FooterBarProps> = ({ bookKey, pageinfo, isHoveredAnim
value={pageinfoValid ? progressFraction * 100 : 0}
onChange={(e) => handleProgressChange(e)}
/>
<Button icon={<FaHeadphones size={20} />} onClick={handleSpeakText} tooltip={_('Speak')} />
<Button
icon={<RiArrowRightWideLine size={20} />}
onClick={handleGoNext}
tooltip={_('Go Right')}
/>
<Button icon={<FaHeadphones />} onClick={handleSpeakText} tooltip={_('Speak')} />
<Button icon={<RiArrowRightWideLine />} onClick={handleGoNext} tooltip={_('Go Right')} />
</div>
);
};
@@ -75,7 +75,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
<Dropdown
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
toggleButton={<PiDotsThreeVerticalBold size={16} />}
toggleButton={<PiDotsThreeVerticalBold />}
onToggle={handleToggleDropdown}
>
<ViewMenu bookKey={bookKey} onSetSettingsDialogOpen={onSetSettingsDialogOpen} />
@@ -26,9 +26,9 @@ const NotebookToggler: React.FC<NotebookTogglerProps> = ({ bookKey }) => {
<Button
icon={
sideBarBookKey == bookKey && isNotebookVisible ? (
<LuNotebookPen size={16} className='text-base-content' />
<LuNotebookPen className='text-base-content' />
) : (
<LuNotebookPen size={16} className='text-base-content' />
<LuNotebookPen className='text-base-content' />
)
}
onClick={handleToggleSidebar}
@@ -13,7 +13,7 @@ const SettingsToggler = () => {
};
return (
<Button
icon={<RiFontSize size={16} className='text-base-content' />}
icon={<RiFontSize className='text-base-content' />}
onClick={handleToggleSettings}
tooltip={_('Font & Layout')}
tooltipDirection='bottom'
@@ -24,9 +24,9 @@ const SidebarToggler: React.FC<SidebarTogglerProps> = ({ bookKey }) => {
<Button
icon={
sideBarBookKey === bookKey && isSideBarVisible ? (
<TbLayoutSidebarFilled size={20} className='text-base-content' />
<TbLayoutSidebarFilled className='text-base-content' />
) : (
<TbLayoutSidebar size={20} className='text-base-content' />
<TbLayoutSidebar className='text-base-content' />
)
}
onClick={handleToggleSidebar}
@@ -96,7 +96,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
zoomLevel <= MIN_ZOOM_LEVEL && 'btn-disabled text-gray-400',
)}
>
<MdZoomOut size={20} />
<MdZoomOut />
</button>
<button
className={clsx(
@@ -113,7 +113,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
zoomLevel >= MAX_ZOOM_LEVEL && 'btn-disabled text-gray-400',
)}
>
<MdZoomIn size={20} />
<MdZoomIn />
</button>
</div>
@@ -124,7 +124,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
<MenuItem
label={_('Scrolled Mode')}
shortcut='Shift+J'
icon={isScrolledMode ? <MdCheck size={20} /> : undefined}
icon={isScrolledMode ? <MdCheck /> : undefined}
onClick={toggleScrolledMode}
/>
@@ -138,20 +138,12 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
? _('Light Mode')
: _('Auto Mode')
}
icon={
themeMode === 'dark' ? (
<BiMoon size={20} />
) : themeMode === 'light' ? (
<BiSun size={20} />
) : (
<TbSunMoon size={20} />
)
}
icon={themeMode === 'dark' ? <BiMoon /> : themeMode === 'light' ? <BiSun /> : <TbSunMoon />}
onClick={cycleThemeMode}
/>
<MenuItem
label={_('Invert Colors in Dark Mode')}
icon={isInvertedColors ? <MdCheck size={20} className='text-base-content' /> : undefined}
icon={isInvertedColors ? <MdCheck className='text-base-content' /> : undefined}
onClick={toggleInvertedColors}
disabled={!isDarkMode}
/>
@@ -1,9 +1,9 @@
import clsx from 'clsx';
import React from 'react';
import { useSettingsStore } from '@/store/settingsStore';
import { HighlightColor, HighlightStyle } from '@/types/book';
import { FaCheckCircle } from 'react-icons/fa';
import { HighlightColor, HighlightStyle } from '@/types/book';
import { useSettingsStore } from '@/store/settingsStore';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
const styles = ['highlight', 'underline', 'squiggly'] as HighlightStyle[];
const colors = ['red', 'violet', 'blue', 'green', 'yellow'] as HighlightColor[];
@@ -25,6 +25,7 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
const globalReadSettings = settings.globalReadSettings;
const [selectedStyle, setSelectedStyle] = React.useState<HighlightStyle>(_selectedStyle);
const [selectedColor, setSelectedColor] = React.useState<HighlightColor>(_selectedColor);
const iconSize16 = useResponsiveSize(16);
const handleSelectStyle = (style: HighlightStyle) => {
globalReadSettings.highlightStyle = style;
@@ -84,7 +85,7 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
)}
>
{selectedColor === color && (
<FaCheckCircle size={16} className={clsx(`fill-${color}-400`)} />
<FaCheckCircle size={iconSize16} className={clsx(`fill-${color}-400`)} />
)}
</button>
))}
@@ -22,7 +22,7 @@ const PopupButton: React.FC<PopupButtonProps> = ({ showTooltip, tooltipText, Ico
onClick={handleClick}
className='my-2 flex h-8 min-h-8 w-8 items-center justify-center p-0'
>
<Icon size={20} />
<Icon />
</button>
</div>
);
@@ -15,7 +15,7 @@ const NotebookHeader: React.FC<{
return (
<div className='notebook-header relative flex h-11 items-center px-3'>
<div className='absolute inset-0 flex items-center justify-center space-x-2'>
<LuNotebookPen size={14} />
<LuNotebookPen />
<div className='notebook-title text-sm font-medium'>{_('Notebook')}</div>
</div>
<div className='z-10 flex items-center gap-x-4'>
@@ -26,16 +26,16 @@ const NotebookHeader: React.FC<{
isPinned ? 'bg-base-300' : 'bg-base-300/65',
)}
>
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
{isPinned ? <MdPushPin /> : <MdOutlinePushPin />}
</button>
<button
onClick={handleClose}
className={'btn btn-ghost btn-circle flex h-6 min-h-6 w-6 hover:bg-transparent sm:hidden'}
>
<MdArrowBackIosNew size={20} />
<MdArrowBackIosNew />
</button>
<button className='btn btn-ghost left-0 h-8 min-h-8 w-8 p-0'>
<FiSearch size={18} />
<FiSearch />
</button>
</div>
</div>
@@ -5,9 +5,10 @@ import { TbSunMoon } from 'react-icons/tb';
import { useTheme } from '@/hooks/useTheme';
import { themes } from '@/styles/themes';
import { getStyles } from '@/utils/style';
import { useReaderStore } from '@/store/readerStore';
import { useTranslation } from '@/hooks/useTranslation';
import { getStyles } from '@/utils/style';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const _ = useTranslation();
@@ -15,6 +16,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
useTheme();
const { getViews, getViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey)!;
const iconSize24 = useResponsiveSize(24);
useEffect(() => {
getViews().forEach((view) => {
@@ -33,7 +35,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
className={`btn btn-ghost btn-circle ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('auto')}
>
<TbSunMoon size={20} />
<TbSunMoon />
</button>
</div>
@@ -42,7 +44,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
className={`btn btn-ghost btn-circle ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('light')}
>
<MdOutlineLightMode size={20} />
<MdOutlineLightMode />
</button>
</div>
@@ -51,7 +53,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
className={`btn btn-ghost btn-circle ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('dark')}
>
<MdOutlineDarkMode size={20} />
<MdOutlineDarkMode />
</button>
</div>
</div>
@@ -80,9 +82,9 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
className='hidden'
/>
{themeColor === name ? (
<MdRadioButtonChecked size={24} />
<MdRadioButtonChecked size={iconSize24} />
) : (
<MdRadioButtonUnchecked size={24} />
<MdRadioButtonUnchecked size={iconSize24} />
)}
<span>{_(label)}</span>
</label>
@@ -27,7 +27,7 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{isFontLayoutSettingsGlobal && <MdCheck size={20} className='text-base-content' />}
{isFontLayoutSettingsGlobal && <MdCheck className='text-base-content' />}
</span>
<div
className='lg:tooltip'
@@ -3,6 +3,7 @@ import React from 'react';
import { FiChevronUp, FiChevronLeft } from 'react-icons/fi';
import { MdCheck } from 'react-icons/md';
import { useTranslation } from '@/hooks/useTranslation';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
interface DropdownProps {
family?: string;
@@ -22,6 +23,7 @@ const FontDropdown: React.FC<DropdownProps> = ({
onGetFontFamily,
}) => {
const _ = useTranslation();
const iconSize16 = useResponsiveSize(16);
return (
<div className='dropdown dropdown-top'>
<button
@@ -29,7 +31,7 @@ const FontDropdown: React.FC<DropdownProps> = ({
className='btn btn-sm flex items-center gap-1 px-[20px] font-normal normal-case'
>
<span style={{ fontFamily: onGetFontFamily(selected, family ?? '') }}>{selected}</span>
<FiChevronUp className='h-4 w-4' />
<FiChevronUp size={iconSize16} />
</button>
<ul
tabIndex={0}
@@ -39,7 +41,7 @@ const FontDropdown: React.FC<DropdownProps> = ({
<li key={option} onClick={() => onSelect(option)}>
<div className='flex items-center px-0'>
<span style={{ minWidth: '20px' }}>
{selected === option && <MdCheck size={20} className='text-base-content' />}
{selected === option && <MdCheck className='text-base-content' />}
</span>
<span style={{ fontFamily: onGetFontFamily(option, family ?? '') }}>{option}</span>
</div>
@@ -49,7 +51,7 @@ const FontDropdown: React.FC<DropdownProps> = ({
<li className='dropdown dropdown-left dropdown-top'>
<div className='flex items-center px-0'>
<span style={{ minWidth: '20px' }}>
<FiChevronLeft className='h-4 w-4' />
<FiChevronLeft size={iconSize16} />
</span>
<span>{_('System Fonts')}</span>
</div>
@@ -64,7 +66,7 @@ const FontDropdown: React.FC<DropdownProps> = ({
<li key={option} onClick={() => onSelect(option)}>
<div className='flex items-center px-2'>
<span style={{ minWidth: '20px' }}>
{selected === option && <MdCheck size={20} className='text-base-content' />}
{selected === option && <MdCheck className='text-base-content' />}
</span>
<span style={{ fontFamily: onGetFontFamily(option, family ?? '') }}>
{option}
@@ -153,7 +153,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
className={`btn btn-ghost btn-circle ${writingMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
onClick={() => setWritingMode('auto')}
>
<MdOutlineAutoMode size={20} />
<MdOutlineAutoMode />
</button>
</div>
@@ -162,7 +162,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
className={`btn btn-ghost btn-circle ${writingMode === 'horizontal-tb' ? 'btn-active bg-base-300' : ''}`}
onClick={() => setWritingMode('horizontal-tb')}
>
<MdOutlineTextRotationNone size={20} />
<MdOutlineTextRotationNone />
</button>
</div>
@@ -171,7 +171,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
className={`btn btn-ghost btn-circle ${writingMode === 'vertical-rl' ? 'btn-active bg-base-300' : ''}`}
onClick={() => setWritingMode('vertical-rl')}
>
<MdOutlineTextRotationDown size={20} />
<MdOutlineTextRotationDown />
</button>
</div>
</div>
@@ -74,7 +74,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
'btn btn-ghost btn-circle flex h-6 min-h-6 w-6 hover:bg-transparent focus:outline-none sm:hidden'
}
>
<MdArrowBackIosNew size={20} />
<MdArrowBackIosNew />
</button>
<div className='dialog-tabs flex h-10 max-w-[100%] flex-grow items-center justify-around pl-4'>
{tabConfig.map(({ tab, icon: Icon, label }) => (
@@ -86,7 +86,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
)}
onClick={() => setActivePanel(tab)}
>
<Icon size={20} className='mr-0' />
<Icon className='mr-0' />
{window.innerWidth >= 500 ? label : ''}
</button>
))}
@@ -95,7 +95,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
<Dropdown
className='dropdown-bottom dropdown-end'
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
toggleButton={<PiDotsThreeVerticalBold size={16} />}
toggleButton={<PiDotsThreeVerticalBold />}
>
<DialogMenu />
</Dropdown>
@@ -4,10 +4,12 @@ import { MdInfoOutline } from 'react-icons/md';
import { Book } from '@/types/book';
import { useTranslation } from '@/hooks/useTranslation';
import { eventDispatcher } from '@/utils/event';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
const BookCard = ({ book }: { book: Book }) => {
const { coverImageUrl, title, author } = book;
const _ = useTranslation();
const iconSize18 = useResponsiveSize(18);
const showBookDetails = () => {
eventDispatcher.dispatchSync('show-book-details', book);
@@ -33,7 +35,7 @@ const BookCard = ({ book }: { book: Book }) => {
className='btn btn-ghost hover:bg-base-300 h-6 min-h-6 w-6 rounded-full p-0 transition-colors'
aria-label={_('More Info')}
>
<MdInfoOutline size={18} className='fill-base-content' onClick={showBookDetails} />
<MdInfoOutline size={iconSize18} className='fill-base-content' onClick={showBookDetails} />
</button>
</div>
);
@@ -6,6 +6,7 @@ import { MdOutlineMenu, MdOutlinePushPin, MdPushPin } from 'react-icons/md';
import { MdArrowBackIosNew } from 'react-icons/md';
import useTrafficLight from '@/hooks/useTrafficLight';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
import Dropdown from '@/components/Dropdown';
import BookMenu from './BookMenu';
@@ -18,6 +19,8 @@ const SidebarHeader: React.FC<{
onToggleSearchBar: () => void;
}> = ({ isPinned, isSearchBarVisible, onGoToLibrary, onClose, onTogglePin, onToggleSearchBar }) => {
const { isTrafficLightVisible } = useTrafficLight();
const iconSize14 = useResponsiveSize(14);
const iconSize18 = useResponsiveSize(18);
return (
<div
className={clsx(
@@ -30,10 +33,10 @@ const SidebarHeader: React.FC<{
onClick={onClose}
className={'btn btn-ghost btn-circle flex h-6 min-h-6 w-6 hover:bg-transparent sm:hidden'}
>
<MdArrowBackIosNew size={20} />
<MdArrowBackIosNew />
</button>
<button className='btn btn-ghost h-8 min-h-8 w-8 p-0' onClick={onGoToLibrary}>
<GiBookshelf size={20} className='fill-base-content' />
<GiBookshelf className='fill-base-content' />
</button>
</div>
<div className='flex min-w-24 max-w-32 items-center justify-between sm:size-[70%]'>
@@ -44,7 +47,7 @@ const SidebarHeader: React.FC<{
isSearchBarVisible ? 'bg-base-300' : '',
)}
>
<FiSearch size={18} className='text-base-content' />
<FiSearch size={iconSize18} className='text-base-content' />
</button>
<Dropdown
className={clsx(
@@ -53,7 +56,7 @@ const SidebarHeader: React.FC<{
)}
menuClassName={window.innerWidth < 640 ? 'no-triangle mt-1' : 'dropdown-center mt-3'}
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
toggleButton={<MdOutlineMenu size={20} className='fill-base-content' />}
toggleButton={<MdOutlineMenu className='fill-base-content' />}
>
<BookMenu />
</Dropdown>
@@ -65,7 +68,7 @@ const SidebarHeader: React.FC<{
isPinned ? 'bg-base-300' : 'bg-base-300/65',
)}
>
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
{isPinned ? <MdPushPin size={iconSize14} /> : <MdOutlinePushPin size={iconSize14} />}
</button>
</div>
</div>
@@ -9,6 +9,7 @@ import { useTranslation } from '@/hooks/useTranslation';
import { BookSearchConfig, BookSearchResult } from '@/types/book';
import Dropdown from '@/components/Dropdown';
import SearchOptions from './SearchOptions';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
const MINIMUM_SEARCH_TERM_LENGTH_DEFAULT = 2;
const MINIMUM_SEARCH_TERM_LENGTH_CJK = 1;
@@ -43,6 +44,9 @@ const SearchBar: React.FC<SearchBarProps> = ({
const isSearchPending = useRef(false);
const searchTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
const iconSize12 = useResponsiveSize(12);
const iconSize16 = useResponsiveSize(16);
useEffect(() => {
handleSearchTermChange(searchTerm);
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -153,7 +157,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
<div className='relative p-2'>
<div className='bg-base-100 flex h-8 items-center rounded-lg'>
<div className='pl-3'>
<FaSearch className='text-gray-500' />
<FaSearch size={iconSize16} className='text-gray-500' />
</div>
<input
@@ -170,7 +174,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
<Dropdown
className='dropdown-bottom flex justify-center'
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0 rounded-none rounded-r-lg'
toggleButton={<FaChevronDown size={12} className='text-gray-500' />}
toggleButton={<FaChevronDown size={iconSize12} className='text-gray-500' />}
>
<SearchOptions
searchConfig={searchConfig}
@@ -22,7 +22,7 @@ const Option: React.FC<OptionProps> = ({ label, isActive, onClick }) => (
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{isActive && <MdCheck size={20} className='text-base-content' />}
{isActive && <MdCheck className='text-base-content' />}
</span>
<span className='ml-2'>{label}</span>
</div>
@@ -37,11 +37,11 @@ const TabNavigation: React.FC<{
>
<div className={clsx('')} onClick={() => onTabChange(tab)}>
{tab === 'toc' ? (
<MdToc size={20} className='mx-auto' />
<MdToc className='mx-auto' />
) : tab === 'annotations' ? (
<MdEditNote size={20} className='mx-auto' />
<MdEditNote className='mx-auto' />
) : (
<MdBookmarkBorder size={20} className='mx-auto' />
<MdBookmarkBorder className='mx-auto' />
)}
</div>
</div>
@@ -6,6 +6,7 @@ import { MdPlayCircle, MdPauseCircle, MdFastRewind, MdFastForward, MdStop } from
import { RiVoiceAiFill } from 'react-icons/ri';
import { MdCheck } from 'react-icons/md';
import { TTSVoice } from '@/services/tts';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
type TTSPanelProps = {
bookKey: string;
@@ -42,6 +43,9 @@ const TTSPanel = ({
const [rate, setRate] = useState(viewSettings?.ttsRate ?? 1.0);
const [selectedVoice, setSelectedVoice] = useState(viewSettings?.ttsVoice ?? '');
const iconSize32 = useResponsiveSize(32);
const iconSize48 = useResponsiveSize(48);
const handleSetRate = (e: ChangeEvent<HTMLInputElement>) => {
let newRate = parseFloat(e.target.value);
newRate = Math.max(0.2, Math.min(3.0, newRate));
@@ -108,24 +112,24 @@ const TTSPanel = ({
</div>
<div className='flex items-center justify-between space-x-2'>
<button onClick={onBackward} className='hover:bg-base-200/75 rounded-full p-1'>
<MdFastRewind size={32} />
<MdFastRewind size={iconSize32} />
</button>
<button onClick={onTogglePlay} className='hover:bg-base-200/75 rounded-full p-1'>
{isPlaying ? (
<MdPauseCircle size={48} className='fill-primary' />
<MdPauseCircle size={iconSize48} className='fill-primary' />
) : (
<MdPlayCircle size={48} className='fill-primary' />
<MdPlayCircle size={iconSize48} className='fill-primary' />
)}
</button>
<button onClick={onForward} className='hover:bg-base-200/75 rounded-full p-1'>
<MdFastForward size={32} />
<MdFastForward size={iconSize32} />
</button>
<button onClick={onStop} className='hover:bg-base-200/75 rounded-full p-1'>
<MdStop size={32} />
<MdStop size={iconSize32} />
</button>
<div className='dropdown dropdown-top'>
<button tabIndex={0} className='hover:bg-base-200/75 rounded-full p-1'>
<RiVoiceAiFill size={32} />
<RiVoiceAiFill size={iconSize32} />
</button>
<ul
tabIndex={0}
@@ -141,9 +145,7 @@ const TTSPanel = ({
>
<div className='flex items-center px-2'>
<span style={{ minWidth: '20px' }}>
{selectedVoice === voice.id && (
<MdCheck size={20} className='text-base-content' />
)}
{selectedVoice === voice.id && <MdCheck className='text-base-content' />}
</span>
<span className={clsx('text-sm', voice.disabled && 'text-gray-400')}>
{voice.name}
@@ -43,6 +43,10 @@ export const handleMousedown = (bookKey: string, event: MouseEvent) => {
};
export const handleMouseup = (bookKey: string, event: MouseEvent) => {
// we will handle mouse back and forward buttons ourselves
if ([3, 4].includes(event.button)) {
event.preventDefault();
}
window.postMessage(
{
type: 'iframe-mouseup',
+1 -1
View File
@@ -67,7 +67,7 @@ const Dialog: React.FC<DialogProps> = ({
'btn btn-ghost btn-circle flex h-6 min-h-6 w-6 hover:bg-transparent focus:outline-none sm:hidden'
}
>
<MdArrowBackIosNew size={20} />
<MdArrowBackIosNew />
</button>
<div className='z-15 pointer-events-none absolute inset-0 flex h-11 items-center justify-center'>
<span className='line-clamp-1 text-center font-bold'>{title ?? ''}</span>
+18 -9
View File
@@ -1,16 +1,25 @@
'use client';
import { AuthProvider } from '@/context/AuthContext';
import { EnvProvider } from '@/context/EnvContext';
import { CSPostHogProvider } from '@/context/PHContext';
import { SyncProvider } from '@/context/SyncContext';
import { IconContext } from 'react-icons';
import { useDefaultIconSize } from '@/hooks/useResponsiveSize';
const Providers = ({ children }: { children: React.ReactNode }) => (
<CSPostHogProvider>
<EnvProvider>
<AuthProvider>
<SyncProvider>{children}</SyncProvider>
</AuthProvider>
</EnvProvider>
</CSPostHogProvider>
);
const Providers = ({ children }: { children: React.ReactNode }) => {
const iconSize = useDefaultIconSize();
return (
<CSPostHogProvider>
<EnvProvider>
<AuthProvider>
<IconContext.Provider value={{ size: `${iconSize}px` }}>
<SyncProvider>{children}</SyncProvider>
</IconContext.Provider>
</AuthProvider>
</EnvProvider>
</CSPostHogProvider>
);
};
export default Providers;
@@ -0,0 +1,14 @@
import { useMediaQuery } from 'react-responsive';
// use desktop size as base size
export const useResponsiveSize = (baseSize: number) => {
const isPhone = useMediaQuery({ maxWidth: 480 });
const isTablet = useMediaQuery({ minWidth: 481, maxWidth: 1024 });
if (isPhone) return baseSize * 1.5;
if (isTablet) return baseSize * 1.25;
return baseSize;
};
export const useDefaultIconSize = () => {
return useResponsiveSize(20);
};
+39
View File
@@ -125,6 +125,9 @@ importers:
react-icons:
specifier: ^5.4.0
version: 5.4.0(react@19.0.0)
react-responsive:
specifier: ^10.0.0
version: 10.0.0(react@19.0.0)
tinycolor2:
specifier: ^1.6.0
version: 1.6.0
@@ -1945,6 +1948,9 @@ packages:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'}
css-mediaquery@0.1.2:
resolution: {integrity: sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==}
css-selector-tokenizer@0.8.0:
resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==}
@@ -2559,6 +2565,9 @@ packages:
engines: {node: '>=18'}
hasBin: true
hyphenate-style-name@1.1.0:
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
i18next-browser-languagedetector@8.0.2:
resolution: {integrity: sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==}
@@ -2957,6 +2966,9 @@ packages:
make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
matchmediaquery@0.4.2:
resolution: {integrity: sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==}
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -3489,6 +3501,12 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-responsive@10.0.0:
resolution: {integrity: sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8.0'
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -3669,6 +3687,9 @@ packages:
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
engines: {node: '>=8'}
shallow-equal@3.1.0:
resolution: {integrity: sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==}
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -6142,6 +6163,8 @@ snapshots:
crypto-random-string@2.0.0: {}
css-mediaquery@0.1.2: {}
css-selector-tokenizer@0.8.0:
dependencies:
cssesc: 3.0.0
@@ -6898,6 +6921,8 @@ snapshots:
husky@9.1.7: {}
hyphenate-style-name@1.1.0: {}
i18next-browser-languagedetector@8.0.2:
dependencies:
'@babel/runtime': 7.26.0
@@ -7270,6 +7295,10 @@ snapshots:
make-error@1.3.6:
optional: true
matchmediaquery@0.4.2:
dependencies:
css-mediaquery: 0.1.2
merge-stream@2.0.0: {}
merge2@1.4.1: {}
@@ -7743,6 +7772,14 @@ snapshots:
react-is@16.13.1: {}
react-responsive@10.0.0(react@19.0.0):
dependencies:
hyphenate-style-name: 1.1.0
matchmediaquery: 0.4.2
prop-types: 15.8.1
react: 19.0.0
shallow-equal: 3.1.0
react@18.3.1:
dependencies:
loose-envify: 1.4.0
@@ -7964,6 +8001,8 @@ snapshots:
dependencies:
kind-of: 6.0.3
shallow-equal@3.1.0: {}
sharp@0.33.5:
dependencies:
color: 4.2.3