Number Ticker

Transitions numeriques animees pour les statistiques, les KPI et les mises en avant numeriques compactes. Installez Number Ticker depuis le registre VLLNT UI avec la CLI shadcn.

Signaler un bug

Transitions numeriques animees pour les statistiques, les KPI et les mises en avant numeriques compactes. Fait partie de la famille utility dans VLLNT UI, il est publie comme entree de registre lisible par machine — copiez la source directement dans votre application avec la CLI shadcn et possedez-la, sans dependance d'execution a une bibliotheque de composants.

Apercu

Basculez entre clair et sombre pour inspecter l'apercu Storybook integre.

Installation

Ajoutez Number Ticker a votre projet avec la CLI shadcn. La source arrive dans votre code, prete a etre adaptee :

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/number-ticker.json

Source

"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; export type NumberTickerProps = React.ComponentPropsWithoutRef<"span"> & { delay?: number; duration?: number; formatOptions?: Intl.NumberFormatOptions; from?: number; locale?: string; value: number; }; const NUMBER_FORMATTER_CACHE = new Map<string, Intl.NumberFormat>(); function getNumberTickerFormatter( locale: string | undefined, formatOptions: Intl.NumberFormatOptions | undefined, ): Intl.NumberFormat { const key = `${locale ?? ""}|${formatOptions ? JSON.stringify(formatOptions) : ""}`; let formatter = NUMBER_FORMATTER_CACHE.get(key); if (!formatter) { formatter = Intl.NumberFormat(locale, formatOptions); NUMBER_FORMATTER_CACHE.set(key, formatter); } return formatter; } export const NumberTicker = ({ className, delay = 0, duration = 1.2, formatOptions, from = 0, locale, ref, value, ...props }: NumberTickerProps & { ref?: React.Ref<HTMLSpanElement> }) => { const [currentValue, setCurrentValue] = React.useState(() => from); React.useEffect(() => { const reducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (reducedMotion || duration <= 0) { setCurrentValue(value); return; } let animationFrame = 0; let timeoutId = 0; const startDelay = Math.max(0, delay * 1000); const durationMs = duration * 1000; timeoutId = window.setTimeout(() => { const startTime = performance.now(); const tick = (timestamp: number) => { const elapsed = timestamp - startTime; const progress = Math.min(elapsed / durationMs, 1); const nextValue = from + (value - from) * progress; setCurrentValue(nextValue); if (progress < 1) { animationFrame = window.requestAnimationFrame(tick); } }; animationFrame = window.requestAnimationFrame(tick); }, startDelay); return () => { window.clearTimeout(timeoutId); window.cancelAnimationFrame(animationFrame); }; }, [delay, duration, from, value]); const formatter = getNumberTickerFormatter(locale, formatOptions); return ( <span className={cn("tabular-nums tracking-tight", className)} ref={ref} {...props} > {formatter.format(currentValue)} </span> ); }; NumberTicker.displayName = "NumberTicker";

Stories

Explorez chaque variante et etat dans le Storybook interactif :

Apercu

Basculez entre clair et sombre pour inspecter l'apercu Storybook integre.

Dependances

  • @vllnt/ui@^0.3.0