diff options
Diffstat (limited to 'src/lib/custom-animations/LikeIcon.web.tsx')
-rw-r--r-- | src/lib/custom-animations/LikeIcon.web.tsx | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/src/lib/custom-animations/LikeIcon.web.tsx b/src/lib/custom-animations/LikeIcon.web.tsx new file mode 100644 index 000000000..c131dcf67 --- /dev/null +++ b/src/lib/custom-animations/LikeIcon.web.tsx @@ -0,0 +1,115 @@ +import React from 'react' +import {View} from 'react-native' +import {useReducedMotion} from 'react-native-reanimated' + +import {s} from 'lib/styles' +import {useTheme} from '#/alf' +import { + Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled, + Heart2_Stroke2_Corner0_Rounded as HeartIconOutline, +} from '#/components/icons/Heart2' + +const animationConfig = { + duration: 400, + easing: 'cubic-bezier(0.4, 0, 0.2, 1)', + fill: 'forwards' as FillMode, +} + +const keyframe = [ + {transform: 'scale(1)'}, + {transform: 'scale(0.7)'}, + {transform: 'scale(1.2)'}, + {transform: 'scale(1)'}, +] + +const circle1Keyframe = [ + {opacity: 0, transform: 'scale(0)'}, + {opacity: 0.4}, + {transform: 'scale(1.5)'}, + {opacity: 0.4}, + {opacity: 0, transform: 'scale(1.5)'}, +] + +const circle2Keyframe = [ + {opacity: 0, transform: 'scale(0)'}, + {opacity: 1}, + {transform: 'scale(0)'}, + {opacity: 1}, + {opacity: 0, transform: 'scale(1.5)'}, +] + +export function AnimatedLikeIcon({ + isLiked, + big, +}: { + isLiked: boolean + big?: boolean +}) { + const t = useTheme() + const size = big ? 22 : 18 + const shouldAnimate = !useReducedMotion() + const prevIsLiked = React.useRef(isLiked) + + const likeIconRef = React.useRef<HTMLDivElement>(null) + const circle1Ref = React.useRef<HTMLDivElement>(null) + const circle2Ref = React.useRef<HTMLDivElement>(null) + + React.useEffect(() => { + if (prevIsLiked.current === isLiked) { + return + } + + if (shouldAnimate && isLiked) { + likeIconRef.current?.animate?.(keyframe, animationConfig) + circle1Ref.current?.animate?.(circle1Keyframe, animationConfig) + circle2Ref.current?.animate?.(circle2Keyframe, animationConfig) + } + prevIsLiked.current = isLiked + }, [shouldAnimate, isLiked]) + + return ( + <View> + {isLiked ? ( + // @ts-expect-error is div + <View ref={likeIconRef}> + <HeartIconFilled style={s.likeColor} width={size} /> + </View> + ) : ( + <HeartIconOutline + style={[{color: t.palette.contrast_500}, {pointerEvents: 'none'}]} + width={size} + /> + )} + <View + // @ts-expect-error is div + ref={circle1Ref} + style={{ + position: 'absolute', + backgroundColor: s.likeColor.color, + top: 0, + left: 0, + width: size, + height: size, + zIndex: -1, + pointerEvents: 'none', + borderRadius: size / 2, + }} + /> + <View + // @ts-expect-error is div + ref={circle2Ref} + style={{ + position: 'absolute', + backgroundColor: t.atoms.bg.backgroundColor, + top: 0, + left: 0, + width: size, + height: size, + zIndex: -1, + pointerEvents: 'none', + borderRadius: size / 2, + }} + /> + </View> + ) +} |