{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "amount-input",
  "version": "2.0.3",
  "category": "payment",
  "meta": {
    "preview": "https://ui.manifest.build/previews/amount-input.png",
    "version": "2.0.3",
    "changelog": {
      "1.0.0": "Initial release with increment buttons and preset values",
      "1.0.2": "Added comprehensive JSDoc documentation",
      "1.0.3": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
      "1.0.4": "Fixed defensive property access to handle empty objects and null values",
      "2.0.0": "BREAKING: Removed onChange action. Value changes are now internal.",
      "2.0.1": "Removed default content data - component only renders explicitly provided data",
      "2.0.2": "Fixed stale controlled state sync and memoized currency symbol computation",
      "2.0.3": "Show demo data when rendered without props"
    }
  },
  "changelog": {
    "1.0.0": "Initial release with increment buttons and preset values",
    "1.0.2": "Added comprehensive JSDoc documentation",
    "1.0.3": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
    "1.0.4": "Fixed defensive property access to handle empty objects and null values",
    "2.0.0": "BREAKING: Removed onChange action. Value changes are now internal.",
    "2.0.1": "Removed default content data - component only renders explicitly provided data",
    "2.0.2": "Fixed stale controlled state sync and memoized currency symbol computation",
    "2.0.3": "Show demo data when rendered without props"
  },
  "title": "Amount Input",
  "author": "MNFST, Inc",
  "description": "Amount input with increment/decrement buttons and preset values.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "https://ui.manifest.build/r/manifest-types.json"
  ],
  "files": [
    {
      "path": "registry/payment/amount-input.tsx",
      "content": "\"use client\"\n\nimport { useState, useRef, useEffect, useMemo } from \"react\"\nimport { Button } from \"@/components/ui/button\"\nimport { Minus, Plus } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { demoAmountPresets } from \"./demo/payment\"\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * AmountInputProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for an amount input with increment/decrement buttons and preset values.\n * Supports direct text editing by clicking on the amount.\n */\nexport interface AmountInputProps {\n  data?: {\n    /** Quick-select preset amounts displayed as buttons. */\n    presets?: number[]\n  }\n  actions?: {\n    /** Called when user confirms the selected amount. */\n    onConfirm?: (value: number) => void\n  }\n  appearance?: {\n    /**\n     * Minimum allowed value.\n     * @default 0\n     */\n    min?: number\n    /**\n     * Maximum allowed value.\n     * @default 10000\n     */\n    max?: number\n    /**\n     * Increment/decrement step size for the +/- buttons.\n     * @default 10\n     */\n    step?: number\n    /**\n     * Currency code for formatting the amount display.\n     * @default \"EUR\"\n     */\n    currency?: string\n    /**\n     * Label text displayed above the input.\n     * @default \"Amount\"\n     */\n    label?: string\n  }\n  control?: {\n    /**\n     * Controlled value for the amount input.\n     * @default 50\n     */\n    value?: number\n  }\n}\n\n/**\n * An amount input with increment/decrement buttons and preset values.\n * Supports direct text editing by clicking on the amount.\n *\n * Features:\n * - Large centered amount display\n * - Plus/minus increment buttons\n * - Preset amount quick-select buttons\n * - Click-to-edit direct input\n * - Min/max value clamping\n * - Configurable step size\n * - Optional confirm button\n *\n * @component\n * @example\n * ```tsx\n * <AmountInput\n *   data={{ presets: [25, 50, 100, 250] }}\n *   actions={{\n *     onChange: (value) => console.log(\"Amount changed:\", value),\n *     onConfirm: (value) => console.log(\"Confirmed:\", value)\n *   }}\n *   appearance={{\n *     min: 10,\n *     max: 500,\n *     step: 5,\n *     currency: \"USD\",\n *     label: \"Donation Amount\"\n *   }}\n *   control={{ value: 50 }}\n * />\n * ```\n */\nexport function AmountInput({ data, actions, appearance, control }: AmountInputProps) {\n  const resolved: NonNullable<AmountInputProps['data']> = data ?? { presets: demoAmountPresets }\n  const presets = resolved.presets ?? []\n  const onConfirm = actions?.onConfirm\n  const min = appearance?.min ?? 0\n  const max = appearance?.max ?? 10000\n  const step = appearance?.step ?? 10\n  const currency = appearance?.currency ?? \"EUR\"\n  const label = appearance?.label ?? \"Amount\"\n  const value = control?.value ?? 0\n  const [amount, setAmount] = useState(value)\n  const [isEditing, setIsEditing] = useState(false)\n  const inputRef = useRef<HTMLInputElement>(null)\n\n  // Sync internal state when controlled value changes\n  useEffect(() => {\n    setAmount(value)\n  }, [value])\n\n  const currencySymbol = useMemo(() => {\n    return new Intl.NumberFormat(\"en-US\", {\n      style: \"currency\",\n      currency,\n      minimumFractionDigits: 0,\n    })\n      .formatToParts(0)\n      .find((part) => part.type === \"currency\")?.value || currency\n  }, [currency])\n\n  const handleChange = (newValue: number) => {\n    const clamped = Math.max(min, Math.min(max, newValue))\n    setAmount(clamped)\n  }\n\n  const handlePreset = (preset: number) => {\n    setAmount(preset)\n  }\n\n  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const val = parseInt(e.target.value.replace(/[^0-9]/g, \"\"), 10)\n    if (!isNaN(val)) {\n      handleChange(val)\n    }\n  }\n\n  const handleInputBlur = () => {\n    setIsEditing(false)\n  }\n\n  const handleInputKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === \"Enter\") {\n      setIsEditing(false)\n    }\n  }\n\n  useEffect(() => {\n    if (isEditing && inputRef.current) {\n      inputRef.current.focus()\n      inputRef.current.select()\n    }\n  }, [isEditing])\n\n  return (\n    <div className=\"w-full rounded-md sm:rounded-lg bg-card p-3 sm:p-2 space-y-3\">\n      {/* Amount display with +/- controls */}\n      <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2\">\n        <span className=\"text-xs sm:text-sm text-muted-foreground\">{label}</span>\n        <div className=\"flex items-center justify-center gap-2\">\n          <button\n            onClick={() => handleChange(amount - step)}\n            disabled={amount <= min}\n            className=\"h-8 w-8 rounded-full border border-border flex items-center justify-center hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors cursor-pointer\"\n          >\n            <Minus className=\"h-4 w-4\" />\n          </button>\n          <div className=\"min-w-24 sm:min-w-28 text-center\">\n            {isEditing ? (\n              <div className=\"flex items-center justify-center gap-1\">\n                <span className=\"text-xl sm:text-2xl font-bold text-muted-foreground\">\n                  {currencySymbol}\n                </span>\n                <input\n                  ref={inputRef}\n                  type=\"text\"\n                  value={amount}\n                  onChange={handleInputChange}\n                  onBlur={handleInputBlur}\n                  onKeyDown={handleInputKeyDown}\n                  className=\"w-16 sm:w-20 text-xl sm:text-2xl font-bold bg-transparent border-b-2 border-primary text-center outline-none\"\n                />\n              </div>\n            ) : (\n              <button\n                onClick={() => setIsEditing(true)}\n                className=\"text-xl sm:text-2xl font-bold hover:text-primary transition-colors cursor-pointer\"\n              >\n                {currencySymbol}{amount}\n              </button>\n            )}\n          </div>\n          <button\n            onClick={() => handleChange(amount + step)}\n            disabled={amount >= max}\n            className=\"h-8 w-8 rounded-full border border-border flex items-center justify-center hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors cursor-pointer\"\n          >\n            <Plus className=\"h-4 w-4\" />\n          </button>\n        </div>\n      </div>\n\n      {/* Presets and confirm */}\n      <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-2\">\n        <div className=\"flex flex-wrap justify-center sm:justify-start gap-2\">\n          {presets.map((preset) => (\n            <button\n              key={preset}\n              onClick={() => handlePreset(preset)}\n              className={cn(\n                \"rounded-full border px-3 py-1 text-xs sm:text-sm transition-colors cursor-pointer\",\n                amount === preset\n                  ? \"border-foreground ring-1 ring-foreground\"\n                  : \"border-border hover:bg-muted\"\n              )}\n            >\n              {currencySymbol}{preset}\n            </button>\n          ))}\n        </div>\n        {onConfirm && (\n          <Button size=\"sm\" className=\"w-full sm:w-auto\" onClick={() => onConfirm(amount)}>\n            Confirm\n          </Button>\n        )}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/ui/amount-input.tsx"
    },
    {
      "path": "registry/payment/demo/payment.ts",
      "content": "// Demo data for Payment category components\n// This file contains sample data used for component previews and documentation\n\nimport type { OrderItem } from '../types'\n\n// Default order items for OrderSummary\nexport const demoOrderItems: OrderItem[] = [\n  { id: '1', name: 'Premium Headphones', quantity: 1, price: 199.99 },\n  { id: '2', name: 'Wireless Charger', quantity: 2, price: 29.99 }\n]\n\n// Default order data for OrderSummary\nexport const demoOrderData = {\n  items: demoOrderItems,\n  subtotal: 259.97,\n  shipping: 9.99,\n  tax: 21.58,\n  discount: 25.0,\n  discountCode: 'SAVE10',\n  total: 266.54,\n}\n\n// OrderConfirm component data\nexport const demoOrderConfirm = {\n  productName: \"Air Force 1 '07\",\n  productImage: 'https://ui.manifest.build/demo/shoe-1.png',\n  price: 299,\n  deliveryDate: 'Jan 20, 2024',\n}\n\n// AmountInput presets\nexport const demoAmountPresets = [10, 25, 50, 100]\n\n// PaymentConfirmed component data\nexport const demoPaymentConfirmed = {\n  productName: \"Air Force 1 '07\",\n  productImage: 'https://ui.manifest.build/demo/shoe-1.png',\n  price: 299,\n  deliveryDate: 'Jan 20, 2024',\n}\n",
      "type": "registry:lib",
      "target": "components/ui/demo/payment.ts"
    }
  ],
  "categories": [
    "payment"
  ],
  "type": "registry:block"
}