{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-ticker",
  "type": "registry:component",
  "title": "Number Ticker",
  "description": "Animated number transitions for stats, KPIs, and compact numeric callouts.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "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 = new Intl.NumberFormat(locale, formatOptions);\n    NUMBER_FORMATTER_CACHE.set(key, formatter);\n  }\n  return formatter;\n}\n\nexport const NumberTicker = React.forwardRef<\n  HTMLSpanElement,\n  NumberTickerProps\n>(\n  (\n    {\n      className,\n      delay = 0,\n      duration = 1.2,\n      formatOptions,\n      from = 0,\n      locale,\n      value,\n      ...props\n    },\n    ref,\n  ) => {\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);\n\nNumberTicker.displayName = \"NumberTicker\";\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
