{
  "name": "quantity-input-basic",
  "type": "registry:component",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "type": "registry:component",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\nimport { Minus, Plus } from \"lucide-react\";\nimport { ChangeEvent, useState, useEffect } from \"react\";\n\ninterface QuantityInputBasicProps {\n  quantity: number;\n  min?: number;\n  max?: number | null;\n  step?: number;\n  disabled?: boolean;\n  onChange: (quantity: number) => void;\n  className?: string;\n}\n\nconst QuantityInputBasic = ({\n  className,\n  disabled = false,\n  max = null,\n  min = 1,\n  onChange,\n  quantity,\n  step = 1,\n}: QuantityInputBasicProps) => {\n  // Internal state to handle input field text during editing\n  const [inputValue, setInputValue] = useState(quantity.toString());\n\n  // Update internal input value when external quantity prop changes\n  useEffect(() => {\n    setInputValue(quantity.toString());\n  }, [quantity]);\n\n  const handleDecrease = () => {\n    if (quantity - step >= min) {\n      onChange(quantity - step);\n    }\n  };\n\n  const handleIncrease = () => {\n    if (max === null || quantity + step <= max) {\n      onChange(quantity + step);\n    }\n  };\n\n  const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {\n    // Allow any input including empty string during editing\n    setInputValue(e.target.value);\n\n    // If the input is a valid number, update the parent component\n    const value = parseInt(e.target.value);\n    if (!isNaN(value) && value >= min && (max === null || value <= max)) {\n      onChange(value);\n    }\n  };\n\n  const handleBlur = () => {\n    // When the field loses focus, ensure we have a valid value\n    const value = parseInt(inputValue);\n    if (isNaN(value) || value < min) {\n      // If invalid or below min, reset to min\n      setInputValue(min.toString());\n      onChange(min);\n    } else if (max !== null && value > max) {\n      // If above max and max is defined, reset to max\n      setInputValue(max.toString());\n      onChange(max);\n    } else {\n      // Ensure the displayed value matches the actual value\n      setInputValue(value.toString());\n      onChange(value);\n    }\n  };\n\n  return (\n    <div\n      className={cn(\n        \"inline-flex cursor-pointer rounded-lg shadow-xs shadow-black/5\",\n        className\n      )}\n    >\n      <button\n        className={cn(\n          \"hover:bg-muted-foreground/10 flex cursor-pointer items-center justify-center rounded-s-lg border px-3 py-1 focus-visible:z-10 disabled:cursor-not-allowed disabled:opacity-50\",\n          disabled && \"pointer-events-none\"\n        )}\n        onClick={handleDecrease}\n        disabled={disabled || quantity <= min}\n        aria-label=\"Decrease quantity\"\n      >\n        <Minus size={16} strokeWidth={2} aria-hidden=\"true\" />\n      </button>\n      <input\n        type=\"text\"\n        value={inputValue}\n        onChange={handleInputChange}\n        onBlur={handleBlur}\n        className=\"w-12 border-y px-2 py-1 text-center font-mono outline-none\"\n        min={min}\n        max={max !== null ? max : undefined}\n        disabled={disabled}\n        aria-label=\"Quantity\"\n      />\n      <button\n        className={cn(\n          \"hover:bg-muted-foreground/10 flex cursor-pointer items-center justify-center rounded-e-lg border px-3 py-1 focus-visible:z-10 disabled:cursor-not-allowed disabled:opacity-50\",\n          disabled && \"pointer-events-none\"\n        )}\n        onClick={handleIncrease}\n        disabled={disabled || (max !== null && quantity >= max)}\n        aria-label=\"Increase quantity\"\n      >\n        <Plus size={16} strokeWidth={2} aria-hidden=\"true\" />\n      </button>\n    </div>\n  );\n};\n\nexport default QuantityInputBasic;\n",
      "path": "/components/commerce-ui/components/quantity-input/basic/quantity-input-basic.tsx",
      "target": "components/commerce-ui/quantity-input-basic.tsx"
    }
  ]
}