All files / src/components/RollingNumber index.tsx

100% Statements 44/44
100% Branches 8/8
100% Functions 1/1
100% Lines 44/44

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>
  );
};