about summary refs log tree commit diff
path: root/src/lib/custom-animations/LikeIcon.web.tsx
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-09-02 01:37:24 -0700
committerGitHub <noreply@github.com>2024-09-02 01:37:24 -0700
commit1225e8448524633466379d5ac00a78b53e1a9a51 (patch)
tree6215245576fed72912eeee9a51bcce01baf725ba /src/lib/custom-animations/LikeIcon.web.tsx
parenteb868a042ad4f767b8e1b90d33bf1171b66a5238 (diff)
downloadvoidsky-1225e8448524633466379d5ac00a78b53e1a9a51.tar.zst
Improve animations for like button (#5074)
Diffstat (limited to 'src/lib/custom-animations/LikeIcon.web.tsx')
-rw-r--r--src/lib/custom-animations/LikeIcon.web.tsx115
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>
+  )
+}