From 533324e5582ee0b58b7f1661548d28eac0136cff Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Wed, 22 Jan 2025 18:33:00 +0100 Subject: [PATCH] Responsive icon sizes for phones, tablets and desktops, closes #214 (#217) --- apps/readest-app/package.json | 1 + apps/readest-app/src/app/auth/page.tsx | 6 +-- .../src/app/library/components/Bookshelf.tsx | 13 +++---- .../app/library/components/LibraryHeader.tsx | 2 +- .../app/library/components/SettingsMenu.tsx | 8 +--- .../app/reader/components/BookmarkToggler.tsx | 4 +- .../src/app/reader/components/FooterBar.tsx | 18 +++------ .../src/app/reader/components/HeaderBar.tsx | 2 +- .../app/reader/components/NotebookToggler.tsx | 4 +- .../app/reader/components/SettingsToggler.tsx | 2 +- .../app/reader/components/SidebarToggler.tsx | 4 +- .../src/app/reader/components/ViewMenu.tsx | 18 +++------ .../components/annotator/HighlightOptions.tsx | 9 +++-- .../components/annotator/PopupButton.tsx | 2 +- .../app/reader/components/notebook/Header.tsx | 8 ++-- .../reader/components/settings/ColorPanel.tsx | 14 ++++--- .../reader/components/settings/DialogMenu.tsx | 2 +- .../components/settings/FontDropDown.tsx | 10 +++-- .../components/settings/LayoutPanel.tsx | 6 +-- .../components/settings/SettingsDialog.tsx | 6 +-- .../reader/components/sidebar/BookCard.tsx | 4 +- .../app/reader/components/sidebar/Header.tsx | 13 ++++--- .../reader/components/sidebar/SearchBar.tsx | 8 +++- .../components/sidebar/SearchOptions.tsx | 2 +- .../components/sidebar/TabNavigation.tsx | 6 +-- .../app/reader/components/tts/TTSPanel.tsx | 20 +++++----- .../app/reader/utils/iframeEventHandlers.ts | 4 ++ apps/readest-app/src/components/Dialog.tsx | 2 +- apps/readest-app/src/components/Providers.tsx | 27 ++++++++----- .../src/hooks/useResponsiveSize.ts | 14 +++++++ pnpm-lock.yaml | 39 +++++++++++++++++++ 31 files changed, 170 insertions(+), 108 deletions(-) create mode 100644 apps/readest-app/src/hooks/useResponsiveSize.ts diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index 52278128..38edff8c 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -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" }, diff --git a/apps/readest-app/src/app/auth/page.tsx b/apps/readest-app/src/app/auth/page.tsx index 31f24880..29f3a371 100644 --- a/apps/readest-app/src/app/auth/page.tsx +++ b/apps/readest-app/src/app/auth/page.tsx @@ -45,7 +45,7 @@ const ProviderLogin: React.FC = ({ provider, handleSignIn, Ic 'bg-base-100 border-gray-300 shadow-sm transition hover:bg-gray-50', )} > - + {label} ); @@ -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' > - +
- + = ({ libraryBooks, isSelectMode, onImp const [importBookUrl] = useState(searchParams?.get('url') || ''); const isImportingBook = useRef(false); const [showDetailsBook, setShowDetailsBook] = useState(null); + const iconSize15 = useResponsiveSize(15); const showBookDetailsModal = (book: Book) => { setShowDetailsBook(book); @@ -232,12 +234,9 @@ const Bookshelf: React.FC = ({ libraryBooks, isSelectMode, onImp {isSelectMode && (
{selectedBooks.includes(item.hash) ? ( - + ) : ( - + )}
)} @@ -268,7 +267,7 @@ const Bookshelf: React.FC = ({ libraryBooks, isSelectMode, onImp } }} > - + )}
@@ -285,7 +284,7 @@ const Bookshelf: React.FC = ({ libraryBooks, isSelectMode, onImp } }} > - + )} diff --git a/apps/readest-app/src/app/library/components/LibraryHeader.tsx b/apps/readest-app/src/app/library/components/LibraryHeader.tsx index 196ed6f3..8d0e722c 100644 --- a/apps/readest-app/src/app/library/components/LibraryHeader.tsx +++ b/apps/readest-app/src/app/library/components/LibraryHeader.tsx @@ -101,7 +101,7 @@ const LibraryHeader: React.FC = ({ } + toggleButton={} > diff --git a/apps/readest-app/src/app/library/components/SettingsMenu.tsx b/apps/readest-app/src/app/library/components/SettingsMenu.tsx index c6414a84..ec67f03f 100644 --- a/apps/readest-app/src/app/library/components/SettingsMenu.tsx +++ b/apps/readest-app/src/app/library/components/SettingsMenu.tsx @@ -75,7 +75,7 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { height={20} /> ) : ( - + ) } > @@ -84,11 +84,7 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { ) : ( - } - onClick={handleUserLogin} - > + } onClick={handleUserLogin}> )}
{isWebApp && } diff --git a/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx b/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx index 5880498e..ab978262 100644 --- a/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx +++ b/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx @@ -97,9 +97,9 @@ const BookmarkToggler: React.FC = ({ bookKey }) => { @@ -124,7 +124,7 @@ const ViewMenu: React.FC = ({ : undefined} + icon={isScrolledMode ? : undefined} onClick={toggleScrolledMode} /> @@ -138,20 +138,12 @@ const ViewMenu: React.FC = ({ ? _('Light Mode') : _('Auto Mode') } - icon={ - themeMode === 'dark' ? ( - - ) : themeMode === 'light' ? ( - - ) : ( - - ) - } + icon={themeMode === 'dark' ? : themeMode === 'light' ? : } onClick={cycleThemeMode} /> : undefined} + icon={isInvertedColors ? : undefined} onClick={toggleInvertedColors} disabled={!isDarkMode} /> diff --git a/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx b/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx index bbf6d1f2..273c5242 100644 --- a/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx @@ -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 = ({ const globalReadSettings = settings.globalReadSettings; const [selectedStyle, setSelectedStyle] = React.useState(_selectedStyle); const [selectedColor, setSelectedColor] = React.useState(_selectedColor); + const iconSize16 = useResponsiveSize(16); const handleSelectStyle = (style: HighlightStyle) => { globalReadSettings.highlightStyle = style; @@ -84,7 +85,7 @@ const HighlightOptions: React.FC = ({ )} > {selectedColor === color && ( - + )} ))} diff --git a/apps/readest-app/src/app/reader/components/annotator/PopupButton.tsx b/apps/readest-app/src/app/reader/components/annotator/PopupButton.tsx index 07980ecd..9e5d5205 100644 --- a/apps/readest-app/src/app/reader/components/annotator/PopupButton.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/PopupButton.tsx @@ -22,7 +22,7 @@ const PopupButton: React.FC = ({ showTooltip, tooltipText, Ico onClick={handleClick} className='my-2 flex h-8 min-h-8 w-8 items-center justify-center p-0' > - + ); diff --git a/apps/readest-app/src/app/reader/components/notebook/Header.tsx b/apps/readest-app/src/app/reader/components/notebook/Header.tsx index 09d04f4f..d43b1c2a 100644 --- a/apps/readest-app/src/app/reader/components/notebook/Header.tsx +++ b/apps/readest-app/src/app/reader/components/notebook/Header.tsx @@ -15,7 +15,7 @@ const NotebookHeader: React.FC<{ return (
- +
{_('Notebook')}
@@ -26,16 +26,16 @@ const NotebookHeader: React.FC<{ isPinned ? 'bg-base-300' : 'bg-base-300/65', )} > - {isPinned ? : } + {isPinned ? : }
diff --git a/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx b/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx index 971b9800..f580b497 100644 --- a/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx @@ -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')} > - + @@ -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')} > - + @@ -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')} > - + @@ -80,9 +82,9 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { className='hidden' /> {themeColor === name ? ( - + ) : ( - + )} {_(label)} diff --git a/apps/readest-app/src/app/reader/components/settings/DialogMenu.tsx b/apps/readest-app/src/app/reader/components/settings/DialogMenu.tsx index 54c3dab7..cf43f35b 100644 --- a/apps/readest-app/src/app/reader/components/settings/DialogMenu.tsx +++ b/apps/readest-app/src/app/reader/components/settings/DialogMenu.tsx @@ -27,7 +27,7 @@ const DialogMenu: React.FC = ({ toggleDropdown }) => { >
- {isFontLayoutSettingsGlobal && } + {isFontLayoutSettingsGlobal && }
= ({ onGetFontFamily, }) => { const _ = useTranslation(); + const iconSize16 = useResponsiveSize(16); return (
    = ({
  • onSelect(option)}>
    - {selected === option && } + {selected === option && } {option}
    @@ -49,7 +51,7 @@ const FontDropdown: React.FC = ({
  • - + {_('System Fonts')}
    @@ -64,7 +66,7 @@ const FontDropdown: React.FC = ({
  • onSelect(option)}>
    - {selected === option && } + {selected === option && } {option} diff --git a/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx b/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx index ed9734cd..c2a18ecb 100644 --- a/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx @@ -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')} > - +
    @@ -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')} > - +
@@ -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')} > - +
diff --git a/apps/readest-app/src/app/reader/components/settings/SettingsDialog.tsx b/apps/readest-app/src/app/reader/components/settings/SettingsDialog.tsx index ecc9ac54..c9880507 100644 --- a/apps/readest-app/src/app/reader/components/settings/SettingsDialog.tsx +++ b/apps/readest-app/src/app/reader/components/settings/SettingsDialog.tsx @@ -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' } > - +
{tabConfig.map(({ tab, icon: Icon, label }) => ( @@ -86,7 +86,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo )} onClick={() => setActivePanel(tab)} > - + {window.innerWidth >= 500 ? label : ''} ))} @@ -95,7 +95,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo } + toggleButton={} > diff --git a/apps/readest-app/src/app/reader/components/sidebar/BookCard.tsx b/apps/readest-app/src/app/reader/components/sidebar/BookCard.tsx index 6e7dd33e..5899f6bb 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/BookCard.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/BookCard.tsx @@ -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')} > - +
); diff --git a/apps/readest-app/src/app/reader/components/sidebar/Header.tsx b/apps/readest-app/src/app/reader/components/sidebar/Header.tsx index 4fb2c4c3..8d26c325 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/Header.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/Header.tsx @@ -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 (
- +
@@ -44,7 +47,7 @@ const SidebarHeader: React.FC<{ isSearchBarVisible ? 'bg-base-300' : '', )} > - + } + toggleButton={} > @@ -65,7 +68,7 @@ const SidebarHeader: React.FC<{ isPinned ? 'bg-base-300' : 'bg-base-300/65', )} > - {isPinned ? : } + {isPinned ? : }
diff --git a/apps/readest-app/src/app/reader/components/sidebar/SearchBar.tsx b/apps/readest-app/src/app/reader/components/sidebar/SearchBar.tsx index c1a86498..347a9469 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/SearchBar.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/SearchBar.tsx @@ -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 = ({ const isSearchPending = useRef(false); const searchTimeout = useRef | 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 = ({
- +
= ({ } + toggleButton={} > = ({ label, isActive, onClick }) => ( >
- {isActive && } + {isActive && } {label}
diff --git a/apps/readest-app/src/app/reader/components/sidebar/TabNavigation.tsx b/apps/readest-app/src/app/reader/components/sidebar/TabNavigation.tsx index 64c9e110..6433f254 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/TabNavigation.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/TabNavigation.tsx @@ -37,11 +37,11 @@ const TabNavigation: React.FC<{ >
onTabChange(tab)}> {tab === 'toc' ? ( - + ) : tab === 'annotations' ? ( - + ) : ( - + )}
diff --git a/apps/readest-app/src/app/reader/components/tts/TTSPanel.tsx b/apps/readest-app/src/app/reader/components/tts/TTSPanel.tsx index 60648417..4efd74db 100644 --- a/apps/readest-app/src/app/reader/components/tts/TTSPanel.tsx +++ b/apps/readest-app/src/app/reader/components/tts/TTSPanel.tsx @@ -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) => { let newRate = parseFloat(e.target.value); newRate = Math.max(0.2, Math.min(3.0, newRate)); @@ -108,24 +112,24 @@ const TTSPanel = ({
    - {selectedVoice === voice.id && ( - - )} + {selectedVoice === voice.id && } {voice.name} diff --git a/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts b/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts index 336cbfda..ecb7415a 100644 --- a/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts +++ b/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts @@ -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', diff --git a/apps/readest-app/src/components/Dialog.tsx b/apps/readest-app/src/components/Dialog.tsx index 4ae1e1bc..c723b7c1 100644 --- a/apps/readest-app/src/components/Dialog.tsx +++ b/apps/readest-app/src/components/Dialog.tsx @@ -67,7 +67,7 @@ const Dialog: React.FC = ({ 'btn btn-ghost btn-circle flex h-6 min-h-6 w-6 hover:bg-transparent focus:outline-none sm:hidden' } > - +
    {title ?? ''} diff --git a/apps/readest-app/src/components/Providers.tsx b/apps/readest-app/src/components/Providers.tsx index 643d4e6e..11ca6b02 100644 --- a/apps/readest-app/src/components/Providers.tsx +++ b/apps/readest-app/src/components/Providers.tsx @@ -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 }) => ( - - - - {children} - - - -); +const Providers = ({ children }: { children: React.ReactNode }) => { + const iconSize = useDefaultIconSize(); + return ( + + + + + {children} + + + + + ); +}; export default Providers; diff --git a/apps/readest-app/src/hooks/useResponsiveSize.ts b/apps/readest-app/src/hooks/useResponsiveSize.ts new file mode 100644 index 00000000..27ba01e5 --- /dev/null +++ b/apps/readest-app/src/hooks/useResponsiveSize.ts @@ -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); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bae9880..ab76deb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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