fix(layout): fix parallel read layout on smaller screens (#3549)

This commit is contained in:
Huang Xin
2026-03-17 14:34:55 +08:00
committed by GitHub
parent b00d321817
commit 3dfe6bd65e
7 changed files with 64 additions and 41 deletions
@@ -121,12 +121,13 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook, onGoToLibr
{isBookmarked && !hoveredBookKey && <Ribbon width={`${horizontalGapPercent}%`} />}
<HeaderBar
bookKey={bookKey}
gridInsets={gridInsets}
screenInsets={screenInsets}
bookTitle={book.title}
isTopLeft={index === 0}
isHoveredAnim={bookKeys.length > 2}
onCloseBook={onCloseBook}
onGoToLibrary={onGoToLibrary}
gridInsets={gridInsets}
onDropdownOpenChange={(isOpen) => setDropdownOpenBook(isOpen ? bookKey : '')}
/>
<PageNavigationButtons
@@ -34,6 +34,7 @@ interface HeaderBarProps {
isTopLeft: boolean;
isHoveredAnim: boolean;
gridInsets: Insets;
screenInsets: Insets;
onCloseBook: (bookKey: string) => void;
onGoToLibrary: () => void;
onDropdownOpenChange?: (isOpen: boolean) => void;
@@ -45,6 +46,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
isTopLeft,
isHoveredAnim,
gridInsets,
screenInsets,
onCloseBook,
onGoToLibrary,
onDropdownOpenChange,
@@ -61,6 +63,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
const viewSettings = getViewSettings(bookKey);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [headerWidth, setHeaderWidth] = useState(0);
const view = getView(bookKey);
const iconSize16 = useResponsiveSize(16);
const iconSize18 = useResponsiveSize(18);
@@ -107,6 +110,16 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appService, hoveredBookKey]);
useEffect(() => {
const header = headerRef.current;
if (!header) return;
const observer = new ResizeObserver(([entry]) => {
if (entry) setHeaderWidth(entry.contentRect.width);
});
observer.observe(header);
return () => observer.disconnect();
}, []);
// Check if mouse is outside header area to avoid false positive event of MouseLeave when clicking inside header on Windows
const isMouseOutsideHeader = useCallback((clientX: number, clientY: number) => {
if (!headerRef.current) return true;
@@ -117,15 +130,20 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
);
}, []);
const isHeaderCompact = headerWidth > 0 && headerWidth < 350;
const insets = window.innerWidth < 640 ? screenInsets : gridInsets;
const isHeaderVisible = hoveredBookKey === bookKey || isDropdownOpen;
const trafficLightInHeader =
appService?.hasTrafficLight && !trafficLightInFullscreen && !isSideBarVisible && isTopLeft;
return (
<div
className={clsx('bg-base-100 absolute top-0 w-full')}
className={clsx(
'bg-base-100 left-0 top-0 w-full',
window.innerWidth < 640 ? 'fixed z-20' : 'absolute',
)}
style={{
paddingTop: appService?.hasSafeAreaInset ? `${gridInsets.top}px` : '0px',
paddingTop: appService?.hasSafeAreaInset ? `${insets.top}px` : '0px',
}}
>
<div
@@ -142,7 +160,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
isHeaderVisible ? 'visible' : 'hidden',
)}
style={{
height: systemUIVisible ? `${Math.max(gridInsets.top, statusBarHeight)}px` : '0px',
height: systemUIVisible ? `${Math.max(insets.top, statusBarHeight)}px` : '0px',
}}
/>
<div
@@ -161,8 +179,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
)}
style={{
marginTop: systemUIVisible
? `${Math.max(gridInsets.top, statusBarHeight)}px`
: `${gridInsets.top}px`,
? `${Math.max(insets.top, statusBarHeight)}px`
: `${insets.top}px`,
}}
onFocus={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
onMouseLeave={(e) => {
@@ -171,21 +189,26 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
}
}}
>
<div className='header-tools-start bg-base-100 sidebar-bookmark-toggler z-20 flex h-full items-center gap-x-4 pe-2 max-[350px]:gap-x-2'>
{!isSideBarVisible && (
<div className='hidden sm:flex'>
<SidebarToggler bookKey={bookKey} />
</div>
)}
<button
title={_('Go to Library')}
className='btn btn-ghost hidden h-8 min-h-8 w-8 p-0 sm:flex'
onClick={onGoToLibrary}
<div className='header-tools-start bg-base-100 sidebar-bookmark-toggler z-20 flex h-full min-w-0 items-center gap-x-4 pe-2 max-[350px]:gap-x-2'>
<div
className='flex min-w-0 items-center gap-x-4 overflow-x-auto max-[350px]:gap-x-2'
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
>
<VscLibrary size={iconSize18} className='fill-base-content' />
</button>
<BookmarkToggler bookKey={bookKey} />
<TranslationToggler bookKey={bookKey} />
{!isSideBarVisible && (
<div className='hidden sm:flex'>
<SidebarToggler bookKey={bookKey} />
</div>
)}
<button
title={_('Go to Library')}
className='btn btn-ghost hidden h-8 min-h-8 w-8 p-0 sm:flex'
onClick={onGoToLibrary}
>
<VscLibrary size={iconSize18} className='fill-base-content' />
</button>
<BookmarkToggler bookKey={bookKey} />
<TranslationToggler bookKey={bookKey} />
</div>
{enableAnnotationQuickActions && (
<Dropdown
label={
@@ -229,6 +252,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
className={clsx(
'header-title z-15 bg-base-100 pointer-events-none hidden flex-1 items-center justify-center sm:flex',
!windowButtonVisible && 'absolute inset-0',
isHeaderCompact && '!hidden',
)}
>
<div
@@ -242,8 +266,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
</div>
</div>
<div className='header-tools-end bg-base-100 z-20 ms-auto flex h-full items-center gap-x-4 ps-2 max-[350px]:gap-x-2'>
<SettingsToggler bookKey={bookKey} />
<div className='header-tools-end bg-base-100 z-20 ms-auto flex h-full min-w-max items-center gap-x-4 ps-2 max-[350px]:gap-x-2'>
{!isHeaderCompact && <SettingsToggler bookKey={bookKey} />}
<NotebookToggler bookKey={bookKey} />
<Dropdown
label={_('View Options')}
@@ -254,9 +278,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
>
<ViewMenu bookKey={bookKey} />
</Dropdown>
<WindowButtons
className='window-buttons flex h-full items-center'
className='window-buttons flex items-center'
headerRef={headerRef}
showMinimize={bookKeys.length == 1 && windowButtonVisible}
showMaximize={bookKeys.length == 1 && windowButtonVisible}
@@ -88,7 +88,9 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
className={clsx(
'text-center',
isVertical ? '' : 'line-clamp-1',
!isVertical && hoveredBookKey == bookKey && 'hidden',
!isVertical &&
(hoveredBookKey == bookKey || (hoveredBookKey && appService?.isMobile)) &&
'hidden',
)}
>
{section || ''}
@@ -47,10 +47,10 @@ const StatusInfo: React.FC<StatusInfoProps> = ({
opacity={isEink ? 1.0 : 0.75}
/>
<rect
x='1.5'
y='1.5'
width={(batteryLevel / 100) * 19}
height='9'
x='0.5'
y='0.5'
width={(batteryLevel / 100) * 21}
height='11'
rx='1'
fill='currentColor'
opacity={isEink ? 1.0 : 0.3}
@@ -67,8 +67,12 @@ const DesktopFooterBar: React.FC<FooterBarChildProps> = ({
return (
<div
className='hidden h-8 w-full items-center gap-x-4 px-4 sm:flex'
style={{ bottom: isMobile ? `${gridInsets.bottom * 0.33}px` : '0px' }}
className='hidden h-8 w-full items-center gap-x-4 overflow-x-auto px-4 sm:flex'
style={{
bottom: isMobile ? `${gridInsets.bottom * 0.33}px` : '0px',
scrollbarWidth: 'none',
msOverflowStyle: 'none',
}}
>
{!viewSettings?.showPaginationButtons && (
<Button
@@ -112,7 +116,7 @@ const DesktopFooterBar: React.FC<FooterBarChildProps> = ({
<span
title={_('Reading Progress')}
aria-label={`${_('Reading Progress')}: ${Math.round(progressFraction * 100)}%`}
className='mx-2 text-center text-sm'
className='mx-2 text-nowrap text-center text-sm'
>
<span aria-hidden='true'>{progressInfo}</span>
</span>
@@ -204,7 +204,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
(bookData?.isFixedLayout && viewSettings?.zoomLevel && viewSettings.zoomLevel > 100);
const containerClasses = clsx(
'footer-bar shadow-xs bottom-0 z-10 flex w-full flex-col sm:h-[52px]',
'footer-bar shadow-xs bottom-0 left-0 z-10 flex w-full flex-col sm:h-[52px]',
'sm:bg-base-100 border-base-300/50 border-t sm:border-none',
'transition-[opacity,transform] duration-300',
window.innerWidth < 640 ? 'fixed' : 'absolute',
@@ -124,15 +124,8 @@ const BookMenu: React.FC<BookMenuProps> = ({ menuClassName, setIsDropdownOpen })
<MenuItem
label={_('Parallel Read')}
buttonClass={bookKeys.length > 1 ? 'lg:tooltip lg:tooltip-bottom' : ''}
tooltip={parallelViews.length > 0 ? _('Disable') : bookKeys.length > 1 ? _('Enable') : ''}
tooltip={parallelViews.length > 0 ? _('Disable') : _('Enable')}
Icon={parallelViews.length > 0 && bookKeys.length > 1 ? MdCheck : undefined}
onClick={
parallelViews.length > 0
? handleUnsetParallel
: bookKeys.length > 1
? handleSetParallel
: undefined
}
>
<ul className='max-h-60 overflow-y-auto'>
{getVisibleLibrary()