You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
126 lines
3.7 KiB
126 lines
3.7 KiB
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 } from 'react' |
|
|
|
const PrimaryPageLayout = forwardRef( |
|
( |
|
{ |
|
children, |
|
titlebar, |
|
pageName, |
|
displayScrollToTopButton = false, |
|
hideTitlebarBottomBorder = false |
|
}: { |
|
children?: React.ReactNode |
|
titlebar: React.ReactNode |
|
pageName: TPrimaryPageName |
|
displayScrollToTopButton?: boolean |
|
hideTitlebarBottomBorder?: boolean |
|
}, |
|
ref |
|
) => { |
|
const scrollAreaRef = useRef<HTMLDivElement>(null) |
|
const smallScreenScrollAreaRef = useRef<HTMLDivElement>(null) |
|
const smallScreenLastScrollTopRef = useRef(0) |
|
const { isSmallScreen } = useScreenSize() |
|
const { current, display } = usePrimaryPage() |
|
|
|
useImperativeHandle( |
|
ref, |
|
() => ({ |
|
scrollToTop: (behavior: ScrollBehavior = 'smooth') => { |
|
setTimeout(() => { |
|
if (scrollAreaRef.current) { |
|
return scrollAreaRef.current.scrollTo({ top: 0, behavior }) |
|
} |
|
window.scrollTo({ top: 0, behavior }) |
|
}, 10) |
|
} |
|
}), |
|
[] |
|
) |
|
|
|
useEffect(() => { |
|
if (!isSmallScreen) return |
|
|
|
const isVisible = () => { |
|
return smallScreenScrollAreaRef.current?.checkVisibility |
|
? smallScreenScrollAreaRef.current?.checkVisibility() |
|
: false |
|
} |
|
|
|
if (isVisible()) { |
|
window.scrollTo({ top: smallScreenLastScrollTopRef.current, behavior: 'instant' }) |
|
} |
|
const handleScroll = () => { |
|
if (isVisible()) { |
|
smallScreenLastScrollTopRef.current = window.scrollY |
|
} |
|
} |
|
window.addEventListener('scroll', handleScroll) |
|
return () => { |
|
window.removeEventListener('scroll', handleScroll) |
|
} |
|
}, [current, isSmallScreen, display]) |
|
|
|
if (isSmallScreen) { |
|
return ( |
|
<DeepBrowsingProvider active={current === pageName && display}> |
|
<div |
|
ref={smallScreenScrollAreaRef} |
|
style={{ |
|
paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)' |
|
}} |
|
> |
|
<PrimaryPageTitlebar hideBottomBorder={hideTitlebarBottomBorder}> |
|
{titlebar} |
|
</PrimaryPageTitlebar> |
|
{children} |
|
</div> |
|
{displayScrollToTopButton && <ScrollToTopButton />} |
|
</DeepBrowsingProvider> |
|
) |
|
} |
|
|
|
return ( |
|
<DeepBrowsingProvider active={current === pageName && display} scrollAreaRef={scrollAreaRef}> |
|
<ScrollArea |
|
className="h-full overflow-auto" |
|
scrollBarClassName="z-50 pt-12" |
|
ref={scrollAreaRef} |
|
> |
|
<PrimaryPageTitlebar hideBottomBorder={hideTitlebarBottomBorder}> |
|
{titlebar} |
|
</PrimaryPageTitlebar> |
|
{children} |
|
<div className="h-4" /> |
|
</ScrollArea> |
|
{displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />} |
|
</DeepBrowsingProvider> |
|
) |
|
} |
|
) |
|
PrimaryPageLayout.displayName = 'PrimaryPageLayout' |
|
export default PrimaryPageLayout |
|
|
|
export type TPrimaryPageLayoutRef = { |
|
scrollToTop: () => void |
|
} |
|
|
|
function PrimaryPageTitlebar({ |
|
children, |
|
hideBottomBorder = false |
|
}: { |
|
children?: React.ReactNode |
|
hideBottomBorder?: boolean |
|
}) { |
|
return ( |
|
<Titlebar className="p-1" hideBottomBorder={hideBottomBorder}> |
|
{children} |
|
</Titlebar> |
|
) |
|
}
|
|
|