mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
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})