56 lines
1.3 KiB
TypeScript
56 lines
1.3 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { useEffect } from "react";
|
||
|
|
import {
|
||
|
|
type MotionValue,
|
||
|
|
motion,
|
||
|
|
useSpring,
|
||
|
|
useTransform,
|
||
|
|
} from "framer-motion";
|
||
|
|
//DOC: https://www.cult-ui.com/docs/components/animated-number
|
||
|
|
|
||
|
|
interface AnimatedNumberProps {
|
||
|
|
value: number;
|
||
|
|
mass?: number;
|
||
|
|
stiffness?: number;
|
||
|
|
damping?: number;
|
||
|
|
precision?: number;
|
||
|
|
format?: (value: number) => string;
|
||
|
|
onAnimationStart?: () => void;
|
||
|
|
onAnimationComplete?: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function AnimatedNumber({
|
||
|
|
value,
|
||
|
|
mass = 0.8,
|
||
|
|
stiffness = 75,
|
||
|
|
damping = 15,
|
||
|
|
precision = 0,
|
||
|
|
format,
|
||
|
|
onAnimationStart,
|
||
|
|
onAnimationComplete,
|
||
|
|
}: AnimatedNumberProps) {
|
||
|
|
const spring = useSpring(value, {
|
||
|
|
mass,
|
||
|
|
stiffness,
|
||
|
|
damping,
|
||
|
|
});
|
||
|
|
|
||
|
|
const display: MotionValue<string> = useTransform(spring, (current) =>
|
||
|
|
format
|
||
|
|
? format(parseFloat(current.toFixed(precision)))
|
||
|
|
: parseFloat(current.toFixed(precision)).toLocaleString(),
|
||
|
|
);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
spring.set(value);
|
||
|
|
if (onAnimationStart) onAnimationStart();
|
||
|
|
const unsubscribe = spring.on("change", () => {
|
||
|
|
if (spring.get() === value && onAnimationComplete) onAnimationComplete();
|
||
|
|
});
|
||
|
|
return () => unsubscribe();
|
||
|
|
}, [spring, value, onAnimationStart, onAnimationComplete]);
|
||
|
|
|
||
|
|
return <motion.span>{display}</motion.span>;
|
||
|
|
}
|