Add ProgressGuide Toast component
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import React, {useImperativeHandle} from 'react'
|
||||
import {Keyboard, Pressable, View} from 'react-native'
|
||||
import Animated, {
|
||||
Easing,
|
||||
runOnJS,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Portal} from '#/components/Portal'
|
||||
import {AnimatedCheck, AnimatedCheckRef} from '../anim/AnimatedCheck'
|
||||
import {Text} from '../Typography'
|
||||
|
||||
export interface ProgressGuideToastRef {
|
||||
open(): void
|
||||
close(): void
|
||||
}
|
||||
|
||||
export interface ProgressGuideToastProps {
|
||||
title: string
|
||||
subtitle?: string
|
||||
visibleDuration?: number // default 5s
|
||||
}
|
||||
|
||||
export const ProgressGuideToast = React.forwardRef<
|
||||
ProgressGuideToastRef,
|
||||
ProgressGuideToastProps
|
||||
>(function ProgressGuideToast({title, subtitle, visibleDuration}, ref) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
const translateY = useSharedValue(0)
|
||||
const opacity = useSharedValue(0)
|
||||
const animatedCheckRef = React.useRef<AnimatedCheckRef | null>(null)
|
||||
const timeoutRef = React.useRef<NodeJS.Timeout | undefined>()
|
||||
|
||||
/**
|
||||
* Methods
|
||||
*/
|
||||
|
||||
const close = React.useCallback(() => {
|
||||
// clear the timeout, in case this was called imperatively
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = undefined
|
||||
}
|
||||
|
||||
// animate the opacity then set isOpen to false when done
|
||||
const setIsntOpen = () => setIsOpen(false)
|
||||
opacity.value = withTiming(
|
||||
0,
|
||||
{
|
||||
duration: 400,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
},
|
||||
() => runOnJS(setIsntOpen)(),
|
||||
)
|
||||
}, [setIsOpen, opacity])
|
||||
|
||||
const open = React.useCallback(() => {
|
||||
// set isOpen=true to render
|
||||
setIsOpen(true)
|
||||
|
||||
// animate the vertical translation, the opacity, and the checkmark
|
||||
const playCheckmark = () => animatedCheckRef.current?.play()
|
||||
opacity.value = 0
|
||||
opacity.value = withTiming(
|
||||
1,
|
||||
{
|
||||
duration: 100,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
},
|
||||
() => runOnJS(playCheckmark)(),
|
||||
)
|
||||
translateY.value = 0
|
||||
translateY.value = withTiming(insets.top + 10, {
|
||||
duration: 500,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
|
||||
// start the countdown timer to autoclose
|
||||
timeoutRef.current = setTimeout(close, visibleDuration || 5e3)
|
||||
}, [setIsOpen, translateY, opacity, insets, close, visibleDuration])
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
open,
|
||||
close,
|
||||
}),
|
||||
[open, close],
|
||||
)
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{translateY: translateY.value}],
|
||||
opacity: opacity.value,
|
||||
}))
|
||||
|
||||
return (
|
||||
isOpen && (
|
||||
<Portal>
|
||||
<View
|
||||
// iOS
|
||||
accessibilityViewIsModal
|
||||
// Android
|
||||
importantForAccessibility="yes"
|
||||
style={[a.absolute, a.inset_0]}
|
||||
onTouchMove={() => Keyboard.dismiss()}>
|
||||
<Animated.View
|
||||
style={[a.absolute, {top: 0, left: 10, right: 10}, animatedStyle]}>
|
||||
<Pressable
|
||||
style={[
|
||||
t.atoms.bg,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_md,
|
||||
a.border,
|
||||
t.atoms.border_contrast_high,
|
||||
a.rounded_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}
|
||||
onPress={close}
|
||||
accessibilityLabel={_(msg`Tap to dismiss`)}
|
||||
accessibilityHint="">
|
||||
<AnimatedCheck
|
||||
fill={t.palette.primary_500}
|
||||
ref={animatedCheckRef}
|
||||
/>
|
||||
<View>
|
||||
<Text style={[a.text_md, a.font_semibold]}>{title}</Text>
|
||||
{subtitle && (
|
||||
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
|
||||
{subtitle}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Portal>
|
||||
)
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user