Files
bsky-social-app/src/components/anim/AnimatedCheck.tsx
T
2026-03-11 11:32:43 -05:00

96 lines
2.3 KiB
TypeScript

import {forwardRef, useCallback, useEffect, useImperativeHandle} from 'react'
import Animated, {
Easing,
useAnimatedProps,
useSharedValue,
withDelay,
withTiming,
} from 'react-native-reanimated'
import Svg, {Circle, Path} from 'react-native-svg'
import {type Props, useCommonSVGProps} from '#/components/icons/common'
const AnimatedPath = Animated.createAnimatedComponent(Path)
const AnimatedCircle = Animated.createAnimatedComponent(Circle)
const PATH = 'M14.1 27.2l7.1 7.2 16.7-16.8'
export interface AnimatedCheckRef {
play(cb?: () => void): void
}
export interface AnimatedCheckProps extends Props {
playOnMount?: boolean
}
export const AnimatedCheck = forwardRef<AnimatedCheckRef, AnimatedCheckProps>(
function AnimatedCheck({playOnMount, ...props}, ref) {
const {fill, size, style, ...rest} = useCommonSVGProps(props)
const circleAnim = useSharedValue(0)
const checkAnim = useSharedValue(0)
const circleAnimatedProps = useAnimatedProps(() => ({
strokeDashoffset: 166 - circleAnim.get() * 166,
}))
const checkAnimatedProps = useAnimatedProps(() => ({
strokeDashoffset: 48 - 48 * checkAnim.get(),
}))
const play = useCallback(
(cb?: () => void) => {
circleAnim.set(0)
checkAnim.set(0)
circleAnim.set(() =>
withTiming(1, {duration: 500, easing: Easing.linear}),
)
checkAnim.set(() =>
withDelay(
500,
withTiming(1, {duration: 300, easing: Easing.linear}, cb),
),
)
},
[circleAnim, checkAnim],
)
useImperativeHandle(ref, () => ({
play,
}))
useEffect(() => {
if (playOnMount) {
play()
}
}, [play, playOnMount])
return (
<Svg
fill="none"
{...rest}
viewBox="0 0 52 52"
width={size}
height={size}
style={style}>
<AnimatedCircle
animatedProps={circleAnimatedProps}
cx="26"
cy="26"
r="24"
fill="none"
stroke={fill}
strokeWidth={4}
strokeDasharray={166}
/>
<AnimatedPath
animatedProps={checkAnimatedProps}
stroke={fill}
d={PATH}
strokeWidth={4}
strokeDasharray={48}
/>
</Svg>
)
},
)