{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "demo-number-flow",
  "title": "Number Flow Demo",
  "description": "Number Flow demo with currency, input overlay, and countdown examples.",
  "registryDependencies": ["@soralabs/number-flow"],
  "files": [
    {
      "path": "registry/demo/primitives/texts/number-flow/index.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Minus, Plus } from \"lucide-react\";\nimport {\n  type InputEventHandler,\n  type PointerEventHandler,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { NumberFlow } from \"@/components/sora-ui/texts/number-flow\";\n\nconst EXAMPLES = [\n  { id: \"currency\", label: \"Currency\" },\n  { id: \"input\", label: \"Input\" },\n  { id: \"countdown\", label: \"Countdown\" },\n] as const;\n\ntype ExampleId = (typeof EXAMPLES)[number][\"id\"];\n\nfunction ExampleTabs({\n  active,\n  onChange,\n}: {\n  active: ExampleId;\n  onChange: (id: ExampleId) => void;\n}) {\n  return (\n    <div className=\"flex flex-wrap justify-center gap-1.5\">\n      {EXAMPLES.map(({ id, label }) => (\n        <button\n          className={cn(\n            \"rounded-md border px-2.5 py-1 text-xs transition-colors\",\n            active === id\n              ? \"border-foreground/20 bg-accent text-foreground\"\n              : \"text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n          )}\n          key={id}\n          onClick={() => onChange(id)}\n          type=\"button\"\n        >\n          {label}\n        </button>\n      ))}\n    </div>\n  );\n}\n\nfunction CurrencyDemo() {\n  const [value, setValue] = useState(1234.56);\n\n  return (\n    <div className=\"flex flex-col items-center gap-6\">\n      <NumberFlow\n        className=\"font-semibold text-5xl tabular-nums\"\n        format={{\n          style: \"currency\",\n          currency: \"USD\",\n        }}\n        value={value}\n      />\n      <div className=\"flex flex-wrap justify-center gap-2\">\n        <button\n          className=\"rounded-md border px-3 py-1.5 text-sm hover:bg-accent\"\n          onClick={() => setValue((v) => Math.max(0, v - 87.4))}\n          type=\"button\"\n        >\n          −87.4\n        </button>\n        <button\n          className=\"rounded-md border px-3 py-1.5 text-sm hover:bg-accent\"\n          onClick={() => setValue((v) => v + 87.4)}\n          type=\"button\"\n        >\n          +87.4\n        </button>\n        <button\n          className=\"rounded-md border px-3 py-1.5 text-sm hover:bg-accent\"\n          onClick={() => setValue(Math.round(Math.random() * 10_000) / 100)}\n          type=\"button\"\n        >\n          Randomize\n        </button>\n      </div>\n    </div>\n  );\n}\n\nfunction InputDemo() {\n  const [value, setValue] = useState(42);\n  const defaultValue = useRef(value);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const [animated, setAnimated] = useState(true);\n  const min = 0;\n  const max = 999;\n\n  const handleInput: InputEventHandler<HTMLInputElement> = ({\n    currentTarget: el,\n  }) => {\n    setAnimated(false);\n    let next = value;\n    if (el.value === \"\") {\n      next = defaultValue.current;\n    } else {\n      const num = el.valueAsNumber;\n      if (!Number.isNaN(num) && min <= num && num <= max) {\n        next = num;\n      }\n    }\n    el.value = String(next);\n    setValue(next);\n  };\n\n  const handlePointerDown =\n    (diff: number): PointerEventHandler<HTMLButtonElement> =>\n    (event) => {\n      setAnimated(true);\n      if (event.pointerType === \"mouse\") {\n        event.preventDefault();\n        inputRef.current?.focus();\n      }\n      setValue((current) => Math.min(Math.max(current + diff, min), max));\n    };\n\n  const instantTransition = { duration: 0 } as const;\n\n  return (\n    <div className=\"group flex items-stretch rounded-md font-semibold text-3xl ring ring-border transition-[box-shadow] focus-within:ring-2 focus-within:ring-ring\">\n      <button\n        aria-hidden=\"true\"\n        className=\"flex items-center pr-[0.325em] pl-[0.5em]\"\n        disabled={value <= min}\n        onPointerDown={handlePointerDown(-1)}\n        tabIndex={-1}\n        type=\"button\"\n      >\n        <Minus absoluteStrokeWidth className=\"size-4\" strokeWidth={3.5} />\n      </button>\n      <div className=\"relative grid items-center justify-items-center text-center [grid-template-areas:'overlap'] *:[grid-area:overlap]\">\n        <input\n          autoComplete=\"off\"\n          className={cn(\n            \"w-[1.5em] bg-transparent py-2 text-center font-[inherit] text-transparent outline-none\",\n            \"[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\"\n          )}\n          inputMode=\"numeric\"\n          max={max}\n          min={min}\n          onInput={handleInput}\n          ref={inputRef}\n          step={1}\n          style={{ fontKerning: \"none\" }}\n          type=\"number\"\n          value={value}\n        />\n        <NumberFlow\n          aria-hidden=\"true\"\n          className=\"pointer-events-none\"\n          format={{ useGrouping: false }}\n          locales=\"en-US\"\n          spinTransition={animated ? undefined : instantTransition}\n          transition={animated ? undefined : instantTransition}\n          value={value}\n        />\n      </div>\n      <button\n        aria-hidden=\"true\"\n        className=\"flex items-center pr-[0.5em] pl-[0.325em]\"\n        disabled={value >= max}\n        onPointerDown={handlePointerDown(1)}\n        tabIndex={-1}\n        type=\"button\"\n      >\n        <Plus absoluteStrokeWidth className=\"size-4\" strokeWidth={3.5} />\n      </button>\n    </div>\n  );\n}\n\nfunction CountdownDemo() {\n  const [seconds, setSeconds] = useState(3723);\n\n  useEffect(() => {\n    const id = window.setInterval(() => {\n      setSeconds((current) => (current > 0 ? current - 1 : 3723));\n    }, 1000);\n    return () => window.clearInterval(id);\n  }, []);\n\n  const hh = Math.floor(seconds / 3600);\n  const mm = Math.floor((seconds % 3600) / 60);\n  const ss = seconds % 60;\n\n  return (\n    <div\n      className=\"flex items-baseline font-semibold text-3xl tabular-nums sm:text-4xl\"\n      style={{ fontVariantNumeric: \"tabular-nums\" }}\n    >\n      <NumberFlow format={{ minimumIntegerDigits: 2 }} trend={-1} value={hh} />\n      <NumberFlow\n        format={{ minimumIntegerDigits: 2 }}\n        prefix=\":\"\n        trend={-1}\n        value={mm}\n      />\n      <NumberFlow\n        format={{ minimumIntegerDigits: 2 }}\n        prefix=\":\"\n        trend={-1}\n        value={ss}\n      />\n    </div>\n  );\n}\n\nexport function NumberFlowExample() {\n  const [example, setExample] = useState<ExampleId>(\"currency\");\n\n  return (\n    <div className=\"flex min-h-[180px] flex-col items-center justify-center gap-6 px-4 text-center\">\n      <ExampleTabs active={example} onChange={setExample} />\n      {example === \"currency\" ? <CurrencyDemo /> : null}\n      {example === \"input\" ? <InputDemo /> : null}\n      {example === \"countdown\" ? <CountdownDemo /> : null}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/sora-ui/demo/texts/number-flow.tsx"
    }
  ],
  "type": "registry:ui"
}
