{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-input",
  "type": "registry:component",
  "title": "Number Input",
  "description": "Numeric input with increment and decrement controls.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/number-input/number-input.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Minus, Plus } from \"lucide-react\";\n\nimport { cn } from \"@vllnt/ui\";\nimport { Button } from \"@vllnt/ui\";\n\nexport type NumberInputProps = Omit<\n  React.ComponentPropsWithoutRef<\"input\">,\n  \"defaultValue\" | \"onChange\" | \"type\" | \"value\"\n> & {\n  defaultValue?: number;\n  onValueChange?: (value?: number) => void;\n  step?: number;\n  value?: number;\n};\n\nfunction getNumericBound(bound: number | string | undefined) {\n  if (bound === undefined) {\n    return;\n  }\n\n  const parsedBound = Number(bound);\n  return Number.isNaN(parsedBound) ? undefined : parsedBound;\n}\n\nfunction useNumberInputState(\n  controlledValue: number | undefined,\n  defaultValue: number | undefined,\n  onValueChange?: (value?: number) => void,\n) {\n  const [internalValue, setInternalValue] = React.useState<number | undefined>(\n    defaultValue,\n  );\n  const resolvedValue = controlledValue ?? internalValue;\n\n  const commitValue = (nextValue?: number) => {\n    if (controlledValue === undefined) {\n      setInternalValue(nextValue);\n    }\n\n    onValueChange?.(nextValue);\n  };\n\n  return { commitValue, resolvedValue };\n}\n\nfunction clampNumber(\n  nextValue: number,\n  min: number | undefined,\n  max: number | undefined,\n) {\n  let result = nextValue;\n\n  if (min !== undefined) {\n    result = Math.max(min, result);\n  }\n  if (max !== undefined) {\n    result = Math.min(max, result);\n  }\n\n  return result;\n}\n\nfunction StepButton({\n  direction,\n  disabled,\n  onClick,\n}: {\n  direction: \"decrement\" | \"increment\";\n  disabled?: boolean;\n  onClick: () => void;\n}) {\n  return (\n    <Button\n      className={cn(\n        \"h-full px-3\",\n        direction === \"decrement\"\n          ? \"rounded-r-none border-r\"\n          : \"rounded-l-none border-l\",\n      )}\n      disabled={disabled}\n      onClick={onClick}\n      tabIndex={-1}\n      type=\"button\"\n      variant=\"ghost\"\n    >\n      {direction === \"decrement\" ? (\n        <Minus className=\"size-4\" />\n      ) : (\n        <Plus className=\"size-4\" />\n      )}\n    </Button>\n  );\n}\n\nfunction NumberInputField({\n  disabled,\n  onValueChange,\n  placeholder,\n  reference,\n  resolvedValue,\n  ...props\n}: React.ComponentPropsWithoutRef<\"input\"> & {\n  onValueChange: (value?: number) => void;\n  reference: React.ForwardedRef<HTMLInputElement>;\n  resolvedValue?: number;\n}) {\n  return (\n    <input\n      {...props}\n      className=\"h-full w-full border-0 bg-transparent px-3 text-center text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n      disabled={disabled}\n      inputMode=\"decimal\"\n      onChange={(event) => {\n        if (event.target.value === \"\") {\n          onValueChange();\n          return;\n        }\n\n        const parsedValue = Number(event.target.value);\n        if (!Number.isNaN(parsedValue)) {\n          onValueChange(parsedValue);\n        }\n      }}\n      placeholder={placeholder}\n      ref={reference}\n      type=\"number\"\n      value={resolvedValue ?? \"\"}\n    />\n  );\n}\n\nfunction NumberInputComponent(\n  {\n    className,\n    defaultValue,\n    disabled,\n    max,\n    min,\n    onValueChange,\n    placeholder,\n    step = 1,\n    value,\n    ...props\n  }: NumberInputProps,\n  reference: React.ForwardedRef<HTMLInputElement>,\n) {\n  const { commitValue, resolvedValue } = useNumberInputState(\n    value,\n    defaultValue,\n    onValueChange,\n  );\n  const parsedMin = getNumericBound(min);\n  const parsedMax = getNumericBound(max);\n\n  const handleStepChange = (direction: number) => {\n    const baseValue = resolvedValue ?? parsedMin ?? 0;\n    commitValue(\n      clampNumber(baseValue + direction * step, parsedMin, parsedMax),\n    );\n  };\n\n  return (\n    <div\n      className={cn(\n        \"flex h-10 w-full items-center rounded-md border border-input bg-background ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2\",\n        disabled && \"cursor-not-allowed opacity-50\",\n        className,\n      )}\n    >\n      <StepButton\n        direction=\"decrement\"\n        disabled={disabled}\n        onClick={() => {\n          handleStepChange(-1);\n        }}\n      />\n      <NumberInputField\n        {...props}\n        disabled={disabled}\n        onValueChange={(nextValue) => {\n          commitValue(\n            nextValue === undefined\n              ? undefined\n              : clampNumber(nextValue, parsedMin, parsedMax),\n          );\n        }}\n        placeholder={placeholder}\n        reference={reference}\n        resolvedValue={resolvedValue}\n      />\n      <StepButton\n        direction=\"increment\"\n        disabled={disabled}\n        onClick={() => {\n          handleStepChange(1);\n        }}\n      />\n    </div>\n  );\n}\n\nconst NumberInput = React.forwardRef(NumberInputComponent);\nNumberInput.displayName = \"NumberInput\";\n\nexport { NumberInput };\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
