forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useEffect, useRef, useState} from 'react'
2import {View} from 'react-native'
3import {useReducedMotion} from 'react-native-reanimated'
4
5import {decideShouldRoll} from '#/lib/custom-animations/util'
6
7const animationConfig = {
8 duration: 400,
9 easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
10 fill: 'forwards' as FillMode,
11}
12
13const enteringUpKeyframe = [
14 {opacity: 0, transform: 'translateY(18px)'},
15 {opacity: 1, transform: 'translateY(0)'},
16]
17
18const enteringDownKeyframe = [
19 {opacity: 0, transform: 'translateY(-18px)'},
20 {opacity: 1, transform: 'translateY(0)'},
21]
22
23const exitingUpKeyframe = [
24 {opacity: 1, transform: 'translateY(0)'},
25 {opacity: 0, transform: 'translateY(-18px)'},
26]
27
28const exitingDownKeyframe = [
29 {opacity: 1, transform: 'translateY(0)'},
30 {opacity: 0, transform: 'translateY(18px)'},
31]
32
33export function CountWheel({
34 count,
35 isToggled,
36 hasBeenToggled,
37 renderCount,
38}: {
39 count: number
40 isToggled: boolean
41 hasBeenToggled: boolean
42 renderCount: (props: {count: number}) => React.ReactNode
43}) {
44 const shouldAnimate = !useReducedMotion() && hasBeenToggled
45 const shouldRoll = decideShouldRoll(isToggled, count)
46
47 const countView = useRef<HTMLDivElement>(null)
48 const prevCountView = useRef<HTMLDivElement>(null)
49
50 const [prevCount, setPrevCount] = useState(count)
51 const prevIsToggled = useRef(isToggled)
52
53 useEffect(() => {
54 if (isToggled === prevIsToggled.current) {
55 return
56 }
57
58 const newPrevCount = isToggled ? count - 1 : count + 1
59 if (shouldAnimate && shouldRoll) {
60 countView.current?.animate?.(
61 isToggled ? enteringUpKeyframe : enteringDownKeyframe,
62 animationConfig,
63 )
64 prevCountView.current?.animate?.(
65 isToggled ? exitingUpKeyframe : exitingDownKeyframe,
66 animationConfig,
67 )
68 setPrevCount(newPrevCount)
69 }
70 prevIsToggled.current = isToggled
71 }, [isToggled, count, shouldAnimate, shouldRoll])
72
73 if (count < 1) {
74 return null
75 }
76
77 return (
78 <View>
79 <View
80 // @ts-expect-error is div
81 ref={countView}>
82 {renderCount({count})}
83 </View>
84 {shouldAnimate && (count > 1 || !isToggled) ? (
85 <View
86 style={{position: 'absolute', opacity: 0}}
87 aria-disabled={true}
88 // @ts-expect-error is div
89 ref={prevCountView}>
90 {renderCount({count: prevCount})}
91 </View>
92 ) : null}
93 </View>
94 )
95}