{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "counting-number",
  "type": "registry:ui",
  "title": "Counting Number",
  "description": "A numeric display component that smoothly animates number changes with a counting animation.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/text/counting-number/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport {\n  type SpringOptions,\n  type UseInViewOptions,\n  useInView,\n  useMotionValue,\n  useSpring,\n} from 'motion/react';\n\ntype CountingNumberProps = React.ComponentProps<'span'> & {\n  number: number;\n  fromNumber?: number;\n  padStart?: boolean;\n  inView?: boolean;\n  inViewMargin?: UseInViewOptions['margin'];\n  inViewOnce?: boolean;\n  decimalSeparator?: string;\n  transition?: SpringOptions;\n  decimalPlaces?: number;\n};\n\nfunction CountingNumber({\n  ref,\n  number,\n  fromNumber = 0,\n  padStart = false,\n  inView = false,\n  inViewMargin = '0px',\n  inViewOnce = true,\n  decimalSeparator = '.',\n  transition = { stiffness: 90, damping: 50 },\n  decimalPlaces = 0,\n  className,\n  ...props\n}: CountingNumberProps) {\n  const localRef = React.useRef<HTMLSpanElement>(null);\n  React.useImperativeHandle(ref, () => localRef.current as HTMLSpanElement);\n\n  const numberStr = number.toString();\n  const decimals =\n    typeof decimalPlaces === 'number'\n      ? decimalPlaces\n      : numberStr.includes('.')\n        ? (numberStr.split('.')[1]?.length ?? 0)\n        : 0;\n\n  const motionVal = useMotionValue(fromNumber);\n  const springVal = useSpring(motionVal, transition);\n  const inViewResult = useInView(localRef, {\n    once: inViewOnce,\n    margin: inViewMargin,\n  });\n  const isInView = !inView || inViewResult;\n\n  React.useEffect(() => {\n    if (isInView) motionVal.set(number);\n  }, [isInView, number, motionVal]);\n\n  React.useEffect(() => {\n    const unsubscribe = springVal.on('change', (latest) => {\n      if (localRef.current) {\n        let formatted =\n          decimals > 0\n            ? latest.toFixed(decimals)\n            : Math.round(latest).toString();\n\n        if (decimals > 0) {\n          formatted = formatted.replace('.', decimalSeparator);\n        }\n\n        if (padStart) {\n          const finalIntLength = Math.floor(Math.abs(number)).toString().length;\n          const [intPart, fracPart] = formatted.split(decimalSeparator);\n          const paddedInt = intPart?.padStart(finalIntLength, '0') ?? '';\n          formatted = fracPart\n            ? `${paddedInt}${decimalSeparator}${fracPart}`\n            : paddedInt;\n        }\n\n        localRef.current.textContent = formatted;\n      }\n    });\n    return () => unsubscribe();\n  }, [springVal, decimals, padStart, number, decimalSeparator]);\n\n  const finalIntLength = Math.floor(Math.abs(number)).toString().length;\n  const initialText = padStart\n    ? '0'.padStart(finalIntLength, '0') +\n      (decimals > 0 ? decimalSeparator + '0'.repeat(decimals) : '')\n    : '0' + (decimals > 0 ? decimalSeparator + '0'.repeat(decimals) : '');\n\n  return (\n    <span\n      ref={localRef}\n      data-slot=\"counting-number\"\n      className={className}\n      {...props}\n    >\n      {initialText}\n    </span>\n  );\n}\n\nexport { CountingNumber, type CountingNumberProps };\n",
      "type": "registry:ui",
      "target": "components/animate-ui/text/counting-number.tsx"
    }
  ]
}