Responsive layout for sidebar and settings dialog on mobile devices (#184)

This commit is contained in:
Huang Xin
2025-01-17 00:17:51 +01:00
committed by GitHub
parent d24f737428
commit 010568eeb1
17 changed files with 248 additions and 144 deletions
@@ -11,6 +11,7 @@ import Dropdown from '@/components/Dropdown';
import SidebarToggler from './SidebarToggler';
import BookmarkToggler from './BookmarkToggler';
import NotebookToggler from './NotebookToggler';
import SettingsToggler from './SettingsToggler';
import ViewMenu from './ViewMenu';
interface HeaderBarProps {
@@ -57,16 +58,17 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
onMouseEnter={() => setHoveredBookKey(bookKey)}
onMouseLeave={() => setHoveredBookKey('')}
>
<div className='sidebar-bookmark-toggler flex h-full items-center'>
<div className='sidebar-bookmark-toggler flex h-full items-center space-x-2 sm:space-x-4'>
<SidebarToggler bookKey={bookKey} />
<BookmarkToggler bookKey={bookKey} />
</div>
<div className='header-title flex flex-1 items-center justify-center'>
<div className='header-title pointer-events-none absolute inset-0 flex items-center justify-center'>
<h2 className='line-clamp-1 max-w-[80%] text-center text-xs font-semibold'>{bookTitle}</h2>
</div>
<div className='flex h-full items-center space-x-2'>
<div className='ml-auto flex h-full items-center space-x-2 sm:space-x-4'>
<SettingsToggler />
<NotebookToggler bookKey={bookKey} />
<Dropdown
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
@@ -1,5 +1,5 @@
import React from 'react';
import { TbLayoutSidebarRight, TbLayoutSidebarRightFilled } from 'react-icons/tb';
import { LuNotebookPen } from 'react-icons/lu';
import { useSidebarStore } from '@/store/sidebarStore';
import { useNotebookStore } from '@/store/notebookStore';
@@ -26,9 +26,9 @@ const NotebookToggler: React.FC<NotebookTogglerProps> = ({ bookKey }) => {
<Button
icon={
sideBarBookKey == bookKey && isNotebookVisible ? (
<TbLayoutSidebarRightFilled size={20} className='text-base-content' />
<LuNotebookPen size={16} className='text-base-content' />
) : (
<TbLayoutSidebarRight size={20} className='text-base-content' />
<LuNotebookPen size={16} className='text-base-content' />
)
}
onClick={handleToggleSidebar}
@@ -0,0 +1,24 @@
import React from 'react';
import { RiFontSize } from 'react-icons/ri';
import { useTranslation } from '@/hooks/useTranslation';
import { useSettingsStore } from '@/store/settingsStore';
import Button from '@/components/Button';
const SettingsToggler = () => {
const _ = useTranslation();
const { isFontLayoutSettingsDialogOpen, setFontLayoutSettingsDialogOpen } = useSettingsStore();
const handleToggleSettings = () => {
setFontLayoutSettingsDialogOpen(!isFontLayoutSettingsDialogOpen);
};
return (
<Button
icon={<RiFontSize size={16} className='text-base-content' />}
onClick={handleToggleSettings}
tooltip={_('Font & Layout')}
tooltipDirection='bottom'
></Button>
);
};
export default SettingsToggler;
@@ -92,7 +92,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
<button
onClick={zoomOut}
className={clsx(
'hover:bg-base-200 text-base-content rounded-full p-2',
'hover:bg-base-300 text-base-content rounded-full p-2',
zoomLevel <= MIN_ZOOM_LEVEL && 'btn-disabled text-gray-400',
)}
>
@@ -100,7 +100,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
</button>
<button
className={clsx(
'hover:bg-base-200 text-base-content h-8 min-h-8 w-[50%] rounded-md p-1 text-center',
'hover:bg-base-300 text-base-content h-8 min-h-8 w-[50%] rounded-md p-1 text-center',
)}
onClick={resetZoom}
>
@@ -109,7 +109,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
<button
onClick={zoomIn}
className={clsx(
'hover:bg-base-200 text-base-content rounded-full p-2',
'hover:bg-base-300 text-base-content rounded-full p-2',
zoomLevel >= MAX_ZOOM_LEVEL && 'btn-disabled text-gray-400',
)}
>
@@ -1,26 +1,44 @@
import clsx from 'clsx';
import React from 'react';
import { FiSearch } from 'react-icons/fi';
import { LuNotebookPen } from 'react-icons/lu';
import { MdOutlinePushPin, MdPushPin } from 'react-icons/md';
import { useTranslation } from '@/hooks/useTranslation';
const NotebookHeader: React.FC<{
isPinned: boolean;
handleClose: () => void;
handleTogglePin: () => void;
}> = ({ isPinned, handleTogglePin }) => {
}> = ({ isPinned, handleClose, handleTogglePin }) => {
const _ = useTranslation();
return (
<div className='notebook-header relative flex h-11 items-center px-3'>
<div className='absolute inset-0 flex items-center justify-center'>
<div className='absolute inset-0 flex items-center justify-center space-x-2'>
<LuNotebookPen size={14} />
<div className='notebook-title text-sm font-medium'>{_('Notebook')}</div>
</div>
<div className='z-10 flex items-center space-x-3'>
<div className='z-10 flex items-center gap-x-3'>
<button
onClick={handleTogglePin}
className={`${isPinned ? 'bg-base-300' : 'bg-base-300/65'} btn btn-ghost btn-circle h-6 min-h-6 w-6`}
className={clsx(
'btn btn-ghost btn-circle hidden h-6 min-h-6 w-6 sm:flex',
isPinned ? 'bg-base-300' : 'bg-base-300/65',
)}
>
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
</button>
<button
onClick={handleClose}
className={'bg-base-300/65 btn btn-ghost btn-circle h-6 min-h-6 w-6 sm:hidden'}
>
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'>
<path
fill='currentColor'
d='M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z'
/>
</svg>
</button>
<button className='btn btn-ghost left-0 h-8 min-h-8 w-8 p-0'>
<FiSearch size={18} />
</button>
@@ -8,13 +8,14 @@ import { useSidebarStore } from '@/store/sidebarStore';
import { useNotebookStore } from '@/store/notebookStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useEnv } from '@/context/EnvContext';
import useDragBar from '../../hooks/useDragBar';
import NotebookHeader from './Header';
import NoteEditor from './NoteEditor';
import { TextSelection } from '@/utils/sel';
import { BookNote } from '@/types/book';
import { uniqueId } from '@/utils/misc';
import { eventDispatcher } from '@/utils/event';
import useDragBar from '../../hooks/useDragBar';
import BooknoteItem from '../sidebar/BooknoteItem';
import NotebookHeader from './Header';
import NoteEditor from './NoteEditor';
const MIN_NOTEBOOK_WIDTH = 0.15;
const MAX_NOTEBOOK_WIDTH = 0.45;
@@ -31,10 +32,23 @@ const Notebook: React.FC = ({}) => {
const { setNotebookWidth, setNotebookVisible, toggleNotebookPin } = useNotebookStore();
const { setNotebookNewAnnotation, setNotebookEditAnnotation } = useNotebookStore();
const onNavigateEvent = async () => {
const pinButton = document.querySelector('.sidebar-pin-btn');
const isPinButtonHidden = !pinButton || window.getComputedStyle(pinButton).display === 'none';
if (isPinButtonHidden) {
setNotebookVisible(false);
}
};
useEffect(() => {
setNotebookWidth(settings.globalReadSettings.notebookWidth);
setNotebookPin(settings.globalReadSettings.isNotebookPinned);
setNotebookVisible(settings.globalReadSettings.isNotebookPinned);
eventDispatcher.on('navigate', onNavigateEvent);
return () => {
eventDispatcher.off('navigate', onNavigateEvent);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -135,11 +149,23 @@ const Notebook: React.FC = ({}) => {
position: isNotebookPinned ? 'relative' : 'absolute',
}}
>
<style jsx>{`
@media (max-width: 640px) {
.notebook-container {
width: 100%;
min-width: 100%;
}
}
`}</style>
<div
className='drag-bar absolute left-0 top-0 h-full w-0.5 cursor-col-resize'
onMouseDown={handleMouseDown}
/>
<NotebookHeader isPinned={isNotebookPinned} handleTogglePin={handleTogglePin} />
<NotebookHeader
isPinned={isNotebookPinned}
handleClose={() => setNotebookVisible(false)}
handleTogglePin={handleTogglePin}
/>
<div className='max-h-[calc(100vh-44px)] overflow-y-auto px-3'>
<div>{excerptNotes.length > 0 && <p className='pt-1 text-sm'>{_('Excerpts')}</p>}</div>
<ul className=''>
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { BookConfig } from '@/types/book';
import { useSettingsStore } from '@/store/settingsStore';
@@ -18,6 +19,12 @@ import MiscPanel from './MiscPanel';
type SettingsPanelType = 'Font' | 'Layout' | 'Color' | 'Misc';
type TabConfig = {
tab: SettingsPanelType;
icon: React.ElementType;
label: string;
};
const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ bookKey }) => {
const _ = useTranslation();
const [activePanel, setActivePanel] = useState<SettingsPanelType>('Font');
@@ -37,39 +44,47 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const tabConfig = [
{
tab: 'Font',
icon: RiFontSize,
label: _('Font'),
},
{
tab: 'Layout',
icon: RiDashboardLine,
label: _('Layout'),
},
{
tab: 'Color',
icon: VscSymbolColor,
label: _('Color'),
},
{
tab: 'Misc',
icon: IoAccessibilityOutline,
label: _('Misc'),
},
] as TabConfig[];
return (
<dialog className='modal modal-open min-w-90 w-full !bg-[rgba(0,0,0,0.2)]'>
<div className='modal-box settings-content flex h-[65%] w-1/2 min-w-[480px] max-w-full flex-col p-0'>
<dialog className='modal modal-open sm:min-w-90 h-full w-full !bg-[rgba(0,0,0,0.2)] sm:w-full'>
<div className='modal-box settings-content flex h-full max-h-full w-full max-w-full flex-col rounded-none p-0 sm:h-[65%] sm:w-1/2 sm:min-w-[480px] sm:max-w-full sm:rounded-2xl'>
<div className='dialog-header bg-base-100 sticky top-0 z-10 flex items-center justify-center px-4 pt-2'>
<div className='dialog-tabs flex h-10 max-w-[80%] flex-grow items-center justify-around'>
<button
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Font' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Font')}
>
<RiFontSize size={20} className='mr-0' />
{_('Font')}
</button>
<button
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Layout' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Layout')}
>
<RiDashboardLine size={20} className='mr-0' />
{_('Layout')}
</button>
<button
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Color' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Color')}
>
<VscSymbolColor size={20} className='mr-0' />
{_('Color')}
</button>
<button
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Misc' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Misc')}
>
<IoAccessibilityOutline size={20} className='mr-0' />
{_('Misc')}
</button>
<div className='dialog-tabs flex h-10 max-w-[75%] flex-grow items-center justify-around sm:max-w-[80%]'>
{tabConfig.map(({ tab, icon: Icon, label }) => (
<button
key={tab}
className={clsx(
'btn btn-ghost text-base-content h-8 min-h-8',
activePanel === tab ? 'btn-active' : '',
)}
onClick={() => setActivePanel(tab)}
>
<Icon size={20} className='mr-0' />
{window.innerWidth >= 500 ? label : ''}
</button>
))}
</div>
<div className='flex h-full items-center justify-end'>
<Dropdown
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import React from 'react';
import Image from 'next/image';
@@ -10,10 +11,11 @@ import { DOWNLOAD_READEST_URL } from '@/services/constants';
import useBooksManager from '../../hooks/useBooksManager';
interface BookMenuProps {
menuClassName?: string;
setIsDropdownOpen?: (isOpen: boolean) => void;
}
const BookMenu: React.FC<BookMenuProps> = ({ setIsDropdownOpen }) => {
const BookMenu: React.FC<BookMenuProps> = ({ menuClassName, setIsDropdownOpen }) => {
const _ = useTranslation();
const { library } = useLibraryStore();
const { openParallelView } = useBooksManager();
@@ -39,7 +41,10 @@ const BookMenu: React.FC<BookMenuProps> = ({ setIsDropdownOpen }) => {
return (
<div
tabIndex={0}
className='book-menu dropdown-content dropdown-center border-base-100 z-20 mt-3 w-60 shadow-2xl'
className={clsx(
'book-menu dropdown-content border-base-100 z-20 w-60 shadow-2xl',
menuClassName,
)}
>
<MenuItem label={_('Parallel Read')} noIcon>
<ul className='max-h-60 overflow-y-auto'>
@@ -9,6 +9,7 @@ import { useNotebookStore } from '@/store/notebookStore';
import { useBookDataStore } from '@/store/bookDataStore';
import { useTranslation } from '@/hooks/useTranslation';
import useScrollToItem from '../../hooks/useScrollToItem';
import { eventDispatcher } from '@/utils/event';
interface BooknoteItemProps {
bookKey: string;
@@ -29,6 +30,8 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item }) => {
const handleClickItem = (event: React.MouseEvent) => {
event.preventDefault();
eventDispatcher.dispatch('navigate', { bookKey, cfi });
getView(bookKey)?.goTo(cfi);
if (note) {
setNotebookVisible(true);
@@ -12,9 +12,10 @@ const SidebarHeader: React.FC<{
isPinned: boolean;
isSearchBarVisible: boolean;
onGoToLibrary: () => void;
onClose: () => void;
onTogglePin: () => void;
onToggleSearchBar: () => void;
}> = ({ isPinned, isSearchBarVisible, onGoToLibrary, onTogglePin, onToggleSearchBar }) => {
}> = ({ isPinned, isSearchBarVisible, onGoToLibrary, onClose, onTogglePin, onToggleSearchBar }) => {
const { isTrafficLightVisible } = useTrafficLight();
return (
<div
@@ -39,7 +40,11 @@ const SidebarHeader: React.FC<{
<FiSearch size={18} className='text-base-content' />
</button>
<Dropdown
className='dropdown-bottom flex justify-center'
className={clsx(
window.innerWidth < 640 && 'dropdown-end',
'dropdown-bottom flex justify-center',
)}
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' />}
>
@@ -48,10 +53,24 @@ const SidebarHeader: React.FC<{
<div className='right-0 flex h-8 w-8 items-center justify-center'>
<button
onClick={onTogglePin}
className={`${isPinned ? 'bg-base-300' : 'bg-base-300/65'} btn btn-ghost btn-circle h-6 min-h-6 w-6`}
className={clsx(
'sidebar-pin-btn btn btn-ghost btn-circle hidden h-6 min-h-6 w-6 sm:flex',
isPinned ? 'bg-base-300' : 'bg-base-300/65',
)}
>
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
</button>
<button
onClick={onClose}
className={'bg-base-300/65 btn btn-ghost btn-circle h-6 min-h-6 w-6 sm:hidden'}
>
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'>
<path
fill='currentColor'
d='M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z'
/>
</svg>
</button>
</div>
</div>
</div>
@@ -166,7 +166,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
className='w-full bg-transparent p-2 font-sans text-sm font-light focus:outline-none'
/>
<div className='flex h-8 w-8 items-center rounded-r-lg bg-gray-300'>
<div className='bg-base-300 flex h-8 w-8 items-center rounded-r-lg'>
<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'
@@ -9,100 +9,68 @@ interface SearchOptionsProps {
setIsDropdownOpen?: (isOpen: boolean) => void;
}
interface OptionProps {
label: string;
isActive: boolean;
onClick: () => void;
}
const Option: React.FC<OptionProps> = ({ label, isActive, onClick }) => (
<button
className='hover:bg-base-300 flex w-full items-center justify-between rounded-md p-2'
onClick={onClick}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{isActive && <MdCheck size={20} className='text-base-content' />}
</span>
<span className='ml-2'>{label}</span>
</div>
</button>
);
const SearchOptions: React.FC<SearchOptionsProps> = ({
searchConfig,
onSearchConfigChanged,
setIsDropdownOpen,
}) => {
const _ = useTranslation();
const handleSetScope = () => {
onSearchConfigChanged({
...searchConfig,
scope: searchConfig.scope === 'book' ? 'section' : 'book',
});
setIsDropdownOpen?.(false);
};
const handleSetMatchCase = () => {
onSearchConfigChanged({ ...searchConfig, matchCase: !searchConfig.matchCase });
setIsDropdownOpen?.(false);
};
const handleSetMatchWholeWords = () => {
onSearchConfigChanged({ ...searchConfig, matchWholeWords: !searchConfig.matchWholeWords });
setIsDropdownOpen?.(false);
};
const handleSetMatchDiacritics = () => {
onSearchConfigChanged({ ...searchConfig, matchDiacritics: !searchConfig.matchDiacritics });
const updateConfig = (key: keyof BookSearchConfig, value: boolean | string) => {
onSearchConfigChanged({ ...searchConfig, [key]: value });
setIsDropdownOpen?.(false);
};
return (
<div
tabIndex={0}
className='book-menu dropdown-content dropdown-center z-20 mt-3 w-56 border bg-white shadow-2xl'
className='book-menu dropdown-content dropdown-center border-base-200 z-20 mt-3 w-56 border shadow-2xl'
>
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
onClick={handleSetScope}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{searchConfig.scope === 'book' && <MdCheck size={20} className='text-base-content' />}
</span>
<span className='ml-2'>{_('Book')}</span>
</div>
</button>
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
onClick={handleSetScope}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{searchConfig.scope === 'section' && (
<MdCheck size={20} className='text-base-content' />
)}
</span>
<span className='ml-2'>{_('Chapter')}</span>
</div>
</button>
<hr className='my-1' />
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
onClick={handleSetMatchCase}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{searchConfig.matchCase && <MdCheck size={20} className='text-base-content' />}
</span>
<span className='ml-2'>{_('Match Case')}</span>
</div>
</button>
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
onClick={handleSetMatchWholeWords}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{searchConfig.matchWholeWords && <MdCheck size={20} className='text-base-content' />}
</span>
<span className='ml-2'>{_('Match Whole Words')}</span>
</div>
</button>
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
onClick={handleSetMatchDiacritics}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{searchConfig.matchDiacritics && <MdCheck size={20} className='text-base-content' />}
</span>
<span className='ml-2'>{_('Match Diacritics')}</span>
</div>
</button>
<Option
label={_('Book')}
isActive={searchConfig.scope === 'book'}
onClick={() => updateConfig('scope', 'book')}
/>
<Option
label={_('Chapter')}
isActive={searchConfig.scope === 'section'}
onClick={() => updateConfig('scope', 'section')}
/>
<hr className='border-base-200 my-1' />
<Option
label={_('Match Case')}
isActive={searchConfig.matchCase}
onClick={() => updateConfig('matchCase', !searchConfig.matchCase)}
/>
<Option
label={_('Match Whole Words')}
isActive={searchConfig.matchWholeWords}
onClick={() => updateConfig('matchWholeWords', !searchConfig.matchWholeWords)}
/>
<Option
label={_('Match Diacritics')}
isActive={searchConfig.matchDiacritics}
onClick={() => updateConfig('matchDiacritics', !searchConfig.matchDiacritics)}
/>
</div>
);
};
@@ -38,7 +38,7 @@ const SideBar: React.FC<{
handleSideBarTogglePin,
} = useSidebar(
settings.globalReadSettings.sideBarWidth,
settings.globalReadSettings.isSideBarPinned,
window.innerWidth >= 640 ? settings.globalReadSettings.isSideBarPinned : false,
);
const onSearchEvent = async (event: CustomEvent) => {
@@ -48,10 +48,20 @@ const SideBar: React.FC<{
setSearchTerm(term);
};
const onNavigateEvent = async () => {
const pinButton = document.querySelector('.sidebar-pin-btn');
const isPinButtonHidden = !pinButton || window.getComputedStyle(pinButton).display === 'none';
if (isPinButtonHidden) {
setSideBarVisible(false);
}
};
useEffect(() => {
eventDispatcher.on('search', onSearchEvent);
eventDispatcher.on('navigate', onNavigateEvent);
return () => {
eventDispatcher.off('search', onSearchEvent);
eventDispatcher.off('navigate', onNavigateEvent);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -79,6 +89,7 @@ const SideBar: React.FC<{
useShortcuts({ onToggleSearchBar: handleToggleSearchBar }, [sideBarBookKey]);
const handleSearchResultClick = (cfi: string) => {
onNavigateEvent();
getView(sideBarBookKey)?.goTo(cfi);
};
@@ -104,11 +115,20 @@ const SideBar: React.FC<{
position: isSideBarPinned ? 'relative' : 'absolute',
}}
>
<style jsx>{`
@media (max-width: 640px) {
.sidebar-container {
width: 100%;
min-width: 100%;
}
}
`}</style>
<div className='flex-shrink-0'>
<SidebarHeader
isPinned={isSideBarPinned}
isSearchBarVisible={isSearchBarVisible}
onGoToLibrary={onGoToLibrary}
onClose={() => setSideBarVisible(false)}
onTogglePin={handleSideBarTogglePin}
onToggleSearchBar={handleToggleSearchBar}
/>
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import { TOCItem } from '@/libs/document';
@@ -5,7 +6,7 @@ import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { findParentPath } from '@/utils/toc';
import { getContentMd5 } from '@/utils/misc';
import clsx from 'clsx';
import { eventDispatcher } from '@/utils/event';
const createExpanderIcon = (isExpanded: boolean) => {
return (
@@ -43,6 +44,7 @@ const TOCItemView: React.FC<{
const handleClickItem = (event: React.MouseEvent) => {
event.preventDefault();
eventDispatcher.dispatch('navigate', { bookKey, href: item.href });
if (item.href) {
getView(bookKey)?.goTo(item.href);
}
+2 -2
View File
@@ -20,14 +20,14 @@ const Button: React.FC<ButtonProps> = ({
}) => {
return (
<div
className={clsx('tooltip z-50', tooltip && `tooltip-${tooltipDirection}`, {
className={clsx('tooltip z-50 h-8 min-h-8 w-8', tooltip && `tooltip-${tooltipDirection}`, {
'tooltip-hidden': !tooltip,
})}
data-tip={tooltip}
>
<button
className={clsx(
'btn btn-ghost mx-2 h-8 min-h-8 w-8 p-0',
'btn btn-ghost h-8 min-h-8 w-8 p-0',
disabled && 'btn-disabled !bg-transparent',
className,
)}
+5 -3
View File
@@ -3,14 +3,16 @@ import React, { useState, isValidElement, ReactElement } from 'react';
interface DropdownProps {
className?: string;
menuClassName?: string;
buttonClassName?: string;
toggleButton: React.ReactNode;
children: ReactElement<{ setIsDropdownOpen: (isOpen: boolean) => void }>;
children: ReactElement<{ setIsDropdownOpen: (isOpen: boolean) => void; menuClassName?: string }>;
onToggle?: (isOpen: boolean) => void;
}
const Dropdown: React.FC<DropdownProps> = ({
className,
menuClassName,
buttonClassName,
toggleButton,
children,
@@ -30,7 +32,7 @@ const Dropdown: React.FC<DropdownProps> = ({
};
const childrenWithToggle = isValidElement(children)
? React.cloneElement(children, { setIsDropdownOpen })
? React.cloneElement(children, { setIsDropdownOpen, menuClassName })
: children;
return (
@@ -42,7 +44,7 @@ const Dropdown: React.FC<DropdownProps> = ({
<div
tabIndex={-1}
onClick={toggleDropdown}
className={clsx('dropdown-toggle', buttonClassName, isOpen && 'bg-base-200')}
className={clsx('dropdown-toggle', buttonClassName, isOpen && 'bg-base-300/50')}
>
{toggleButton}
</div>
+2 -2
View File
@@ -25,7 +25,7 @@ const MenuItem: React.FC<MenuItemProps> = ({
const menuButton = (
<button
className={clsx(
'hover:bg-neutral text-base-content flex h-10 w-full items-center justify-between rounded-md p-2',
'hover:bg-base-300 text-base-content flex h-10 w-full items-center justify-between rounded-md p-2',
disabled && 'btn-disabled text-gray-400',
)}
onClick={onClick}
@@ -44,7 +44,7 @@ const MenuItem: React.FC<MenuItemProps> = ({
<ul className='menu rounded-box m-0 p-0'>
<li>
<details>
<summary className='hover:bg-neutral p-0 pr-3'>{menuButton}</summary>
<summary className='hover:bg-base-300 p-0 pr-3'>{menuButton}</summary>
{children}
</details>
</li>