import { AnimatePresence, motion, useReducedMotion } from 'framer-motion' import { useState } from 'react' import styles from './AnimatedNumber.module.scss' type AnimatedNumberProps = { value: number format?: (value: number) => string align?: 'center' | 'start' className?: string } const variants = { initial: (direction: number) => ({ x: direction * 12, opacity: 0 }), animate: { x: 0, opacity: 1 }, exit: (direction: number) => ({ x: direction * -12, opacity: 0 }), } // A quantity stepper's count slides leftward when incrementing (the new // digit enters from the right, the old one exits to the left) and // rightward when decrementing. // // `direction` is derived from a prop change during render (the React-docs // "adjusting state when a prop changes" pattern) rather than mutating a ref // in the render body — mutating a ref here would already reflect the new // value by the time `exit`'s "custom" is resolved for the outgoing digit, // so every transition — increment or decrement — read as "forward". export const AnimatedNumber = ({ value, format = String, align = 'center', className, }: AnimatedNumberProps) => { const reduceMotion = useReducedMotion() const [previousValue, setPreviousValue] = useState(value) const [direction, setDirection] = useState(1) if (value !== previousValue) { setDirection(value > previousValue ? 1 : -1) setPreviousValue(value) } if (reduceMotion) { return {format(value)} } return ( {format(value)} ) }