Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | 1x 1x 1x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 58x 19x 19x 19x 2x 2x 19x 19x 19x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x | // Libs
import { useEffect, useRef, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
interface Props {
value: number;
containerClassName?: string;
valueClassName?: string;
id: string;
}
export const RollingNumber = ({
value,
containerClassName,
valueClassName,
id,
}: Props) => {
const [displayValue, setDisplayValue] = useState(value);
const [animationKey, setAnimationKey] = useState(0);
const [direction, setDirection] = useState(1);
const prevValue = useRef(value);
useEffect(() => {
if (prevValue.current !== value) {
// determine animation direction
setDirection(value > prevValue.current ? 1 : -1);
// trigger animation first
setAnimationKey((k) => k + 1);
// update value **after animation duration**
const timeout = setTimeout(() => {
setDisplayValue(value);
prevValue.current = value;
}, 300); // match motion transition duration
return () => clearTimeout(timeout);
}
}, [value]);
return (
<div
className={`relative flex justify-center items-center w-fit min-h-[1.25em] leading-none ${containerClassName}`}
>
{/* Invisible placeholder to maintain width */}
<span
className={`invisible text-md sm:text-xs font-poppins font-semibold leading-sm ${valueClassName}`}
>
{value}
</span>
<AnimatePresence mode="wait" initial={false}>
<motion.span
data-testid="reaction-count"
key={animationKey}
initial={{ y: `${direction * 30}%`, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: `${-direction * 30}%`, opacity: 0 }}
transition={{ duration: 0.3 }}
className={`absolute text-md sm:text-xs font-poppins font-semibold leading-sm whitespace-nowrap text-center ${valueClassName}`}
>
{displayValue}
</motion.span>
</AnimatePresence>
</div>
);
};
|