Bluesky app fork with some witchin' additions 馃挮
at main 122 lines 3.3 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import {useReducedMotion} from 'react-native-reanimated' 4 5import {decideShouldRoll} from '#/lib/custom-animations/util' 6import {s} from '#/lib/styles' 7import {Text} from '#/view/com/util/text/Text' 8import {atoms as a, useTheme} from '#/alf' 9import {useFormatPostStatCount} from '#/components/PostControls/util' 10 11const animationConfig = { 12 duration: 400, 13 easing: 'cubic-bezier(0.4, 0, 0.2, 1)', 14 fill: 'forwards' as FillMode, 15} 16 17const enteringUpKeyframe = [ 18 {opacity: 0, transform: 'translateY(18px)'}, 19 {opacity: 1, transform: 'translateY(0)'}, 20] 21 22const enteringDownKeyframe = [ 23 {opacity: 0, transform: 'translateY(-18px)'}, 24 {opacity: 1, transform: 'translateY(0)'}, 25] 26 27const exitingUpKeyframe = [ 28 {opacity: 1, transform: 'translateY(0)'}, 29 {opacity: 0, transform: 'translateY(-18px)'}, 30] 31 32const exitingDownKeyframe = [ 33 {opacity: 1, transform: 'translateY(0)'}, 34 {opacity: 0, transform: 'translateY(18px)'}, 35] 36 37export function CountWheel({ 38 likeCount, 39 big, 40 isLiked, 41 hasBeenToggled, 42}: { 43 likeCount: number 44 big?: boolean 45 isLiked: boolean 46 hasBeenToggled: boolean 47}) { 48 const t = useTheme() 49 const shouldAnimate = !useReducedMotion() && hasBeenToggled 50 const shouldRoll = decideShouldRoll(isLiked, likeCount) 51 52 const countView = React.useRef<HTMLDivElement>(null) 53 const prevCountView = React.useRef<HTMLDivElement>(null) 54 55 const [prevCount, setPrevCount] = React.useState(likeCount) 56 const prevIsLiked = React.useRef(isLiked) 57 const formatPostStatCount = useFormatPostStatCount() 58 const formattedCount = formatPostStatCount(likeCount) 59 const formattedPrevCount = formatPostStatCount(prevCount) 60 61 React.useEffect(() => { 62 if (isLiked === prevIsLiked.current) { 63 return 64 } 65 66 const newPrevCount = isLiked ? likeCount - 1 : likeCount + 1 67 if (shouldAnimate && shouldRoll) { 68 countView.current?.animate?.( 69 isLiked ? enteringUpKeyframe : enteringDownKeyframe, 70 animationConfig, 71 ) 72 prevCountView.current?.animate?.( 73 isLiked ? exitingUpKeyframe : exitingDownKeyframe, 74 animationConfig, 75 ) 76 setPrevCount(newPrevCount) 77 } 78 prevIsLiked.current = isLiked 79 }, [isLiked, likeCount, shouldAnimate, shouldRoll]) 80 81 if (likeCount < 1) { 82 return null 83 } 84 85 return ( 86 <View> 87 <View 88 // @ts-expect-error is div 89 ref={countView}> 90 <Text 91 testID="likeCount" 92 style={[ 93 big ? a.text_md : a.text_sm, 94 a.user_select_none, 95 isLiked 96 ? [a.font_semi_bold, s.likeColor] 97 : {color: t.palette.contrast_500}, 98 ]}> 99 {formattedCount} 100 </Text> 101 </View> 102 {shouldAnimate && (likeCount > 1 || !isLiked) ? ( 103 <View 104 style={{position: 'absolute', opacity: 0}} 105 aria-disabled={true} 106 // @ts-expect-error is div 107 ref={prevCountView}> 108 <Text 109 style={[ 110 big ? a.text_md : a.text_sm, 111 a.user_select_none, 112 isLiked 113 ? [a.font_semi_bold, s.likeColor] 114 : {color: t.palette.contrast_500}, 115 ]}> 116 {formattedPrevCount} 117 </Text> 118 </View> 119 ) : null} 120 </View> 121 ) 122}