{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-ticker",
  "title": "Number Ticker",
  "description": "Animated number transitions for stats, KPIs, and compact numeric callouts.",
  "dependencies": [
    "@vllnt/ui@^0.3.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/number-ticker/number-ticker.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\nexport type NumberTickerProps = React.ComponentPropsWithoutRef<\"span\"> & {\n  delay?: number;\n  duration?: number;\n  formatOptions?: Intl.NumberFormatOptions;\n  from?: number;\n  locale?: string;\n  value: number;\n};\n\nconst NUMBER_FORMATTER_CACHE = new Map<string, Intl.NumberFormat>();\nfunction getNumberTickerFormatter(\n  locale: string | undefined,\n  formatOptions: Intl.NumberFormatOptions | undefined,\n): Intl.NumberFormat {\n  const key = `${locale ?? \"\"}|${formatOptions ? JSON.stringify(formatOptions) : \"\"}`;\n  let formatter = NUMBER_FORMATTER_CACHE.get(key);\n  if (!formatter) {\n    formatter = Intl.NumberFormat(locale, formatOptions);\n    NUMBER_FORMATTER_CACHE.set(key, formatter);\n  }\n  return formatter;\n}\n\nexport const NumberTicker = ({\n  className,\n  delay = 0,\n  duration = 1.2,\n  formatOptions,\n  from = 0,\n  locale,\n  ref,\n  value,\n  ...props\n}: NumberTickerProps & { ref?: React.Ref<HTMLSpanElement> }) => {\n  const [currentValue, setCurrentValue] = React.useState(() => from);\n\n  React.useEffect(() => {\n    const reducedMotion =\n      typeof window !== \"undefined\" &&\n      typeof window.matchMedia === \"function\" &&\n      window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\n    if (reducedMotion || duration <= 0) {\n      setCurrentValue(value);\n      return;\n    }\n\n    let animationFrame = 0;\n    let timeoutId = 0;\n    const startDelay = Math.max(0, delay * 1000);\n    const durationMs = duration * 1000;\n\n    timeoutId = window.setTimeout(() => {\n      const startTime = performance.now();\n\n      const tick = (timestamp: number) => {\n        const elapsed = timestamp - startTime;\n        const progress = Math.min(elapsed / durationMs, 1);\n        const nextValue = from + (value - from) * progress;\n\n        setCurrentValue(nextValue);\n\n        if (progress < 1) {\n          animationFrame = window.requestAnimationFrame(tick);\n        }\n      };\n\n      animationFrame = window.requestAnimationFrame(tick);\n    }, startDelay);\n\n    return () => {\n      window.clearTimeout(timeoutId);\n      window.cancelAnimationFrame(animationFrame);\n    };\n  }, [delay, duration, from, value]);\n\n  const formatter = getNumberTickerFormatter(locale, formatOptions);\n\n  return (\n    <span\n      className={cn(\"tabular-nums tracking-tight\", className)}\n      ref={ref}\n      {...props}\n    >\n      {formatter.format(currentValue)}\n    </span>\n  );\n};\n\nNumberTicker.displayName = \"NumberTicker\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.3.0",
  "stability": "stable"
}
