{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-ticker",
  "type": "registry:ui",
  "title": "Number Ticker",
  "description": "The NumberTicker component counts a number up when it scrolls into view, with locale-aware formatting.",
  "author": "Claude Code (https://claude.com/claude-code)",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "components/demo/text/number-ticker.tsx",
      "content": "\"use client\";\n\nimport {\n  useInView,\n  useMotionValueEvent,\n  useSpring,\n  useReducedMotion,\n} from \"motion/react\";\nimport { useEffect, useRef } from \"react\";\n\ninterface NumberTickerProps {\n  /** The value to count to. */\n  value: number;\n  /** Where the count starts. */\n  from?: number;\n  decimals?: number;\n  prefix?: string;\n  suffix?: string;\n  /** Count only the first time it scrolls into view. */\n  once?: boolean;\n  /** BCP 47 locale for grouping separators. */\n  locale?: string;\n  className?: string;\n}\n\n/**\n * A number that counts up when it scrolls into view.\n *\n * The animated digits are written straight to the DOM node rather than held\n * in state: a spring emits a value every frame, and re-rendering React sixty\n * times a second to print a number is wasted work.\n */\nexport function NumberTicker({\n  value,\n  from = 0,\n  decimals = 0,\n  prefix = \"\",\n  suffix = \"\",\n  once = true,\n  locale = \"en-US\",\n  className = \"\",\n}: NumberTickerProps) {\n  const ref = useRef<HTMLSpanElement>(null);\n  const isInView = useInView(ref, { once, margin: \"0px 0px -20% 0px\" });\n  const prefersReducedMotion = useReducedMotion();\n\n  const spring = useSpring(from, {\n    damping: 40,\n    stiffness: 90,\n    restDelta: decimals > 0 ? 0.001 : 0.5,\n  });\n\n  const format = (input: number) =>\n    `${prefix}${new Intl.NumberFormat(locale, {\n      minimumFractionDigits: decimals,\n      maximumFractionDigits: decimals,\n    }).format(input)}${suffix}`;\n\n  useEffect(() => {\n    if (!isInView) return;\n    if (prefersReducedMotion) {\n      spring.jump(value);\n      return;\n    }\n    spring.set(value);\n  }, [isInView, prefersReducedMotion, spring, value]);\n\n  useMotionValueEvent(spring, \"change\", (latest) => {\n    const node = ref.current;\n    if (!node) return;\n    // firstChild is the visible span; see the markup below.\n    const visible = node.firstChild as HTMLElement | null;\n    if (visible) visible.textContent = format(latest);\n  });\n\n  return (\n    <span ref={ref} className={className}>\n      {/* Only the settled value is announced. Without this, a screen reader\n          would read out every intermediate number as the spring runs. */}\n      <span aria-hidden=\"true\" className=\"tabular-nums\">\n        {format(from)}\n      </span>\n      <span className=\"sr-only\">{format(value)}</span>\n    </span>\n  );\n}\n\nexport default NumberTicker;\n",
      "type": "registry:ui",
      "target": "components/ui/number-ticker.tsx"
    }
  ]
}
