{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "phone-input",
  "title": "Phone Input",
  "description": "A country-code select paired with a formatted national number field, exposing the full E.164 value.",
  "registryDependencies": [
    "@nx-ui/utils",
    "@nx-ui/select"
  ],
  "files": [
    {
      "path": "components/ui/phone-input.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\n\n/** A single selectable dial-code entry (flag emoji + ISO code + dial code). */\nexport type PhoneCountry = {\n  /** ISO 3166-1 alpha-2 code, e.g. \"GB\". Used as the select value. */\n  iso: string\n  /** Display name, e.g. \"United Kingdom\". */\n  name: string\n  /** Dial code digits, no plus sign, e.g. \"44\". */\n  dial: string\n  /** Flag emoji shown in the trigger and list. */\n  flag: string\n}\n\n/**\n * Curated common-country list for UK-facing Nexus ReGen deployments. Pass\n * `countries` to extend or replace this set (e.g. for other regions).\n */\nexport const DEFAULT_PHONE_COUNTRIES: PhoneCountry[] = [\n  { iso: \"GB\", name: \"United Kingdom\", dial: \"44\", flag: \"🇬🇧\" },\n  { iso: \"IE\", name: \"Ireland\", dial: \"353\", flag: \"🇮🇪\" },\n  { iso: \"US\", name: \"United States\", dial: \"1\", flag: \"🇺🇸\" },\n  { iso: \"FR\", name: \"France\", dial: \"33\", flag: \"🇫🇷\" },\n  { iso: \"DE\", name: \"Germany\", dial: \"49\", flag: \"🇩🇪\" },\n  { iso: \"NL\", name: \"Netherlands\", dial: \"31\", flag: \"🇳🇱\" },\n  { iso: \"ES\", name: \"Spain\", dial: \"34\", flag: \"🇪🇸\" },\n  { iso: \"IT\", name: \"Italy\", dial: \"39\", flag: \"🇮🇹\" },\n  { iso: \"BE\", name: \"Belgium\", dial: \"32\", flag: \"🇧🇪\" },\n  { iso: \"PL\", name: \"Poland\", dial: \"48\", flag: \"🇵🇱\" },\n  { iso: \"AU\", name: \"Australia\", dial: \"61\", flag: \"🇦🇺\" },\n  { iso: \"NZ\", name: \"New Zealand\", dial: \"64\", flag: \"🇳🇿\" },\n]\n\nfunction onlyDigits(value: string) {\n  return value.replace(/\\D/g, \"\")\n}\n\n/** Light generic grouping (3-4-4…) so long strings of digits stay readable. */\nfunction formatNational(digits: string) {\n  const groups: string[] = []\n  let rest = digits\n  if (rest.length <= 5) return rest\n  groups.push(rest.slice(0, 4))\n  rest = rest.slice(4)\n  while (rest.length > 0) {\n    groups.push(rest.slice(0, 3))\n    rest = rest.slice(3)\n  }\n  return groups.join(\" \")\n}\n\n/** Finds the country whose dial code is the longest match for an E.164 value. */\nfunction matchCountry(e164: string, countries: PhoneCountry[]) {\n  const digits = onlyDigits(e164)\n  let best: PhoneCountry | undefined\n  for (const country of countries) {\n    if (digits.startsWith(country.dial)) {\n      if (!best || country.dial.length > best.dial.length) best = country\n    }\n  }\n  return best\n}\n\nexport type PhoneInputProps = Omit<\n  React.ComponentProps<\"input\">,\n  \"value\" | \"defaultValue\" | \"onChange\" | \"size\"\n> & {\n  /** Full E.164 value, e.g. \"+442012345678\" (controlled). */\n  value?: string\n  /** Full E.164 value for uncontrolled usage. */\n  defaultValue?: string\n  /** Fires with the full E.164 value whenever the country or number changes. */\n  onChange?: (value: string) => void\n  /** Curated list of selectable countries. Defaults to `DEFAULT_PHONE_COUNTRIES`. */\n  countries?: PhoneCountry[]\n  /** ISO code to preselect when there's no parsed value. Defaults to \"GB\". */\n  defaultCountry?: string\n  invalid?: boolean\n  size?: \"sm\" | \"default\"\n}\n\n/**\n * PhoneInput — a country-code `Select` (flag + dial code) composed with a\n * formatted national-number `Input`, exposing the full E.164 value via\n * `onChange`. Works controlled (`value`) or uncontrolled (`defaultValue`).\n */\nfunction PhoneInput({\n  className,\n  value,\n  defaultValue,\n  onChange,\n  countries = DEFAULT_PHONE_COUNTRIES,\n  defaultCountry = \"GB\",\n  invalid,\n  disabled,\n  size = \"default\",\n  placeholder = \"20 1234 5678\",\n  id,\n  name,\n  ...props\n}: PhoneInputProps) {\n  const isControlled = value !== undefined\n\n  const initial = React.useMemo(() => {\n    const source = isControlled ? value : defaultValue\n    if (source) {\n      const matched = matchCountry(source, countries)\n      if (matched) {\n        return {\n          country: matched,\n          national: onlyDigits(source).slice(matched.dial.length),\n        }\n      }\n    }\n    return {\n      country:\n        countries.find((c) => c.iso === defaultCountry) ?? countries[0],\n      national: \"\",\n    }\n  }, [isControlled, value, defaultValue, countries, defaultCountry])\n\n  const [country, setCountry] = React.useState<PhoneCountry>(initial.country)\n  const [national, setNational] = React.useState(initial.national)\n\n  // Keep local state in sync when a controlled value changes externally.\n  React.useEffect(() => {\n    if (!isControlled || value === undefined) return\n    const matched = matchCountry(value, countries) ?? country\n    setCountry(matched)\n    setNational(onlyDigits(value).slice(matched.dial.length))\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [value])\n\n  function emit(nextCountry: PhoneCountry, nextNational: string) {\n    onChange?.(`+${nextCountry.dial}${nextNational}`)\n  }\n\n  function handleCountryChange(iso: string | null) {\n    if (!iso) return\n    const next = countries.find((c) => c.iso === iso) ?? country\n    setCountry(next)\n    emit(next, national)\n  }\n\n  function handleNationalChange(raw: string) {\n    const digits = onlyDigits(raw)\n    setNational(digits)\n    emit(country, digits)\n  }\n\n  return (\n    <div\n      data-slot=\"phone-input\"\n      data-disabled={disabled ? true : undefined}\n      className={cn(\n        \"flex w-full min-w-0 items-stretch gap-2 data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        className\n      )}\n    >\n      <Select\n        value={country.iso}\n        onValueChange={handleCountryChange}\n        disabled={disabled}\n      >\n        <SelectTrigger\n          size={size}\n          aria-label=\"Country code\"\n          className=\"w-28 shrink-0 gap-1 pl-2.5\"\n        >\n          <SelectValue>\n            <span aria-hidden className=\"text-md leading-none\">\n              {country.flag}\n            </span>\n            <span className=\"text-muted-foreground\">+{country.dial}</span>\n          </SelectValue>\n        </SelectTrigger>\n        <SelectContent>\n          {countries.map((c) => (\n            <SelectItem key={c.iso} value={c.iso}>\n              <span aria-hidden className=\"text-md leading-none\">\n                {c.flag}\n              </span>\n              {c.name}\n              <span className=\"text-muted-foreground\">+{c.dial}</span>\n            </SelectItem>\n          ))}\n        </SelectContent>\n      </Select>\n\n      <input\n        id={id}\n        name={name}\n        type=\"tel\"\n        inputMode=\"tel\"\n        autoComplete=\"tel-national\"\n        disabled={disabled}\n        aria-invalid={invalid}\n        placeholder={placeholder}\n        value={formatNational(national)}\n        onChange={(e) => handleNationalChange(e.target.value)}\n        className={cn(\n          \"h-10 w-full min-w-0 flex-1 rounded-lg border border-input bg-background px-3 text-md shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-4 focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-4 aria-invalid:ring-destructive/25\",\n          size === \"sm\" && \"h-9\"\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nexport { PhoneInput }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}