{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-number",
  "title": "Animated Number",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/animated-number/AnimatedNumber.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\nexport interface AnimatedNumberProps extends React.HTMLAttributes<HTMLSpanElement> {\n  value: number\n  /** Value the first animation starts from. */\n  from?: number\n  /** ms per tween. */\n  duration?: number\n  /** ms before the first tween starts. */\n  delay?: number\n  format?: (value: number) => string\n  /** Render the target value instantly, no tween. */\n  disabled?: boolean\n}\n\nconst defaultFormat = (v: number) => String(Math.round(v))\n\n/**\n * Tweened number display. Counts up on mount, then smoothly retargets\n * from the currently displayed value whenever `value` changes.\n * Server render outputs the final value so hydration always matches.\n */\nconst AnimatedNumber = React.forwardRef<HTMLSpanElement, AnimatedNumberProps>(\n  ({ value, from = 0, duration = 900, delay = 0, format, disabled = false, className, ...props }, ref) => {\n    const [display, setDisplayState] = React.useState(value)\n    const displayRef = React.useRef(value)\n    const mountedRef = React.useRef(false)\n    const frameRef = React.useRef(0)\n    const timerRef = React.useRef<number | undefined>(undefined)\n\n    const fmt = format ?? defaultFormat\n\n    const setDisplay = React.useCallback((next: number) => {\n      displayRef.current = next\n      setDisplayState(next)\n    }, [])\n\n    React.useEffect(() => {\n      const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\n      function cancel() {\n        if (frameRef.current) cancelAnimationFrame(frameRef.current)\n        frameRef.current = 0\n        if (timerRef.current !== undefined) {\n          clearTimeout(timerRef.current)\n          timerRef.current = undefined\n        }\n      }\n\n      if (disabled || reduce) {\n        cancel()\n        setDisplay(value)\n        return cancel\n      }\n\n      const firstRun = !mountedRef.current\n      mountedRef.current = true\n      const startValue = firstRun ? from : displayRef.current\n      setDisplay(startValue)\n\n      const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3)\n\n      const run = () => {\n        const startTime = performance.now()\n        const step = () => {\n          const t = Math.min(1, (performance.now() - startTime) / duration)\n          setDisplay(startValue + (value - startValue) * easeOutCubic(t))\n          frameRef.current = t < 1 ? requestAnimationFrame(step) : 0\n        }\n        step()\n      }\n\n      if (firstRun && delay > 0) {\n        timerRef.current = window.setTimeout(run, delay)\n      } else {\n        run()\n      }\n\n      return cancel\n    }, [value, from, duration, delay, disabled, setDisplay])\n\n    return (\n      <span ref={ref} data-uipkge=\"\" data-slot=\"animated-number\" className={cn('tabular-nums', className)} {...props}>\n        {fmt(display)}\n      </span>\n    )\n  },\n)\nAnimatedNumber.displayName = 'AnimatedNumber'\n\nexport { AnimatedNumber }\n",
      "type": "registry:ui",
      "target": "~/components/ui/animated-number/AnimatedNumber.tsx"
    },
    {
      "path": "packages/registry-react/components/animated-number/index.ts",
      "content": "export { AnimatedNumber, type AnimatedNumberProps } from './AnimatedNumber'\n",
      "type": "registry:ui",
      "target": "~/components/ui/animated-number/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Tweened number display — counts up on mount and smoothly retargets when the value changes. Ease-out cubic via requestAnimationFrame, custom formatter (Intl-ready), delay/stagger support, honors prefers-reduced-motion, SSR-safe.",
  "categories": [
    "display",
    "data"
  ]
}