mirror of
https://github.com/readest/readest
synced 2026-04-21 13:37:44 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
9f8894c1e0
commit
93b96d64eb
5 changed files with 27 additions and 32 deletions
2
.github/workflows/pull-request.yml
vendored
2
.github/workflows/pull-request.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
}}
|
||||
>
|
||||
<style jsx>{`
|
||||
@media (max-width: 640px) {
|
||||
.notebook-container {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
<div
|
||||
className={clsx(
|
||||
'drag-bar absolute -left-2 top-0 h-full w-0.5 cursor-col-resize bg-transparent p-2',
|
||||
|
|
|
|||
|
|
@ -72,11 +72,16 @@ const SideBar = ({}) => {
|
|||
}
|
||||
};
|
||||
|
||||
const sidebarRef = useRef<HTMLDivElement | null>(null);
|
||||
const overlayRef = useRef<HTMLDivElement | null>(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 = ({}) => {
|
|||
/>
|
||||
)}
|
||||
<div
|
||||
ref={sidebarRef}
|
||||
className={clsx(
|
||||
'sidebar-container flex min-w-60 select-none flex-col',
|
||||
'full-height transition-[padding-top] duration-300',
|
||||
|
|
@ -248,9 +255,9 @@ 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 = ({}) => {
|
|||
<style jsx>{`
|
||||
@media (max-width: 640px) {
|
||||
.sidebar-container {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
}
|
||||
.sidebar-container.open {
|
||||
top: 0%;
|
||||
}
|
||||
.overlay {
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue