{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "currency-input",
  "title": "Currency Input",
  "description": "An amount field with a currency symbol and thousands-separator formatting on blur.",
  "registryDependencies": [
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/ui/currency-input.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction onlyDigitsAndSeparator(value: string) {\n  // Keep digits, a single decimal separator (. or ,), and nothing else.\n  return value.replace(/[^\\d.,]/g, \"\")\n}\n\nfunction parseAmount(raw: string): number | null {\n  const cleaned = onlyDigitsAndSeparator(raw).replace(\",\", \".\")\n  if (cleaned === \"\" || cleaned === \".\") return null\n  const parsed = Number.parseFloat(cleaned)\n  return Number.isNaN(parsed) ? null : parsed\n}\n\nfunction currencySymbol(currency: string, locale: string) {\n  const parts = new Intl.NumberFormat(locale, {\n    style: \"currency\",\n    currency,\n    currencyDisplay: \"narrowSymbol\",\n  }).formatToParts(0)\n  return parts.find((p) => p.type === \"currency\")?.value ?? currency\n}\n\nfunction formatAmount(amount: number, currency: string, locale: string) {\n  return new Intl.NumberFormat(locale, {\n    style: \"decimal\",\n    minimumFractionDigits: 2,\n    maximumFractionDigits: 2,\n  }).format(amount)\n}\n\nexport type CurrencyInputProps = Omit<\n  React.ComponentProps<\"input\">,\n  \"value\" | \"defaultValue\" | \"onChange\" | \"type\" | \"size\"\n> & {\n  /** Controlled amount in major units (e.g. 1234.5 for £1,234.50). */\n  value?: number | null\n  /** Uncontrolled initial amount in major units. */\n  defaultValue?: number | null\n  /** Fires with the numeric amount (major units) whenever it changes; `null` when empty. */\n  onChange?: (value: number | null) => void\n  /** ISO 4217 currency code. Defaults to \"GBP\". */\n  currency?: string\n  /** BCP 47 locale used for the symbol and thousands-separator formatting. Defaults to \"en-GB\". */\n  locale?: string\n  invalid?: boolean\n  size?: \"sm\" | \"default\"\n}\n\n/**\n * CurrencyInput — an amount field with a currency-symbol affix and\n * thousands-separator formatting applied on blur (`Intl.NumberFormat`,\n * default `en-GB`/GBP). While focused the raw digits are editable; on blur\n * the display reformats and `onChange` reports the numeric amount in major\n * units (never minor/pence, so callers convert as needed).\n */\nfunction CurrencyInput({\n  className,\n  value,\n  defaultValue = null,\n  onChange,\n  currency = \"GBP\",\n  locale = \"en-GB\",\n  invalid,\n  disabled,\n  size = \"default\",\n  placeholder = \"0.00\",\n  onFocus,\n  onBlur,\n  ...props\n}: CurrencyInputProps) {\n  const isControlled = value !== undefined\n  const [internal, setInternal] = React.useState<number | null>(defaultValue)\n  const amount = isControlled ? value : internal\n\n  const [focused, setFocused] = React.useState(false)\n  const [draft, setDraft] = React.useState(\"\")\n\n  const display = focused\n    ? draft\n    : amount === null || amount === undefined\n      ? \"\"\n      : formatAmount(amount, currency, locale)\n\n  const symbol = React.useMemo(\n    () => currencySymbol(currency, locale),\n    [currency, locale]\n  )\n\n  function setAmount(next: number | null) {\n    if (!isControlled) setInternal(next)\n    onChange?.(next)\n  }\n\n  return (\n    <div\n      data-slot=\"currency-input\"\n      data-disabled={disabled ? true : undefined}\n      className={cn(\n        \"flex h-10 w-full min-w-0 items-center rounded-lg border border-input bg-background px-3 shadow-xs transition-[color,box-shadow] outline-none focus-within:border-ring focus-within:ring-4 focus-within:ring-ring/25 data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        size === \"sm\" && \"h-9\",\n        invalid &&\n          \"border-destructive focus-within:border-destructive focus-within:ring-destructive/25\",\n        className\n      )}\n    >\n      <span\n        aria-hidden\n        className=\"mr-1.5 flex items-center text-md font-medium text-fg-quaternary select-none\"\n      >\n        {symbol}\n      </span>\n      <input\n        type=\"text\"\n        inputMode=\"decimal\"\n        disabled={disabled}\n        aria-invalid={invalid}\n        placeholder={placeholder}\n        value={display}\n        onFocus={(e) => {\n          setFocused(true)\n          setDraft(\n            amount === null || amount === undefined ? \"\" : String(amount)\n          )\n          onFocus?.(e)\n        }}\n        onChange={(e) => {\n          const raw = onlyDigitsAndSeparator(e.target.value)\n          setDraft(raw)\n          setAmount(parseAmount(raw))\n        }}\n        onBlur={(e) => {\n          setFocused(false)\n          onBlur?.(e)\n        }}\n        className=\"h-full w-full min-w-0 flex-1 bg-transparent text-md tabular-nums outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n        {...props}\n      />\n    </div>\n  )\n}\n\nexport { CurrencyInput }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}