index.tsx raw
1 import ScrollToTopButton from '@/components/ScrollToTopButton'
2 import { Titlebar } from '@/components/Titlebar'
3 import { Button } from '@/components/ui/button'
4 import { ScrollArea } from '@/components/ui/scroll-area'
5 import { useSecondaryPage } from '@/PageManager'
6 import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
7 import { useUserPreferences } from '@/providers/UserPreferencesProvider'
8 import { ChevronLeft } from 'lucide-react'
9 import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
10 import { useTranslation } from 'react-i18next'
11
12 const SecondaryPageLayout = forwardRef(
13 (
14 {
15 children,
16 index,
17 title,
18 controls,
19 hideBackButton = false,
20 hideTitlebarBottomBorder = false,
21 displayScrollToTopButton = false,
22 titlebar
23 }: {
24 children?: React.ReactNode
25 index?: number
26 title?: React.ReactNode
27 controls?: React.ReactNode
28 hideBackButton?: boolean
29 hideTitlebarBottomBorder?: boolean
30 displayScrollToTopButton?: boolean
31 titlebar?: React.ReactNode
32 },
33 ref
34 ) => {
35 const scrollAreaRef = useRef<HTMLDivElement>(null)
36 const { enableSingleColumnLayout } = useUserPreferences()
37 const { currentIndex } = useSecondaryPage()
38
39 useImperativeHandle(
40 ref,
41 () => ({
42 scrollToTop: (behavior: ScrollBehavior = 'smooth') => {
43 setTimeout(() => {
44 if (scrollAreaRef.current) {
45 return scrollAreaRef.current.scrollTo({ top: 0, behavior })
46 }
47 window.scrollTo({ top: 0, behavior })
48 }, 10)
49 }
50 }),
51 []
52 )
53
54 useEffect(() => {
55 if (enableSingleColumnLayout) {
56 setTimeout(() => window.scrollTo({ top: 0 }), 10)
57 return
58 }
59 }, [])
60
61 if (enableSingleColumnLayout) {
62 return (
63 <DeepBrowsingProvider active={currentIndex === index}>
64 <div
65 style={{
66 paddingBottom: 'env(safe-area-inset-bottom)'
67 }}
68 >
69 <SecondaryPageTitlebar
70 title={title}
71 controls={controls}
72 hideBackButton={hideBackButton}
73 hideBottomBorder={hideTitlebarBottomBorder}
74 titlebar={titlebar}
75 />
76 {children}
77 </div>
78 {displayScrollToTopButton && <ScrollToTopButton />}
79 </DeepBrowsingProvider>
80 )
81 }
82
83 return (
84 <DeepBrowsingProvider active={currentIndex === index} scrollAreaRef={scrollAreaRef}>
85 <ScrollArea
86 className="h-full overflow-auto"
87 scrollBarClassName="z-30 pt-12"
88 ref={scrollAreaRef}
89 >
90 <SecondaryPageTitlebar
91 title={title}
92 controls={controls}
93 hideBackButton={hideBackButton}
94 hideBottomBorder={hideTitlebarBottomBorder}
95 titlebar={titlebar}
96 />
97 {children}
98 <div className="h-4" />
99 </ScrollArea>
100 {displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
101 </DeepBrowsingProvider>
102 )
103 }
104 )
105 SecondaryPageLayout.displayName = 'SecondaryPageLayout'
106 export default SecondaryPageLayout
107
108 export function SecondaryPageTitlebar({
109 title,
110 controls,
111 hideBackButton = false,
112 hideBottomBorder = false,
113 titlebar
114 }: {
115 title?: React.ReactNode
116 controls?: React.ReactNode
117 hideBackButton?: boolean
118 hideBottomBorder?: boolean
119 titlebar?: React.ReactNode
120 }): JSX.Element {
121 if (titlebar) {
122 return (
123 <Titlebar className="p-1" hideBottomBorder={hideBottomBorder}>
124 {titlebar}
125 </Titlebar>
126 )
127 }
128 return (
129 <Titlebar
130 className="flex gap-1 p-1 items-center justify-between font-semibold"
131 hideBottomBorder={hideBottomBorder}
132 >
133 {hideBackButton ? (
134 <div className="flex gap-2 items-center pl-3 w-fit truncate text-lg font-semibold">
135 {title}
136 </div>
137 ) : (
138 <div className="flex items-center flex-1 w-0">
139 <BackButton>{title}</BackButton>
140 </div>
141 )}
142 <div className="flex-shrink-0">{controls}</div>
143 </Titlebar>
144 )
145 }
146
147 function BackButton({ children }: { children?: React.ReactNode }) {
148 const { t } = useTranslation()
149 const { pop } = useSecondaryPage()
150
151 return (
152 <Button
153 className="flex gap-1 items-center w-fit max-w-full justify-start pl-2 pr-3"
154 variant="ghost"
155 size="titlebar-icon"
156 title={t('back')}
157 onClick={() => pop()}
158 >
159 <ChevronLeft />
160 <div className="truncate text-lg font-semibold">{children}</div>
161 </Button>
162 )
163 }
164