{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "primitives-texts-counting-number",
  "title": "Counting Number",
  "description": "A counting number animation.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@unlumen-ui/hooks-use-is-in-view"
  ],
  "files": [
    {
      "path": "registry/primitives/texts/counting-number/index.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useMotionValue, useSpring, type SpringOptions } from \"motion/react\";\n\nimport {\n  useIsInView,\n  type UseIsInViewOptions,\n} from \"@/hooks/use-is-in-view\";\n\ntype CountingNumberProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  number: number;\n  fromNumber?: number;\n  padStart?: boolean;\n  decimalSeparator?: string;\n  decimalPlaces?: number;\n  transition?: SpringOptions;\n  delay?: number;\n  initiallyStable?: boolean;\n} & UseIsInViewOptions;\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  delay = 0,\n  initiallyStable = false,\n  ...props\n}: CountingNumberProps) {\n  const { ref: localRef, isInView } = useIsInView(\n    ref as React.Ref<HTMLElement>,\n    {\n      inView,\n      inViewOnce,\n      inViewMargin,\n    },\n  );\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(initiallyStable ? number : fromNumber);\n  const springVal = useSpring(motionVal, transition);\n\n  React.useEffect(() => {\n    const timeoutId = setTimeout(() => {\n      if (isInView) motionVal.set(number);\n    }, delay);\n\n    return () => clearTimeout(timeoutId);\n  }, [isInView, number, motionVal, delay]);\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, localRef]);\n\n  const finalIntLength = Math.floor(Math.abs(number)).toString().length;\n\n  const formatValue = (val: number) => {\n    let out = decimals > 0 ? val.toFixed(decimals) : Math.round(val).toString();\n    if (decimals > 0) out = out.replace(\".\", decimalSeparator);\n    if (padStart) {\n      const [intPart, fracPart] = out.split(decimalSeparator);\n      const paddedInt = (intPart ?? \"\").padStart(finalIntLength, \"0\");\n      out = fracPart ? `${paddedInt}${decimalSeparator}${fracPart}` : paddedInt;\n    }\n    return out;\n  };\n\n  const zeroText = padStart\n    ? \"0\".padStart(finalIntLength, \"0\") +\n      (decimals > 0 ? decimalSeparator + \"0\".repeat(decimals) : \"\")\n    : \"0\" + (decimals > 0 ? decimalSeparator + \"0\".repeat(decimals) : \"\");\n\n  const initialText = initiallyStable ? formatValue(number) : zeroText;\n\n  return (\n    <span ref={localRef} data-slot=\"counting-number\" {...props}>\n      {initialText}\n    </span>\n  );\n}\n\nexport { CountingNumber, type CountingNumberProps };\n",
      "type": "registry:ui",
      "target": "components/unlumen-ui/primitives/texts/counting-number.tsx"
    }
  ],
  "type": "registry:ui"
}