storytellurx-v1/app/components/ui/number-ticker.tsx

96 lines
2.2 KiB
TypeScript

"use client";
import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useState,
} from "react";
import {
animate,
AnimationPlaybackControls,
motion,
useMotionValue,
useTransform,
ValueAnimationTransition,
} from "motion/react";
interface NumberTickerProps {
from: number; // Starting value of the animation
target: number; // End value of the animation
transition?: ValueAnimationTransition; // Animation configuration, refer to motion docs for more details
className?: string; // additionl CSS classes for styling
onStart?: () => void; // Callback function when animation starts
onComplete?: () => void; // Callback function when animation completes
autoStart?: boolean; // Whether to start the animation automatically
}
// Ref interface to allow external control of the animation
export interface NumberTickerRef {
startAnimation: () => void;
}
const NumberTicker = forwardRef<NumberTickerRef, NumberTickerProps>(
(
{
from = 0,
target = 100,
transition = {
duration: 3,
type: "tween",
ease: "easeInOut",
},
className,
onStart,
onComplete,
autoStart = true,
...props
},
ref,
) => {
const count = useMotionValue(from);
const rounded = useTransform(count, (latest) => Math.round(latest));
const [controls, setControls] = useState<AnimationPlaybackControls | null>(
null,
);
// Function to start the animation
const startAnimation = useCallback(() => {
if (controls) controls.stop();
onStart?.();
count.set(from);
const newControls = animate(count, target, {
...transition,
onComplete: () => {
onComplete?.();
},
});
setControls(newControls);
}, []);
// Expose the startAnimation function via ref
useImperativeHandle(ref, () => ({
startAnimation,
}));
useEffect(() => {
if (autoStart) {
startAnimation();
}
return () => controls?.stop();
}, [autoStart]);
return (
<motion.span className={className} {...props}>
{rounded}
</motion.span>
);
},
);
NumberTicker.displayName = "NumberTicker";
export default NumberTicker;