import {useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {Card} from '@/components/ui/card'; import useReducedMotion from '../hooks/useReducedMotion.js'; import {useDrillTarget} from './stats/StatDrill.jsx'; function useCountUp(target, instant, durationMs = 1600) { const [value, setValue] = useState(0); useEffect(() => { const safeTarget = typeof target === 'number' && Number.isFinite(target) ? target : 0; if (instant || safeTarget === 0) { setValue(safeTarget); return; } let raf; let start; const step = (ts) => { if (start === undefined) start = ts; const progress = Math.min((ts - start) / durationMs, 1); setValue(Math.floor(progress * safeTarget)); if (progress < 1) raf = requestAnimationFrame(step); }; raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [target, instant, durationMs]); return value; } /** * @param drill `{params}` of StatCount: the card is a button that opens the games behind the number * @param onClick used when there is no drill target */ export default function StatCard({label, value, icon: Icon, suffix, drill, onClick}) { const {i18n} = useTranslation(); const reduce = useReducedMotion(); const animated = useCountUp(value, reduce); const target = useDrillTarget({title: label, count: value, ...drill}) ?? (onClick && {onClick}); const card = ( {Icon && ( )} {animated.toLocaleString(i18n.language)}{suffix ?? ''} {label} ); return target ? ( {card} ) : card; }