index.tsx raw
1 import ScrollToTopButton from '@/components/ScrollToTopButton'
2 import { Titlebar } from '@/components/Titlebar'
3 import { ScrollArea } from '@/components/ui/scroll-area'
4 import { usePrimaryPage } from '@/PageManager'
5 import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
6 import { useNostr } from '@/providers/NostrProvider'
7 import { useUserPreferences } from '@/providers/UserPreferencesProvider'
8 import { TPrimaryPageName } from '@/routes/primary'
9 import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
10
11 const PrimaryPageLayout = forwardRef(
12 (
13 {
14 children,
15 titlebar,
16 pageName,
17 displayScrollToTopButton = false,
18 hideTitlebarBottomBorder = false
19 }: {
20 children?: React.ReactNode
21 titlebar: React.ReactNode
22 pageName: TPrimaryPageName
23 displayScrollToTopButton?: boolean
24 hideTitlebarBottomBorder?: boolean
25 },
26 ref
27 ) => {
28 const { pubkey } = useNostr()
29 const scrollAreaRef = useRef<HTMLDivElement>(null)
30 const smallScreenScrollAreaRef = useRef<HTMLDivElement>(null)
31 const smallScreenLastScrollTopRef = useRef(0)
32 const { enableSingleColumnLayout } = useUserPreferences()
33 const { current, display } = usePrimaryPage()
34
35 useImperativeHandle(
36 ref,
37 () => ({
38 scrollToTop: (behavior: ScrollBehavior = 'smooth') => {
39 setTimeout(() => {
40 if (scrollAreaRef.current) {
41 return scrollAreaRef.current.scrollTo({ top: 0, behavior })
42 }
43 window.scrollTo({ top: 0, behavior })
44 }, 10)
45 }
46 }),
47 []
48 )
49
50 useEffect(() => {
51 if (!enableSingleColumnLayout) return
52
53 const isVisible = () => {
54 return smallScreenScrollAreaRef.current?.checkVisibility
55 ? smallScreenScrollAreaRef.current?.checkVisibility()
56 : false
57 }
58
59 if (isVisible()) {
60 window.scrollTo({ top: smallScreenLastScrollTopRef.current, behavior: 'instant' })
61 }
62 const handleScroll = () => {
63 if (isVisible()) {
64 smallScreenLastScrollTopRef.current = window.scrollY
65 }
66 }
67 window.addEventListener('scroll', handleScroll)
68 return () => {
69 window.removeEventListener('scroll', handleScroll)
70 }
71 }, [current, enableSingleColumnLayout, display])
72
73 useEffect(() => {
74 smallScreenLastScrollTopRef.current = 0
75 }, [pubkey])
76
77 if (enableSingleColumnLayout) {
78 return (
79 <DeepBrowsingProvider active={current === pageName && display}>
80 <div
81 ref={smallScreenScrollAreaRef}
82 style={{
83 paddingBottom: 'env(safe-area-inset-bottom)'
84 }}
85 >
86 <PrimaryPageTitlebar hideBottomBorder={hideTitlebarBottomBorder}>
87 {titlebar}
88 </PrimaryPageTitlebar>
89 {children}
90 </div>
91 {displayScrollToTopButton && <ScrollToTopButton />}
92 </DeepBrowsingProvider>
93 )
94 }
95
96 return (
97 <DeepBrowsingProvider active={current === pageName && display} scrollAreaRef={scrollAreaRef}>
98 <ScrollArea
99 className="h-full overflow-auto"
100 scrollBarClassName="z-30 pt-12"
101 ref={scrollAreaRef}
102 >
103 <PrimaryPageTitlebar hideBottomBorder={hideTitlebarBottomBorder}>
104 {titlebar}
105 </PrimaryPageTitlebar>
106 {children}
107 <div className="h-4" />
108 </ScrollArea>
109 {displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
110 </DeepBrowsingProvider>
111 )
112 }
113 )
114 PrimaryPageLayout.displayName = 'PrimaryPageLayout'
115 export default PrimaryPageLayout
116
117 export type TPrimaryPageLayoutRef = {
118 scrollToTop: (behavior?: ScrollBehavior) => void
119 }
120
121 function PrimaryPageTitlebar({
122 children,
123 hideBottomBorder = false
124 }: {
125 children?: React.ReactNode
126 hideBottomBorder?: boolean
127 }) {
128 return (
129 <Titlebar className="p-1" hideBottomBorder={hideBottomBorder}>
130 {children}
131 </Titlebar>
132 )
133 }
134