feat: sticky list mode switcher
This commit is contained in:
@@ -3,8 +3,9 @@ import ScrollToTopButton from '@/components/ScrollToTopButton'
|
||||
import { Titlebar } from '@/components/Titlebar'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { TPrimaryPageName, usePrimaryPage } from '@/PageManager'
|
||||
import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
||||
|
||||
const PrimaryPageLayout = forwardRef(
|
||||
(
|
||||
@@ -22,8 +23,6 @@ const PrimaryPageLayout = forwardRef(
|
||||
ref
|
||||
) => {
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||
const [visible, setVisible] = useState(true)
|
||||
const [lastScrollTop, setLastScrollTop] = useState(0)
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const { current } = usePrimaryPage()
|
||||
|
||||
@@ -44,77 +43,39 @@ const PrimaryPageLayout = forwardRef(
|
||||
useEffect(() => {
|
||||
if (isSmallScreen) {
|
||||
window.scrollTo({ top: 0 })
|
||||
setVisible(true)
|
||||
return
|
||||
}
|
||||
}, [current])
|
||||
|
||||
useEffect(() => {
|
||||
if (current !== pageName) return
|
||||
|
||||
const handleScroll = () => {
|
||||
const atBottom = isSmallScreen
|
||||
? window.innerHeight + window.scrollY >= document.body.offsetHeight - 20
|
||||
: scrollAreaRef.current
|
||||
? scrollAreaRef.current?.clientHeight + scrollAreaRef.current?.scrollTop >=
|
||||
scrollAreaRef.current?.scrollHeight - 20
|
||||
: false
|
||||
if (atBottom) {
|
||||
setVisible(true)
|
||||
return
|
||||
}
|
||||
|
||||
const scrollTop = (isSmallScreen ? window.scrollY : scrollAreaRef.current?.scrollTop) || 0
|
||||
const diff = scrollTop - lastScrollTop
|
||||
if (scrollTop <= 800) {
|
||||
setVisible(true)
|
||||
setLastScrollTop(scrollTop)
|
||||
return
|
||||
}
|
||||
|
||||
if (diff > 20) {
|
||||
setVisible(false)
|
||||
setLastScrollTop(scrollTop)
|
||||
} else if (diff < -20) {
|
||||
setVisible(true)
|
||||
setLastScrollTop(scrollTop)
|
||||
}
|
||||
}
|
||||
|
||||
if (isSmallScreen) {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}
|
||||
|
||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [lastScrollTop, isSmallScreen, current])
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<DeepBrowsingProvider active={current === pageName}>
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)'
|
||||
}}
|
||||
>
|
||||
{titlebar && <PrimaryPageTitlebar>{titlebar}</PrimaryPageTitlebar>}
|
||||
{children}
|
||||
{displayScrollToTopButton && <ScrollToTopButton />}
|
||||
<BottomNavigationBar />
|
||||
</div>
|
||||
</DeepBrowsingProvider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className="sm:h-screen sm:overflow-auto pt-12 sm:pt-0"
|
||||
scrollBarClassName="sm:z-50"
|
||||
ref={scrollAreaRef}
|
||||
style={{
|
||||
paddingBottom: isSmallScreen ? 'calc(env(safe-area-inset-bottom) + 3rem)' : ''
|
||||
}}
|
||||
>
|
||||
{titlebar && (
|
||||
<PrimaryPageTitlebar visible={!isSmallScreen || visible}>{titlebar}</PrimaryPageTitlebar>
|
||||
)}
|
||||
<div className="overflow-x-hidden">{children}</div>
|
||||
{displayScrollToTopButton && (
|
||||
<ScrollToTopButton
|
||||
scrollAreaRef={scrollAreaRef}
|
||||
visible={visible && lastScrollTop > 800}
|
||||
/>
|
||||
)}
|
||||
{isSmallScreen && <BottomNavigationBar visible={visible} />}
|
||||
</ScrollArea>
|
||||
<DeepBrowsingProvider active={current === pageName} scrollAreaRef={scrollAreaRef}>
|
||||
<ScrollArea
|
||||
className="h-screen overflow-auto"
|
||||
scrollBarClassName="z-50"
|
||||
ref={scrollAreaRef}
|
||||
>
|
||||
{titlebar && <PrimaryPageTitlebar>{titlebar}</PrimaryPageTitlebar>}
|
||||
{children}
|
||||
</ScrollArea>
|
||||
{displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
|
||||
</DeepBrowsingProvider>
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -125,16 +86,6 @@ export type TPrimaryPageLayoutRef = {
|
||||
scrollToTop: () => void
|
||||
}
|
||||
|
||||
function PrimaryPageTitlebar({
|
||||
children,
|
||||
visible = true
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
visible?: boolean
|
||||
}) {
|
||||
return (
|
||||
<Titlebar className="h-12 p-1" visible={visible}>
|
||||
{children}
|
||||
</Titlebar>
|
||||
)
|
||||
function PrimaryPageTitlebar({ children }: { children?: React.ReactNode }) {
|
||||
return <Titlebar className="h-12 p-1">{children}</Titlebar>
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ import ScrollToTopButton from '@/components/ScrollToTopButton'
|
||||
import { Titlebar } from '@/components/Titlebar'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export default function SecondaryPageLayout({
|
||||
children,
|
||||
@@ -23,118 +24,64 @@ export default function SecondaryPageLayout({
|
||||
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 atBottom = isSmallScreen
|
||||
? window.innerHeight + window.scrollY >= document.body.offsetHeight - 20
|
||||
: scrollAreaRef.current
|
||||
? scrollAreaRef.current?.clientHeight + scrollAreaRef.current?.scrollTop >=
|
||||
scrollAreaRef.current?.scrollHeight - 20
|
||||
: false
|
||||
if (atBottom) {
|
||||
setVisible(true)
|
||||
return
|
||||
}
|
||||
|
||||
const scrollTop = (isSmallScreen ? window.scrollY : scrollAreaRef.current?.scrollTop) || 0
|
||||
const diff = scrollTop - lastScrollTop
|
||||
if (scrollTop <= 800) {
|
||||
setVisible(true)
|
||||
setLastScrollTop(scrollTop)
|
||||
return
|
||||
}
|
||||
|
||||
if (diff > 20) {
|
||||
setVisible(false)
|
||||
setLastScrollTop(scrollTop)
|
||||
} else if (diff < -20) {
|
||||
setVisible(true)
|
||||
setLastScrollTop(scrollTop)
|
||||
}
|
||||
}
|
||||
|
||||
if (isSmallScreen) {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}
|
||||
|
||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [lastScrollTop, isSmallScreen, currentIndex])
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<DeepBrowsingProvider active={currentIndex === index}>
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)'
|
||||
}}
|
||||
>
|
||||
<SecondaryPageTitlebar
|
||||
title={title}
|
||||
controls={controls}
|
||||
hideBackButton={hideBackButton}
|
||||
/>
|
||||
<div className="pb-4 mt-2">{children}</div>
|
||||
{displayScrollToTopButton && <ScrollToTopButton />}
|
||||
<BottomNavigationBar />
|
||||
</div>
|
||||
</DeepBrowsingProvider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className="sm:h-screen sm:overflow-auto pt-12 sm:pt-0"
|
||||
scrollBarClassName="sm:z-50"
|
||||
ref={scrollAreaRef}
|
||||
style={{
|
||||
paddingBottom: isSmallScreen ? 'calc(env(safe-area-inset-bottom) + 3rem)' : ''
|
||||
}}
|
||||
>
|
||||
<SecondaryPageTitlebar
|
||||
title={title}
|
||||
controls={controls}
|
||||
hideBackButton={hideBackButton}
|
||||
visible={visible}
|
||||
/>
|
||||
<div className="pb-4 mt-2">{children}</div>
|
||||
{displayScrollToTopButton && (
|
||||
<ScrollToTopButton scrollAreaRef={scrollAreaRef} visible={visible && lastScrollTop > 800} />
|
||||
)}
|
||||
{isSmallScreen && <BottomNavigationBar visible={visible} />}
|
||||
</ScrollArea>
|
||||
<DeepBrowsingProvider active={currentIndex === index} scrollAreaRef={scrollAreaRef}>
|
||||
<ScrollArea
|
||||
className="h-screen overflow-auto"
|
||||
scrollBarClassName="sm:z-50"
|
||||
ref={scrollAreaRef}
|
||||
>
|
||||
<SecondaryPageTitlebar title={title} controls={controls} hideBackButton={hideBackButton} />
|
||||
<div className="pb-4 mt-2">{children}</div>
|
||||
</ScrollArea>
|
||||
{displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
|
||||
</DeepBrowsingProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function SecondaryPageTitlebar({
|
||||
title,
|
||||
controls,
|
||||
hideBackButton = false,
|
||||
visible = true
|
||||
hideBackButton = false
|
||||
}: {
|
||||
title?: React.ReactNode
|
||||
controls?: React.ReactNode
|
||||
hideBackButton?: boolean
|
||||
visible?: boolean
|
||||
}): JSX.Element {
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<Titlebar
|
||||
className="h-12 flex gap-1 p-1 items-center justify-between font-semibold"
|
||||
visible={visible}
|
||||
>
|
||||
<BackButton hide={hideBackButton}>{title}</BackButton>
|
||||
<div className="flex-shrink-0">{controls}</div>
|
||||
</Titlebar>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<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}>{title}</BackButton>
|
||||
</div>
|
||||
<Titlebar className="h-12 flex gap-1 p-1 items-center justify-between font-semibold">
|
||||
<BackButton hide={hideBackButton}>{title}</BackButton>
|
||||
<div className="flex-shrink-0">{controls}</div>
|
||||
</Titlebar>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user