mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at samuel/exp-cli 211 lines 5.3 kB view raw
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}