"use client"; import type { LucideIcon } from "lucide-react"; import { useRef } from "react"; import { cn } from "@/lib/cn"; import { gsap, motionEase, prefersReducedMotion, useGSAP } from "@/lib/motion"; export type StatItem = { label: string; value: number | string; icon?: LucideIcon; tone?: "default" | "positive" | "warning"; }; export function StatStrip({ items, className, }: { items: readonly StatItem[]; className?: string; }) { const ref = useRef(null); useGSAP( () => { const root = ref.current; if (!root) return; const cells = root.querySelectorAll("[data-stat-cell]"); if (prefersReducedMotion()) { gsap.set(cells, { autoAlpha: 1, y: 0 }); return; } gsap.fromTo( cells, { autoAlpha: 0, y: 12 }, { autoAlpha: 1, y: 0, duration: 0.45, stagger: 0.05, ease: motionEase.out } ); for (const valueEl of root.querySelectorAll("[data-stat-value]")) { const raw = valueEl.dataset.statValue; const numeric = raw !== undefined ? Number(raw) : Number.NaN; if (!Number.isFinite(numeric)) continue; const state = { value: 0 }; gsap.to(state, { value: numeric, duration: 0.7, ease: motionEase.out, onUpdate: () => { valueEl.textContent = String(Math.round(state.value)); }, }); } }, { scope: ref, dependencies: [items.map((item) => `${item.label}:${item.value}`).join("|")] } ); return (
{items.map(({ label, value, icon: Icon, tone = "default" }) => (
{Icon && ( )} {label}
{value}
))}
); }