From 93b96d64eb6b950a726012887f657043bfee8a9d Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 9 Mar 2026 01:04:37 +0800 Subject: [PATCH] fix(sidebar): use position fixed and transform for mobile sidebar (#3490) * chore: bump nodejs version to 24 * fix(sidebar): use position fixed and transform for mobile sidebar Use position: fixed to prevent horizontal scrolling on the mobile bottom sheet, and replace style.top with transform: translateY() for smooth drag performance. Cache element refs to avoid document.querySelector on every drag frame. Apply the same position: fixed fix to the notebook panel. Closes #3492 Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pull-request.yml | 2 +- .github/workflows/release.yml | 2 +- README.md | 4 +-- .../reader/components/notebook/Notebook.tsx | 15 +++----- .../app/reader/components/sidebar/SideBar.tsx | 36 ++++++++++--------- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 97741936..cfceae15 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -49,7 +49,7 @@ jobs: - name: setup node uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: cache Next.js build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c49f2fd5..09be1ff6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -159,7 +159,7 @@ jobs: - name: setup node uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: setup Java (for Android build only) diff --git a/README.md b/README.md index 58a36129..34ea2a6e 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,8 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion For the best experience to build Readest for yourself, use a recent version of Node.js and Rust. Refer to the [Tauri documentation](https://v2.tauri.app/start/prerequisites/) for details on setting up the development environment prerequisites on different platforms. ```bash -nvm install v22 -nvm use v22 +nvm install v24 +nvm use v24 npm install -g pnpm rustup update ``` diff --git a/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx b/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx index 87c79cf1..638e2e64 100644 --- a/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx +++ b/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx @@ -242,6 +242,7 @@ const Notebook: React.FC = ({}) => { const hasSearchResults = filteredAnnotationNotes.length > 0 || filteredExcerptNotes.length > 0; const hasAnyNotes = annotationNotes.length > 0 || excerptNotes.length > 0; + const isMobile = window.innerWidth < 640; return isNotebookVisible ? ( <> @@ -264,22 +265,14 @@ const Notebook: React.FC = ({}) => { aria-label={_('Notebook')} dir={viewSettings?.rtl && languageDir === 'rtl' ? 'rtl' : 'ltr'} style={{ - width: `${notebookWidth}`, - maxWidth: `${MAX_NOTEBOOK_WIDTH * 100}%`, - position: isNotebookPinned ? 'relative' : 'absolute', + width: isMobile ? '100%' : `${notebookWidth}`, + maxWidth: isMobile ? '100%' : `${MAX_NOTEBOOK_WIDTH * 100}%`, + position: isMobile ? 'fixed' : isNotebookPinned ? 'relative' : 'absolute', paddingTop: systemUIVisible ? `${Math.max(safeAreaInsets?.top || 0, statusBarHeight)}px` : `${safeAreaInsets?.top || 0}px`, }} > -
{ } }; + const sidebarRef = useRef(null); + const overlayRef = useRef(null); + useEffect(() => { if (isSideBarVisible) { updateAppTheme('base-200'); + overlayRef.current = document.querySelector('.overlay') as HTMLDivElement | null; } else { updateAppTheme('base-100'); + overlayRef.current = null; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSideBarVisible]); @@ -102,18 +107,19 @@ const SideBar = ({}) => { const newTop = Math.max(0.0, Math.min(1, heightFraction)); sidebarHeight.current = newTop; - const sidebar = document.querySelector('.sidebar-container') as HTMLElement; - const overlay = document.querySelector('.overlay') as HTMLElement; + const sidebar = sidebarRef.current; + const overlay = overlayRef.current; if (sidebar && overlay) { - sidebar.style.top = `${newTop * 100}%`; + sidebar.style.transition = 'none'; + sidebar.style.transform = `translateY(${newTop * 100}%)`; overlay.style.opacity = `${1 - heightFraction}`; } }; const handleVerticalDragEnd = (data: { velocity: number; clientY: number }) => { - const sidebar = document.querySelector('.sidebar-container') as HTMLElement; - const overlay = document.querySelector('.overlay') as HTMLElement; + const sidebar = sidebarRef.current; + const overlay = overlayRef.current; if (!sidebar || !overlay) return; @@ -122,8 +128,8 @@ const SideBar = ({}) => { (data.velocity >= 0 && data.clientY >= window.innerHeight * 0.5) ) { const transitionDuration = 0.15 / Math.max(data.velocity, 0.5); - sidebar.style.transition = `top ${transitionDuration}s ease-out`; - sidebar.style.top = '100%'; + sidebar.style.transition = `transform ${transitionDuration}s ease-out`; + sidebar.style.transform = 'translateY(100%)'; overlay.style.transition = `opacity ${transitionDuration}s ease-out`; overlay.style.opacity = '0'; setTimeout(() => setSideBarVisible(false), 300); @@ -131,8 +137,8 @@ const SideBar = ({}) => { impactFeedback('medium'); } } else { - sidebar.style.transition = 'top 0.3s ease-out'; - sidebar.style.top = '0%'; + sidebar.style.transition = 'transform 0.3s ease-out'; + sidebar.style.transform = 'translateY(0%)'; overlay.style.transition = 'opacity 0.3s ease-out'; overlay.style.opacity = '0.8'; if (appService?.hasHaptics) { @@ -236,6 +242,7 @@ const SideBar = ({}) => { /> )}
{ aria-label={_('Sidebar')} dir={viewSettings?.rtl && languageDir === 'rtl' ? 'rtl' : 'ltr'} style={{ - width: `${sideBarWidth}`, - maxWidth: `${MAX_SIDEBAR_WIDTH * 100}%`, - position: isSideBarPinned ? 'relative' : 'absolute', + width: isMobile ? '100%' : `${sideBarWidth}`, + maxWidth: isMobile ? '100%' : `${MAX_SIDEBAR_WIDTH * 100}%`, + position: isMobile ? 'fixed' : isSideBarPinned ? 'relative' : 'absolute', paddingTop: systemUIVisible ? `${Math.max(safeAreaInsets?.top || 0, statusBarHeight)}px` : `${safeAreaInsets?.top || 0}px`, @@ -259,14 +266,9 @@ const SideBar = ({}) => {