feat: improve mobile experience

This commit is contained in:
codytseng 2025-01-02 21:57:14 +08:00
parent 8ec0d46d58
commit 3946e603b3
98 changed files with 2508 additions and 1058 deletions

View file

@ -1,29 +1,45 @@
import BackButton from '@/components/BackButton'
import BottomNavigationBar from '@/components/BottomNavigationBar'
import ScrollToTopButton from '@/components/ScrollToTopButton'
import ThemeToggle from '@/components/ThemeToggle'
import { Titlebar } from '@/components/Titlebar'
import { ScrollArea } from '@/components/ui/scroll-area'
import { useSecondaryPage } from '@/PageManager'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useEffect, useRef, useState } from 'react'
export default function SecondaryPageLayout({
children,
index,
titlebarContent,
hideBackButton = false,
hideScrollToTopButton = false
displayScrollToTopButton = false
}: {
children?: React.ReactNode
index?: number
titlebarContent?: React.ReactNode
hideBackButton?: boolean
hideScrollToTopButton?: boolean
displayScrollToTopButton?: boolean
}): JSX.Element {
const scrollAreaRef = useRef<HTMLDivElement>(null)
const [visible, setVisible] = useState(true)
const [lastScrollTop, setLastScrollTop] = useState(0)
const { isSmallScreen } = useScreenSize()
const { currentIndex } = useSecondaryPage()
useEffect(() => {
if (isSmallScreen) {
window.scrollTo({ top: 0 })
setVisible(true)
return
}
}, [])
useEffect(() => {
if (currentIndex !== index) return
const handleScroll = () => {
const scrollTop = scrollAreaRef.current?.scrollTop || 0
const scrollTop = (isSmallScreen ? window.scrollY : scrollAreaRef.current?.scrollTop) || 0
const diff = scrollTop - lastScrollTop
if (scrollTop <= 100) {
setVisible(true)
@ -40,26 +56,38 @@ export default function SecondaryPageLayout({
}
}
const scrollArea = scrollAreaRef.current
scrollArea?.addEventListener('scroll', handleScroll)
return () => {
scrollArea?.removeEventListener('scroll', handleScroll)
if (isSmallScreen) {
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
}
}, [lastScrollTop])
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
return () => {
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
}
}, [lastScrollTop, isSmallScreen, currentIndex])
return (
<ScrollArea ref={scrollAreaRef} className="h-full" scrollBarClassName="sm:pt-9 pt-11">
<ScrollArea
className="sm:h-screen sm:overflow-auto"
scrollBarClassName="sm:z-50"
ref={scrollAreaRef}
style={{
paddingBottom: 'env(safe-area-inset-bottom)'
}}
>
<SecondaryPageTitlebar
content={titlebarContent}
hideBackButton={hideBackButton}
visible={visible}
/>
<div className="sm:px-4 pb-4 pt-11 w-full h-full">{children}</div>
<ScrollToTopButton
scrollAreaRef={scrollAreaRef}
visible={!hideScrollToTopButton && visible && lastScrollTop > 500}
/>
<div className="pb-4 mt-2">{children}</div>
{displayScrollToTopButton && (
<ScrollToTopButton scrollAreaRef={scrollAreaRef} visible={visible && lastScrollTop > 500} />
)}
{isSmallScreen && <BottomNavigationBar visible={visible} />}
</ScrollArea>
)
}
@ -77,18 +105,16 @@ export function SecondaryPageTitlebar({
if (isSmallScreen) {
return (
<Titlebar className="pl-2" visible={visible}>
<BackButton hide={hideBackButton} variant="small-screen-titlebar" />
<div className="truncate text-lg">{content}</div>
<Titlebar className="h-12 flex gap-1 p-1 items-center font-semibold" visible={visible}>
<BackButton hide={hideBackButton}>{content}</BackButton>
</Titlebar>
)
}
return (
<Titlebar className="justify-between">
<Titlebar className="h-12 flex gap-1 p-1 justify-between items-center font-semibold">
<div className="flex items-center gap-1 flex-1 w-0">
<BackButton hide={hideBackButton} />
<div className="truncate text-lg">{content}</div>
<BackButton hide={hideBackButton}>{content}</BackButton>
</div>
<div className="flex-shrink-0 flex items-center">
<ThemeToggle />