mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at rm-precision 321 lines 8.5 kB view raw
1import React, {useImperativeHandle} from 'react' 2import { 3 FlatList, 4 type FlatListProps, 5 type GestureResponderEvent, 6 type StyleProp, 7 TouchableWithoutFeedback, 8 View, 9 type ViewStyle, 10} from 'react-native' 11import {msg} from '@lingui/macro' 12import {useLingui} from '@lingui/react' 13import {DismissableLayer, FocusGuards, FocusScope} from 'radix-ui/internal' 14import {RemoveScrollBar} from 'react-remove-scroll-bar' 15 16import {logger} from '#/logger' 17import {useA11y} from '#/state/a11y' 18import {useDialogStateControlContext} from '#/state/dialogs' 19import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf' 20import {Button, ButtonIcon} from '#/components/Button' 21import {Context} from '#/components/Dialog/context' 22import { 23 type DialogControlProps, 24 type DialogInnerProps, 25 type DialogOuterProps, 26} from '#/components/Dialog/types' 27import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 28import {Portal} from '#/components/Portal' 29 30export {useDialogContext, useDialogControl} from '#/components/Dialog/context' 31export * from '#/components/Dialog/shared' 32export * from '#/components/Dialog/types' 33export * from '#/components/Dialog/utils' 34export {Input} from '#/components/forms/TextField' 35 36// 100 minus 10vh of paddingVertical 37export const WEB_DIALOG_HEIGHT = '80vh' 38 39const stopPropagation = (e: any) => e.stopPropagation() 40const preventDefault = (e: any) => e.preventDefault() 41 42export function Outer({ 43 children, 44 control, 45 onClose, 46 webOptions, 47}: React.PropsWithChildren<DialogOuterProps>) { 48 const {_} = useLingui() 49 const {gtMobile} = useBreakpoints() 50 const [isOpen, setIsOpen] = React.useState(false) 51 const {setDialogIsOpen} = useDialogStateControlContext() 52 53 const open = React.useCallback(() => { 54 setDialogIsOpen(control.id, true) 55 setIsOpen(true) 56 }, [setIsOpen, setDialogIsOpen, control.id]) 57 58 const close = React.useCallback<DialogControlProps['close']>( 59 cb => { 60 setDialogIsOpen(control.id, false) 61 setIsOpen(false) 62 63 try { 64 if (cb && typeof cb === 'function') { 65 // This timeout ensures that the callback runs at the same time as it would on native. I.e. 66 // console.log('Step 1') -> close(() => console.log('Step 3')) -> console.log('Step 2') 67 // This should always output 'Step 1', 'Step 2', 'Step 3', but without the timeout it would output 68 // 'Step 1', 'Step 3', 'Step 2'. 69 setTimeout(cb) 70 } 71 } catch (e: any) { 72 logger.error(`Dialog closeCallback failed`, { 73 message: e.message, 74 }) 75 } 76 77 onClose?.() 78 }, 79 [control.id, onClose, setDialogIsOpen], 80 ) 81 82 const handleBackgroundPress = React.useCallback( 83 async (e: GestureResponderEvent) => { 84 webOptions?.onBackgroundPress ? webOptions.onBackgroundPress(e) : close() 85 }, 86 [webOptions, close], 87 ) 88 89 useImperativeHandle( 90 control.ref, 91 () => ({ 92 open, 93 close, 94 }), 95 [close, open], 96 ) 97 98 const context = React.useMemo( 99 () => ({ 100 close, 101 isNativeDialog: false, 102 nativeSnapPoint: 0, 103 disableDrag: false, 104 setDisableDrag: () => {}, 105 isWithinDialog: true, 106 }), 107 [close], 108 ) 109 110 return ( 111 <> 112 {isOpen && ( 113 <Portal> 114 <Context.Provider value={context}> 115 <RemoveScrollBar /> 116 <TouchableWithoutFeedback 117 accessibilityHint={undefined} 118 accessibilityLabel={_(msg`Close active dialog`)} 119 onPress={handleBackgroundPress}> 120 <View 121 style={[ 122 web(a.fixed), 123 a.inset_0, 124 a.z_10, 125 a.px_xl, 126 webOptions?.alignCenter ? a.justify_center : undefined, 127 a.align_center, 128 { 129 overflowY: 'auto', 130 paddingVertical: gtMobile ? '10vh' : a.pt_xl.paddingTop, 131 }, 132 ]}> 133 <Backdrop /> 134 {/** 135 * This is needed to prevent centered dialogs from overflowing 136 * above the screen, and provides a "natural" centering so that 137 * stacked dialogs appear relatively aligned. 138 */} 139 <View 140 style={[ 141 a.w_full, 142 a.z_20, 143 a.align_center, 144 web({minHeight: '60vh', position: 'static'}), 145 ]}> 146 {children} 147 </View> 148 </View> 149 </TouchableWithoutFeedback> 150 </Context.Provider> 151 </Portal> 152 )} 153 </> 154 ) 155} 156 157export function Inner({ 158 children, 159 style, 160 label, 161 accessibilityLabelledBy, 162 accessibilityDescribedBy, 163 header, 164 contentContainerStyle, 165}: DialogInnerProps) { 166 const t = useTheme() 167 const {close} = React.useContext(Context) 168 const {gtMobile} = useBreakpoints() 169 const {reduceMotionEnabled} = useA11y() 170 FocusGuards.useFocusGuards() 171 return ( 172 <FocusScope.FocusScope loop asChild trapped> 173 <View 174 role="dialog" 175 aria-role="dialog" 176 aria-label={label} 177 aria-labelledby={accessibilityLabelledBy} 178 aria-describedby={accessibilityDescribedBy} 179 // @ts-expect-error web only -prf 180 onClick={stopPropagation} 181 onStartShouldSetResponder={_ => true} 182 onTouchEnd={stopPropagation} 183 style={flatten([ 184 a.relative, 185 a.rounded_md, 186 a.w_full, 187 a.border, 188 t.atoms.bg, 189 { 190 maxWidth: 600, 191 borderColor: t.palette.contrast_200, 192 shadowColor: t.palette.black, 193 shadowOpacity: t.name === 'light' ? 0.1 : 0.4, 194 shadowRadius: 30, 195 }, 196 !reduceMotionEnabled && a.zoom_fade_in, 197 style, 198 ])}> 199 <DismissableLayer.DismissableLayer 200 onInteractOutside={preventDefault} 201 onFocusOutside={preventDefault} 202 onDismiss={close} 203 style={{height: '100%', display: 'flex', flexDirection: 'column'}}> 204 {header} 205 <View style={[gtMobile ? a.p_2xl : a.p_xl, contentContainerStyle]}> 206 {children} 207 </View> 208 </DismissableLayer.DismissableLayer> 209 </View> 210 </FocusScope.FocusScope> 211 ) 212} 213 214export const ScrollableInner = Inner 215 216export const InnerFlatList = React.forwardRef< 217 FlatList, 218 FlatListProps<any> & {label: string} & { 219 webInnerStyle?: StyleProp<ViewStyle> 220 webInnerContentContainerStyle?: StyleProp<ViewStyle> 221 footer?: React.ReactNode 222 } 223>(function InnerFlatList( 224 { 225 label, 226 style, 227 webInnerStyle, 228 webInnerContentContainerStyle, 229 footer, 230 ...props 231 }, 232 ref, 233) { 234 const {gtMobile} = useBreakpoints() 235 return ( 236 <Inner 237 label={label} 238 style={[ 239 a.overflow_hidden, 240 a.px_0, 241 web({maxHeight: WEB_DIALOG_HEIGHT}), 242 webInnerStyle, 243 ]} 244 contentContainerStyle={[a.h_full, a.px_0, webInnerContentContainerStyle]}> 245 <FlatList 246 ref={ref} 247 style={[a.h_full, gtMobile ? a.px_2xl : a.px_xl, flatten(style)]} 248 {...props} 249 /> 250 {footer} 251 </Inner> 252 ) 253}) 254 255export function FlatListFooter({children}: {children: React.ReactNode}) { 256 const t = useTheme() 257 258 return ( 259 <View 260 style={[ 261 a.absolute, 262 a.bottom_0, 263 a.w_full, 264 a.z_10, 265 t.atoms.bg, 266 a.border_t, 267 t.atoms.border_contrast_low, 268 a.px_lg, 269 a.py_md, 270 ]}> 271 {children} 272 </View> 273 ) 274} 275 276export function Close() { 277 const {_} = useLingui() 278 const {close} = React.useContext(Context) 279 return ( 280 <View 281 style={[ 282 a.absolute, 283 a.z_10, 284 { 285 top: a.pt_md.paddingTop, 286 right: a.pr_md.paddingRight, 287 }, 288 ]}> 289 <Button 290 size="small" 291 variant="ghost" 292 color="secondary" 293 shape="round" 294 onPress={() => close()} 295 label={_(msg`Close active dialog`)}> 296 <ButtonIcon icon={X} size="md" /> 297 </Button> 298 </View> 299 ) 300} 301 302export function Handle() { 303 return null 304} 305 306function Backdrop() { 307 const t = useTheme() 308 const {reduceMotionEnabled} = useA11y() 309 return ( 310 <View style={{opacity: 0.8}}> 311 <View 312 style={[ 313 a.fixed, 314 a.inset_0, 315 {backgroundColor: t.palette.black}, 316 !reduceMotionEnabled && a.fade_in, 317 ]} 318 /> 319 </View> 320 ) 321}