mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at react-sdui 169 lines 4.5 kB view raw
1import React, {useImperativeHandle} from 'react' 2import {Pressable, useWindowDimensions, View} from 'react-native' 3import Animated, { 4 Easing, 5 runOnJS, 6 useAnimatedStyle, 7 useSharedValue, 8 withTiming, 9} from 'react-native-reanimated' 10import {useSafeAreaInsets} from 'react-native-safe-area-context' 11import {msg} from '@lingui/macro' 12import {useLingui} from '@lingui/react' 13 14import {isWeb} from '#/platform/detection' 15import {atoms as a, useTheme} from '#/alf' 16import {Portal} from '#/components/Portal' 17import {AnimatedCheck, AnimatedCheckRef} from '../anim/AnimatedCheck' 18import {Text} from '../Typography' 19 20export interface ProgressGuideToastRef { 21 open(): void 22 close(): void 23} 24 25export interface ProgressGuideToastProps { 26 title: string 27 subtitle?: string 28 visibleDuration?: number // default 5s 29} 30 31export const ProgressGuideToast = React.forwardRef< 32 ProgressGuideToastRef, 33 ProgressGuideToastProps 34>(function ProgressGuideToast({title, subtitle, visibleDuration}, ref) { 35 const t = useTheme() 36 const {_} = useLingui() 37 const insets = useSafeAreaInsets() 38 const [isOpen, setIsOpen] = React.useState(false) 39 const translateY = useSharedValue(0) 40 const opacity = useSharedValue(0) 41 const animatedCheckRef = React.useRef<AnimatedCheckRef | null>(null) 42 const timeoutRef = React.useRef<NodeJS.Timeout | undefined>() 43 const winDim = useWindowDimensions() 44 45 /** 46 * Methods 47 */ 48 49 const close = React.useCallback(() => { 50 // clear the timeout, in case this was called imperatively 51 if (timeoutRef.current) { 52 clearTimeout(timeoutRef.current) 53 timeoutRef.current = undefined 54 } 55 56 // animate the opacity then set isOpen to false when done 57 const setIsntOpen = () => setIsOpen(false) 58 opacity.value = withTiming( 59 0, 60 { 61 duration: 400, 62 easing: Easing.out(Easing.cubic), 63 }, 64 () => runOnJS(setIsntOpen)(), 65 ) 66 }, [setIsOpen, opacity]) 67 68 const open = React.useCallback(() => { 69 // set isOpen=true to render 70 setIsOpen(true) 71 72 // animate the vertical translation, the opacity, and the checkmark 73 const playCheckmark = () => animatedCheckRef.current?.play() 74 opacity.value = 0 75 opacity.value = withTiming( 76 1, 77 { 78 duration: 100, 79 easing: Easing.out(Easing.cubic), 80 }, 81 () => runOnJS(playCheckmark)(), 82 ) 83 translateY.value = 0 84 translateY.value = withTiming(insets.top + 10, { 85 duration: 500, 86 easing: Easing.out(Easing.cubic), 87 }) 88 89 // start the countdown timer to autoclose 90 timeoutRef.current = setTimeout(close, visibleDuration || 5e3) 91 }, [setIsOpen, translateY, opacity, insets, close, visibleDuration]) 92 93 useImperativeHandle( 94 ref, 95 () => ({ 96 open, 97 close, 98 }), 99 [open, close], 100 ) 101 102 const containerStyle = React.useMemo(() => { 103 let left = 10 104 let right = 10 105 if (isWeb && winDim.width > 400) { 106 left = right = (winDim.width - 380) / 2 107 } 108 return { 109 position: isWeb ? 'fixed' : 'absolute', 110 top: 0, 111 left, 112 right, 113 } 114 }, [winDim.width]) 115 116 const animatedStyle = useAnimatedStyle(() => ({ 117 transform: [{translateY: translateY.value}], 118 opacity: opacity.value, 119 })) 120 121 return ( 122 isOpen && ( 123 <Portal> 124 <Animated.View 125 style={[ 126 // @ts-ignore position: fixed is web only 127 containerStyle, 128 animatedStyle, 129 ]}> 130 <Pressable 131 style={[ 132 t.atoms.bg, 133 a.flex_row, 134 a.align_center, 135 a.gap_md, 136 a.border, 137 t.atoms.border_contrast_high, 138 a.rounded_md, 139 a.px_lg, 140 a.py_md, 141 a.shadow_sm, 142 { 143 shadowRadius: 8, 144 shadowOpacity: 0.1, 145 shadowOffset: {width: 0, height: 2}, 146 elevation: 8, 147 }, 148 ]} 149 onPress={close} 150 accessibilityLabel={_(msg`Tap to dismiss`)} 151 accessibilityHint=""> 152 <AnimatedCheck 153 fill={t.palette.primary_500} 154 ref={animatedCheckRef} 155 /> 156 <View> 157 <Text style={[a.text_md, a.font_semibold]}>{title}</Text> 158 {subtitle && ( 159 <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> 160 {subtitle} 161 </Text> 162 )} 163 </View> 164 </Pressable> 165 </Animated.View> 166 </Portal> 167 ) 168 ) 169})