"use client";
import { useRef } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";
type CounterFlowProps = {
/** Final number. Change it later and only the digits that differ roll. */
value: number;
/** Text before the number, like "$". */
prefix?: string;
/** Text after the number, like "%" or "+". */
suffix?: string;
/** Digits after the point. */
decimals?: number;
/** Number format, like "en-IN" for lakh commas. */
locale?: string;
/** Seconds each digit rolls. */
duration?: number;
/** Wrapper look (text size, colour). */
className?: string;
};
const DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function Digit({ digit, delay, duration }: { digit: number; delay: number; duration: number }) {
const reduce = useReducedMotion();
return (
{DIGITS.map((d) => (
{d}
))}
);
}
/* CounterFlow: an odometer. Each digit is a strip of 0 to 9 that rolls into place the first time the number scrolls into view, right digit first. */
export function CounterFlow({ value, prefix, suffix, decimals = 0, locale = "en-US", duration = 1.4, className = "text-4xl font-semibold text-white" }: CounterFlowProps) {
const box = useRef(null);
const seen = useInView(box, { once: true, amount: 0.5 });
const text = value.toLocaleString(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
const chars = [...text];
return (
{`${prefix ?? ""}${text}${suffix ?? ""}`}
{prefix}
{chars.map((char, i) => {
const fromRight = chars.length - i;
return /\d/.test(char) ? (
) : (
{char}
);
})}
{suffix}
);
}