{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "billing-address",
  "type": "registry:ui",
  "title": "Billing Address",
  "description": "Composable address form with correct autocomplete attributes. Country and state selects are automatically linked.",
  "files": [
    {
      "path": "src/registry/ui/billing-address.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport {\n  COUNTRIES,\n  getStatesForCountry,\n  type IState,\n} from \"@/lib/billing-address-data\";\nimport { useControllableState } from \"@/lib/use-controllable-state\";\nimport { cn } from \"@/lib/utils\";\n\ninterface BillingAddressContextValue {\n  countryCode: string;\n  setCountryCode: (code: string) => void;\n  states: IState[];\n}\n\nconst BillingAddressContext =\n  React.createContext<BillingAddressContextValue | null>(null);\n\nfunction useBillingAddressContext() {\n  const context = React.useContext(BillingAddressContext);\n  if (!context) {\n    throw new Error(\n      \"BillingAddress components must be used within a BillingAddress provider\",\n    );\n  }\n  return context;\n}\n\ninterface BillingAddressProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"defaultValue\"> {\n  /** Controlled country code (ISO 3166-1 alpha-2) */\n  country?: string;\n  /** Default country code for uncontrolled mode */\n  defaultCountry?: string;\n  /** Callback when country changes */\n  onCountryChange?: (code: string) => void;\n}\n\nconst BillingAddress = React.forwardRef<HTMLDivElement, BillingAddressProps>(\n  (\n    {\n      className,\n      country,\n      defaultCountry = \"US\",\n      onCountryChange,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const [countryCode, setCountryCode] = useControllableState(\n      country,\n      defaultCountry,\n      onCountryChange,\n    );\n\n    const states = React.useMemo(\n      () => getStatesForCountry(countryCode),\n      [countryCode],\n    );\n\n    const contextValue = React.useMemo(\n      () => ({ countryCode, setCountryCode, states }),\n      [countryCode, setCountryCode, states],\n    );\n\n    return (\n      <BillingAddressContext.Provider value={contextValue}>\n        <div\n          ref={ref}\n          className={cn(\"flex flex-col gap-4\", className)}\n          {...props}\n        >\n          {children}\n        </div>\n      </BillingAddressContext.Provider>\n    );\n  },\n);\nBillingAddress.displayName = \"BillingAddress\";\n\n/**\n * Configuration for billing address fields.\n * Ensures proper browser autofill, accessibility, and UX.\n */\nconst billingFieldConfig = {\n  name: {\n    autocomplete: \"billing name\",\n    inputMode: \"text\" as const,\n    spellCheck: true,\n  },\n  line1: {\n    autocomplete: \"billing address-line1\",\n    inputMode: \"text\" as const,\n    spellCheck: true,\n  },\n  line2: {\n    autocomplete: \"billing address-line2\",\n    inputMode: \"text\" as const,\n    spellCheck: true,\n  },\n  city: {\n    autocomplete: \"billing address-level2\",\n    inputMode: \"text\" as const,\n    spellCheck: true,\n  },\n  postalCode: {\n    autocomplete: \"billing postal-code\",\n    inputMode: \"text\" as const,\n    spellCheck: false, // Disabled per UI guidelines for codes\n  },\n} as const;\n\ntype BillingAddressInputField = keyof typeof billingFieldConfig;\n\ninterface BillingAddressInputProps\n  extends Omit<\n    React.ComponentPropsWithoutRef<typeof Input>,\n    | \"autoComplete\"\n    | \"inputMode\"\n    | \"spellCheck\"\n    | \"value\"\n    | \"defaultValue\"\n    | \"onChange\"\n  > {\n  /** Address field type - automatically sets autocomplete, inputMode, and spellCheck */\n  field: BillingAddressInputField;\n  /** Controlled value */\n  value?: string;\n  /** Default value for uncontrolled mode */\n  defaultValue?: string;\n  /** Callback fired when value changes */\n  onValueChange?: (value: string) => void;\n  /** Trim whitespace on blur (default: true) */\n  trimOnBlur?: boolean;\n}\n\nconst BillingAddressInput = React.forwardRef<\n  HTMLInputElement,\n  BillingAddressInputProps\n>(\n  (\n    {\n      field,\n      className,\n      value,\n      defaultValue = \"\",\n      onValueChange,\n      trimOnBlur = true,\n      onBlur,\n      ...props\n    },\n    ref,\n  ) => {\n    const config = billingFieldConfig[field];\n    const [currentValue, setValue] = useControllableState(\n      value,\n      defaultValue,\n      onValueChange,\n    );\n\n    const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n      if (trimOnBlur) {\n        const trimmed = currentValue.trim();\n        if (trimmed !== currentValue) {\n          setValue(trimmed);\n        }\n      }\n      onBlur?.(e);\n    };\n\n    return (\n      <Input\n        ref={ref}\n        value={currentValue}\n        onChange={(e) => setValue(e.target.value)}\n        onBlur={handleBlur}\n        autoComplete={config.autocomplete}\n        inputMode={config.inputMode}\n        spellCheck={config.spellCheck}\n        className={cn(\"w-full\", className)}\n        {...props}\n      />\n    );\n  },\n);\nBillingAddressInput.displayName = \"BillingAddressInput\";\n\ninterface BillingAddressCountryProps {\n  /** Placeholder text */\n  placeholder?: string;\n  /** Additional class name */\n  className?: string;\n  /** Callback fired when country changes */\n  onValueChange?: (value: string) => void;\n  /** Name attribute for form submission */\n  name?: string;\n  /** ID attribute */\n  id?: string;\n  /** Whether the select is disabled */\n  disabled?: boolean;\n}\n\nconst BillingAddressCountry = React.forwardRef<\n  HTMLButtonElement,\n  BillingAddressCountryProps\n>(\n  (\n    {\n      placeholder = \"Select country…\",\n      className,\n      onValueChange,\n      name,\n      id,\n      disabled,\n    },\n    ref,\n  ) => {\n    const { countryCode, setCountryCode } = useBillingAddressContext();\n\n    const handleValueChange = (newValue: string) => {\n      setCountryCode(newValue);\n      onValueChange?.(newValue);\n    };\n\n    return (\n      <>\n        {name && <input type=\"hidden\" name={name} value={countryCode} />}\n        <Select\n          value={countryCode}\n          onValueChange={handleValueChange}\n          disabled={disabled}\n        >\n          <SelectTrigger ref={ref} id={id} className={cn(\"w-full\", className)}>\n            <SelectValue placeholder={placeholder} />\n          </SelectTrigger>\n          <SelectContent>\n            {COUNTRIES.map((country) => (\n              <SelectItem key={country.isoCode} value={country.isoCode}>\n                {country.name}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n      </>\n    );\n  },\n);\nBillingAddressCountry.displayName = \"BillingAddressCountry\";\n\ninterface BillingAddressStateProps {\n  /** Placeholder text */\n  placeholder?: string;\n  /** Additional class name */\n  className?: string;\n  /** Controlled value */\n  value?: string;\n  /** Default value for uncontrolled mode */\n  defaultValue?: string;\n  /** Callback fired when state changes */\n  onValueChange?: (value: string) => void;\n  /** Name attribute for form submission */\n  name?: string;\n  /** ID attribute */\n  id?: string;\n  /** Whether the input/select is disabled */\n  disabled?: boolean;\n  /** Trim whitespace on blur for input mode (default: true) */\n  trimOnBlur?: boolean;\n}\n\n/**\n * Renders a Select when states are available for the country,\n * otherwise renders an Input for free-form entry.\n *\n * Note: Ref points to a wrapper div with `display: contents` for consistent typing.\n * Use `ref.current?.querySelector('input, button')` if you need the inner element.\n */\nconst BillingAddressState = React.forwardRef<\n  HTMLDivElement,\n  BillingAddressStateProps\n>(\n  (\n    {\n      placeholder = \"State / Province…\",\n      className,\n      value,\n      defaultValue = \"\",\n      onValueChange,\n      name,\n      id,\n      disabled,\n      trimOnBlur = true,\n    },\n    ref,\n  ) => {\n    const { states, countryCode } = useBillingAddressContext();\n\n    const [currentValue, setValue] = useControllableState(\n      value,\n      defaultValue,\n      onValueChange,\n    );\n\n    const isControlled = value !== undefined;\n\n    // Reset state when country changes\n    const prevCountryRef = React.useRef(countryCode);\n    React.useEffect(() => {\n      if (prevCountryRef.current !== countryCode) {\n        prevCountryRef.current = countryCode;\n        if (!isControlled) {\n          setValue(\"\");\n        } else {\n          onValueChange?.(\"\");\n        }\n      }\n    }, [countryCode, isControlled, setValue, onValueChange]);\n\n    const handleInputBlur = () => {\n      if (trimOnBlur) {\n        const trimmed = currentValue.trim();\n        if (trimmed !== currentValue) {\n          setValue(trimmed);\n        }\n      }\n    };\n\n    // No states available → free-form input\n    if (states.length === 0) {\n      return (\n        <div ref={ref} className=\"contents\">\n          <Input\n            id={id}\n            name={name}\n            value={currentValue}\n            onChange={(e) => setValue(e.target.value)}\n            onBlur={handleInputBlur}\n            placeholder={placeholder}\n            autoComplete=\"billing address-level1\"\n            disabled={disabled}\n            className={cn(\"w-full\", className)}\n          />\n        </div>\n      );\n    }\n\n    // States available → select dropdown\n    return (\n      <div ref={ref} className=\"contents\">\n        {name && <input type=\"hidden\" name={name} value={currentValue} />}\n        <Select\n          value={currentValue}\n          onValueChange={setValue}\n          disabled={disabled}\n        >\n          <SelectTrigger id={id} className={cn(\"w-full\", className)}>\n            <SelectValue placeholder={placeholder} />\n          </SelectTrigger>\n          <SelectContent>\n            {states.map((state) => (\n              <SelectItem key={state.isoCode} value={state.isoCode}>\n                {state.name}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n      </div>\n    );\n  },\n);\nBillingAddressState.displayName = \"BillingAddressState\";\n\nexport {\n  BillingAddress,\n  BillingAddressInput,\n  BillingAddressCountry,\n  BillingAddressState,\n  billingFieldConfig,\n};\n\nexport type {\n  BillingAddressProps,\n  BillingAddressInputProps,\n  BillingAddressInputField,\n  BillingAddressCountryProps,\n  BillingAddressStateProps,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/billing-address.tsx"
    },
    {
      "path": "src/registry/lib/billing-address-data.ts",
      "content": "import { Country, type ICountry, type IState, State } from \"country-state-city\";\n\n/** All countries sorted alphabetically */\nexport const COUNTRIES: ICountry[] = Country.getAllCountries().sort((a, b) =>\n  a.name.localeCompare(b.name),\n);\n\n/** Get states/provinces for a country by ISO code */\nexport function getStatesForCountry(countryCode: string): IState[] {\n  return State.getStatesOfCountry(countryCode);\n}\n\nexport type { ICountry, IState };\n",
      "type": "registry:lib",
      "target": "lib/billing-address-data.ts"
    },
    {
      "path": "src/registry/lib/use-controllable-state.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Hook for components that support both controlled and uncontrolled modes.\n *\n * @param controlledValue - The controlled value (if provided)\n * @param defaultValue - The default value for uncontrolled mode\n * @param onChange - Callback fired when value changes\n * @returns A tuple of [currentValue, setValue]\n *\n * @example\n * ```tsx\n * interface Props {\n *   value?: string;\n *   defaultValue?: string;\n *   onValueChange?: (value: string) => void;\n * }\n *\n * function Input({ value, defaultValue = \"\", onValueChange }: Props) {\n *   const [currentValue, setValue] = useControllableState(\n *     value,\n *     defaultValue,\n *     onValueChange,\n *   );\n *\n *   return (\n *     <input\n *       value={currentValue}\n *       onChange={(e) => setValue(e.target.value)}\n *     />\n *   );\n * }\n * ```\n */\nexport function useControllableState<T>(\n  controlledValue: T | undefined,\n  defaultValue: T,\n  onChange?: (value: T) => void,\n): [T, (value: T) => void] {\n  const [internalValue, setInternalValue] = React.useState(defaultValue);\n  const isControlled = controlledValue !== undefined;\n  const value = isControlled ? controlledValue : internalValue;\n\n  const setValue = React.useCallback(\n    (newValue: T) => {\n      if (!isControlled) {\n        setInternalValue(newValue);\n      }\n      onChange?.(newValue);\n    },\n    [isControlled, onChange],\n  );\n\n  return [value, setValue];\n}\n",
      "type": "registry:lib",
      "target": "lib/use-controllable-state.ts"
    },
    {
      "path": "src/registry/lib/utils.ts",
      "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n",
      "type": "registry:lib",
      "target": "lib/utils.ts"
    }
  ],
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "input",
    "select"
  ]
}