mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {createContext, useCallback, useContext} from 'react'
2import {type GestureResponderEvent, Keyboard, View} from 'react-native'
3import {msg} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5import {useNavigation} from '@react-navigation/native'
6
7import {HITSLOP_30} from '#/lib/constants'
8import {type NavigationProp} from '#/lib/routes/types'
9import {isIOS} from '#/platform/detection'
10import {useSetDrawerOpen} from '#/state/shell'
11import {
12 atoms as a,
13 platform,
14 type TextStyleProp,
15 useBreakpoints,
16 useGutters,
17 useLayoutBreakpoints,
18 useTheme,
19 web,
20} from '#/alf'
21import {Button, ButtonIcon, type ButtonProps} from '#/components/Button'
22import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
23import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
24import {
25 BUTTON_VISUAL_ALIGNMENT_OFFSET,
26 CENTER_COLUMN_OFFSET,
27 HEADER_SLOT_SIZE,
28 SCROLLBAR_OFFSET,
29} from '#/components/Layout/const'
30import {ScrollbarOffsetContext} from '#/components/Layout/context'
31import {Text} from '#/components/Typography'
32
33export function Outer({
34 children,
35 noBottomBorder,
36 headerRef,
37 sticky = true,
38}: {
39 children: React.ReactNode
40 noBottomBorder?: boolean
41 headerRef?: React.MutableRefObject<View | null>
42 sticky?: boolean
43}) {
44 const t = useTheme()
45 const gutters = useGutters([0, 'base'])
46 const {gtMobile} = useBreakpoints()
47 const {isWithinOffsetView} = useContext(ScrollbarOffsetContext)
48 const {centerColumnOffset} = useLayoutBreakpoints()
49
50 return (
51 <View
52 ref={headerRef}
53 style={[
54 a.w_full,
55 !noBottomBorder && a.border_b,
56 a.flex_row,
57 a.align_center,
58 a.gap_sm,
59 sticky && web([a.sticky, {top: 0}, a.z_10, t.atoms.bg]),
60 gutters,
61 platform({
62 native: [a.pb_xs, {minHeight: 48}],
63 web: [a.py_xs, {minHeight: 52}],
64 }),
65 t.atoms.border_contrast_low,
66 gtMobile && [a.mx_auto, {maxWidth: 600}],
67 !isWithinOffsetView && {
68 transform: [
69 {translateX: centerColumnOffset ? CENTER_COLUMN_OFFSET : 0},
70 {translateX: web(SCROLLBAR_OFFSET) ?? 0},
71 ],
72 },
73 ]}>
74 {children}
75 </View>
76 )
77}
78
79const AlignmentContext = createContext<'platform' | 'left'>('platform')
80
81export function Content({
82 children,
83 align = 'platform',
84}: {
85 children?: React.ReactNode
86 align?: 'platform' | 'left'
87}) {
88 return (
89 <View
90 style={[
91 a.flex_1,
92 a.justify_center,
93 isIOS && align === 'platform' && a.align_center,
94 {minHeight: HEADER_SLOT_SIZE},
95 ]}>
96 <AlignmentContext.Provider value={align}>
97 {children}
98 </AlignmentContext.Provider>
99 </View>
100 )
101}
102
103export function Slot({children}: {children?: React.ReactNode}) {
104 return <View style={[a.z_50, {width: HEADER_SLOT_SIZE}]}>{children}</View>
105}
106
107export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
108 const {_} = useLingui()
109 const navigation = useNavigation<NavigationProp>()
110
111 const onPressBack = useCallback(
112 (evt: GestureResponderEvent) => {
113 onPress?.(evt)
114 if (evt.defaultPrevented) return
115 if (navigation.canGoBack()) {
116 navigation.goBack()
117 } else {
118 navigation.navigate('Home')
119 }
120 },
121 [onPress, navigation],
122 )
123
124 return (
125 <Slot>
126 <Button
127 label={_(msg`Go back`)}
128 size="small"
129 variant="ghost"
130 color="secondary"
131 shape="square"
132 onPress={onPressBack}
133 hitSlop={HITSLOP_30}
134 style={[
135 {marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
136 a.bg_transparent,
137 style,
138 ]}
139 {...props}>
140 <ButtonIcon icon={ArrowLeft} size="lg" />
141 </Button>
142 </Slot>
143 )
144}
145
146export function MenuButton() {
147 const {_} = useLingui()
148 const setDrawerOpen = useSetDrawerOpen()
149 const {gtMobile} = useBreakpoints()
150
151 const onPress = useCallback(() => {
152 Keyboard.dismiss()
153 setDrawerOpen(true)
154 }, [setDrawerOpen])
155
156 return gtMobile ? null : (
157 <Slot>
158 <Button
159 label={_(msg`Open drawer menu`)}
160 size="small"
161 variant="ghost"
162 color="secondary"
163 shape="square"
164 onPress={onPress}
165 hitSlop={HITSLOP_30}
166 style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}]}>
167 <ButtonIcon icon={Menu} size="lg" />
168 </Button>
169 </Slot>
170 )
171}
172
173export function TitleText({
174 children,
175 style,
176}: {children: React.ReactNode} & TextStyleProp) {
177 const {gtMobile} = useBreakpoints()
178 const align = useContext(AlignmentContext)
179 return (
180 <Text
181 style={[
182 a.text_lg,
183 a.font_heavy,
184 a.leading_tight,
185 isIOS && align === 'platform' && a.text_center,
186 gtMobile && a.text_xl,
187 style,
188 ]}
189 numberOfLines={2}
190 emoji>
191 {children}
192 </Text>
193 )
194}
195
196export function SubtitleText({children}: {children: React.ReactNode}) {
197 const t = useTheme()
198 const align = useContext(AlignmentContext)
199 return (
200 <Text
201 style={[
202 a.text_sm,
203 a.leading_snug,
204 isIOS && align === 'platform' && a.text_center,
205 t.atoms.text_contrast_medium,
206 ]}
207 numberOfLines={2}>
208 {children}
209 </Text>
210 )
211}