{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "billui",
  "homepage": "https://billui.com",
  "items": [
    {
      "$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"
        }
      ],
      "registryDependencies": [
        "input",
        "select",
        "https://billui.com/r/billing-address-data.json",
        "https://billui.com/r/use-controllable-state.json",
        "https://billui.com/r/utils.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "card-icons",
      "type": "registry:ui",
      "title": "Card Icons",
      "description": "Card Icons component",
      "files": [
        {
          "path": "src/registry/ui/card-icons.tsx",
          "content": "import { CreditCard } from \"lucide-react\";\n\nexport type CardBrand =\n  | \"visa\"\n  | \"mastercard\"\n  | \"amex\"\n  | \"discover\"\n  | \"diners\"\n  | \"jcb\"\n  | \"unionpay\"\n  | \"unknown\";\n\nexport const brandLabels: Record<CardBrand, string> = {\n  visa: \"Visa\",\n  mastercard: \"Mastercard\",\n  amex: \"American Express\",\n  discover: \"Discover\",\n  diners: \"Diners Club\",\n  jcb: \"JCB\",\n  unionpay: \"UnionPay\",\n  unknown: \"Card\",\n};\n\nexport const CardBrandIcons: Record<\n  CardBrand,\n  React.FC<{ className?: string }>\n> = {\n  visa: ({ className }) => (\n    <svg\n      width=\"780\"\n      height=\"500\"\n      viewBox=\"0 0 780 500\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      aria-label=\"Visa\"\n    >\n      <path\n        d=\"M654.223 279.812L637.598 202.107L609.685 279.812H654.223Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M41.458 0H736.038C758.935 0 777.497 18.99 777.497 42.426V457.076C777.497 480.508 758.926 499.502 736.038 499.502H41.458C18.562 499.502 0 480.513 0 457.076V42.426C0 18.994 18.571 0 41.458 0ZM399.884 212.007C399.884 167.144 441.738 142.862 488.573 142.862C518.568 142.862 539.837 152.04 539.837 152.04L530.378 195.765C530.378 195.765 510.831 185.865 486.872 185.865C465.637 185.865 455.402 195.206 455.402 203.732C455.402 213.307 467.277 219.61 481.547 227.184C503.029 238.586 529.94 252.869 529.94 285.535C529.94 331.166 489.648 356.64 438.442 356.64C403.895 356.64 377.873 345.141 377.873 345.141L387.539 299.87C387.539 299.87 410.127 313.482 440.35 313.482C461.645 313.482 474.114 303.74 474.114 292.857C474.114 282.136 461.343 275.251 446.395 267.192C425.306 255.822 399.884 242.116 399.884 212.007ZM58.9453 152.762L60.0794 146.162H149.49C162.401 146.162 172.949 153.954 175.032 167.612L197.739 286.953L253.385 146.162H309.469L222.868 353.237H166.655L124.721 191.537C120.872 176.619 119.245 171.815 96.395 163.565C78.6488 156.369 58.9453 152.762 58.9453 152.762ZM288.025 353.237L332.021 146.162H385.348L341.506 353.237H288.025ZM605.561 165.137C610.575 153.069 617.408 146.162 630.304 146.162H675.383L718.555 353.237H669.068L662.47 321.887H594.143L583.292 353.237H527.208L605.561 165.137Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  mastercard: ({ className }) => (\n    <svg\n      width=\"780\"\n      height=\"500\"\n      viewBox=\"0 0 780 500\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      aria-label=\"Mastercard\"\n    >\n      <path\n        d=\"M513.444 443.897C513.444 435.693 518.725 428.951 527.434 428.951C535.637 428.951 541.424 435.244 541.424 443.897C541.424 452.1 535.637 458.842 527.434 458.842C518.781 458.336 513.444 452.043 513.444 443.897Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M392.42 428.446C398.713 428.446 403.039 432.322 403.994 439.514H379.891C380.846 433.278 385.172 428.446 392.42 428.446Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M606.543 443.897C606.543 435.693 611.825 428.951 620.533 428.951C628.737 428.951 634.524 435.244 634.524 443.897C634.524 452.1 628.737 458.842 620.533 458.842C611.825 458.336 606.543 452.043 606.543 443.897Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M230.381 443.897C230.381 435.693 235.662 428.951 244.371 428.951C252.574 428.951 258.361 435.244 258.361 443.897C258.361 452.1 252.574 458.842 244.371 458.842C235.662 458.336 230.381 452.043 230.381 443.897Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M736.038 0H41.458C18.571 0 0 18.975 0 42.383V456.613C0 480.026 18.562 498.997 41.458 498.997H736.038C758.927 498.997 777.497 480.021 777.497 456.613V42.383C777.497 18.971 758.935 0 736.038 0ZM211.053 467.045V438.109C211.053 427.041 204.311 419.793 192.736 419.793C186.949 419.793 180.657 421.703 176.33 427.996C172.959 422.715 168.127 419.793 160.879 419.793C156.047 419.793 151.215 421.254 147.395 426.535V420.748H137.281V467.045H147.395V441.481C147.395 433.278 151.721 429.401 158.463 429.401C165.205 429.401 168.577 433.727 168.577 441.481V467.045H178.69V441.481C178.69 433.278 183.522 429.401 189.759 429.401C196.501 429.401 199.872 433.727 199.872 441.481V467.045H211.053ZM361.012 420.748H344.662V406.758H334.549V420.748H325.391V429.906H334.549V451.145C334.549 461.764 338.875 468 350.449 468C354.776 468 359.608 466.539 362.979 464.629L360.057 455.92C357.135 457.831 353.764 458.336 351.348 458.336C346.516 458.336 344.606 455.415 344.606 450.639V429.906H361.012V420.748ZM446.92 419.737C441.133 419.737 437.256 422.658 434.84 426.479V420.692H424.727V466.989H434.84V440.919C434.84 433.221 438.211 428.839 444.504 428.839C446.414 428.839 448.83 429.345 450.797 429.794L453.718 420.13C451.696 419.737 448.83 419.737 446.92 419.737ZM317.187 424.569C312.355 421.198 305.613 419.737 298.365 419.737C286.791 419.737 279.094 425.524 279.094 434.682C279.094 442.38 284.881 446.762 294.994 448.167L299.826 448.672C305.108 449.628 308.029 451.088 308.029 453.504C308.029 456.875 304.152 459.291 297.41 459.291C290.668 459.291 285.33 456.875 281.959 454.46L277.127 462.157C282.409 466.034 289.657 467.944 296.904 467.944C310.389 467.944 318.143 461.651 318.143 452.999C318.143 444.796 311.85 440.469 302.242 439.008L297.41 438.503C293.084 437.997 289.713 437.042 289.713 434.176C289.713 430.805 293.084 428.895 298.421 428.895C304.209 428.895 309.996 431.311 312.917 432.772L317.187 424.569ZM586.26 419.737C580.473 419.737 576.596 422.658 574.18 426.479V420.692H564.067V466.989H574.18V440.919C574.18 433.221 577.551 428.839 583.844 428.839C585.755 428.839 588.17 429.345 590.137 429.794L593.059 420.242C591.092 419.737 588.227 419.737 586.26 419.737ZM457.033 443.897C457.033 457.887 466.697 468 481.643 468C488.385 468 493.217 466.539 498.049 462.719L493.217 454.516C489.34 457.437 485.519 458.842 481.137 458.842C472.934 458.842 467.147 453.055 467.147 443.897C467.147 435.188 472.934 429.401 481.137 428.951C485.463 428.951 489.34 430.412 493.217 433.278L498.049 425.074C493.217 421.198 488.385 419.793 481.643 419.793C466.697 419.737 457.033 429.906 457.033 443.897ZM550.582 420.748H540.469V426.535C537.098 422.209 532.266 419.793 525.973 419.793C512.938 419.793 502.825 429.906 502.825 443.897C502.825 457.887 512.938 468 525.973 468C532.715 468 537.547 465.584 540.469 461.258V467.045H550.582V420.748ZM392.42 419.737C378.935 419.737 369.271 429.401 369.271 443.84C369.271 458.336 378.935 467.944 392.926 467.944C399.668 467.944 406.41 466.034 411.748 461.651L406.916 454.403C403.039 457.325 398.207 459.235 393.431 459.235C387.139 459.235 380.902 456.314 379.441 448.167H413.658V444.29C414.108 429.401 405.399 419.737 392.42 419.737ZM643.682 402.432H633.568V426.535C630.197 422.209 625.365 419.793 619.073 419.793C606.037 419.793 595.924 429.906 595.924 443.897C595.924 457.887 606.037 468 619.073 468C625.815 468 630.647 465.584 633.568 461.258V467.045H643.682V402.432ZM267.969 420.748H257.855V426.535C254.484 422.209 249.652 419.793 243.36 419.793C230.325 419.793 220.211 429.906 220.211 443.897C220.211 457.887 230.325 468 243.36 468C250.102 468 254.934 465.584 257.855 461.258V467.045H267.969V420.748ZM282.987 378.458C187.36 378.458 109.633 300.855 109.633 205.104C109.633 109.728 187.36 32 282.987 32.1252C317.032 32.1252 350.451 42.2635 378.738 61.0383C288.119 135.261 287.493 275.321 378.738 349.544C350.451 368.194 317.032 378.458 282.987 378.458ZM390.003 341.283C478.245 272.443 477.869 138.265 390.003 69.0489C302.137 138.265 301.762 272.568 390.003 341.283ZM497.019 32C592.646 32 670.373 109.728 670.373 205.104C670.373 300.855 592.646 378.458 497.019 378.332C463.225 378.332 429.555 368.194 401.268 349.419C492.889 275.572 491.512 135.136 401.268 60.9132C429.555 42.2636 462.974 32 497.019 32Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  amex: ({ className }) => (\n    <svg\n      width=\"780\"\n      height=\"500\"\n      viewBox=\"0 0 780 500\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      aria-label=\"American Express\"\n    >\n      <path\n        d=\"m199.87 124.06l-26.311 63.322-28.644-63.323h-35.722v88.379l-37.224-88.378h-32.517l-39.193 93.344h23.77l8.496-20.82h45.57l8.41 20.82h44.515v-73.171l31.819 73.171h19.218l31.735-73.106 0.083 73.106h22.435v-93.344h-36.44zm-129.68 53.068h-29.875l14.896-37.213 14.979 37.211v2e-3z\"\n        fill=\"currentColor\"\n      />\n      <polygon\n        points=\"325.74 217.41 252.62 217.41 252.62 124.06 325.74 124.06 325.74 143.5 274.51 143.5 274.51 160.33 324.51 160.33 324.51 179.46 274.51 179.46 274.51 198.1 325.74 198.1 325.74 217.41\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"m428.82 149.2c0-10.412-4.152-16.5-11.076-20.669-7.076-4.17-15.316-4.471-26.502-4.471h-50.34v93.344h21.884v-33.952h23.495c7.88 0 12.755 0.713 15.952 3.996 3.982 4.491 3.455 12.569 3.455 18.186l0.084 11.771h22.075v-18.338c0-8.336-0.534-12.484-3.624-17.126-1.947-2.72-6.037-6.002-10.827-7.861 5.679-2.31 15.424-9.998 15.424-24.88zm-28.622 13.109c-3.007 1.861-6.564 1.923-10.826 1.923h-26.588v-20.734h26.95c3.813 0 7.795 0.176 10.378 1.685 2.84 1.36 4.597 4.253 4.597 8.251 0 4.08-1.672 7.363-4.511 8.875z\"\n        fill=\"currentColor\"\n      />\n      <rect\n        x=\"441.52\"\n        y=\"124.06\"\n        width=\"22.334\"\n        height=\"93.344\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"m700.52 124.07v65.009l-38.474-65.009h-33.323v88.29l-37.056-88.29h-32.794l-30.847 73.407h-9.83c-5.761 0-11.884-1.121-15.253-4.813-4.068-4.711-6.02-11.922-6.02-21.922 0-9.785 2.562-17.213 6.293-21.144 4.344-4.318 8.854-5.53 16.842-5.53h20.743v-20h-21.189c-15.083 0-26.163 3.436-33.238 10.885-9.407 10-11.798 22.656-11.798 36.499 0 16.975 4 27.71 11.692 35.637 7.629 7.925 21.098 10.323 31.737 10.323h25.61l8.263-20.82h45.465l8.518 20.82h44.573v-70.059l41.485 70.059h31.016l1e-3 1e-3v-93.342h-22.416zm-110.64 53.064h-30.209l15.062-37.213 15.147 37.213z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"m387.16 284.61h-69.936l-27.839 30.603-26.928-30.603h-88.027v93.366h86.693l28.007-30.909 26.951 30.908h42.54v-31.315h27.31c19.132 0 38.113-5.355 38.113-31.292-1e-3 -25.857-19.516-30.758-36.884-30.758zm-137.12 73.909h-53.811v-18.577h48.049v-19.05h-48.049v-16.974h54.872l23.939 27.208-25 27.393zm86.78 10.966l-33.603-38.032 33.603-36.823v74.855zm50.082-41.789h-28.285v-23.776h28.539c7.902 0 13.386 3.284 13.386 11.448 1e-3 8.075-5.23 12.328-13.64 12.328z\"\n        fill=\"currentColor\"\n      />\n      <polygon\n        points=\"534.57 284.61 607.62 284.61 607.62 303.92 556.37 303.92 556.37 320.89 606.37 320.89 606.37 339.94 556.37 339.94 556.37 358.52 607.62 358.6 607.62 377.98 534.57 377.98\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"m506.92 334.59c5.761-2.331 15.511-9.936 15.513-24.838 0-10.652-4.344-16.479-11.252-20.734-7.183-3.906-15.253-4.404-26.334-4.404h-50.53v93.365h21.993v-34.1h23.389c7.985 0 12.861 0.799 16.059 4.144 4.067 4.342 3.537 12.658 3.537 18.276v11.681h21.973v-18.509c-0.088-8.229-0.535-12.484-3.625-17.043-1.87-2.719-5.852-6.006-10.723-7.838zm-13.218-11.619c-2.928 1.771-6.549 1.923-10.809 1.923h-26.588v-20.971h26.951c3.896 0 7.796 0.085 10.445 1.688 2.836 1.512 4.532 4.403 4.532 8.399 1e-3 3.995-1.695 7.212-4.531 8.961z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"m691.2 328.73c4.262 4.496 6.547 10.173 6.547 19.782 0 20.086-12.312 29.461-34.383 29.461h-42.629v-20.021h42.457c4.152 0 7.096-0.559 8.939-2.311 1.506-1.445 2.585-3.543 2.585-6.09 0-2.721-1.167-4.88-2.67-6.174-1.674-1.426-3.982-2.072-7.794-2.072-20.468-0.713-46.101 0.646-46.101-28.9 0-13.542 8.347-27.796 31.292-27.796h43.877v19.872h-40.148c-3.98 0-6.568 0.151-8.77 1.686-2.396 1.513-3.286 3.757-3.286 6.718 0 3.522 2.035 5.92 4.789 6.954 2.309 0.818 4.79 1.059 8.519 1.059l11.78 0.324c11.884 0.295 20.039 2.391 24.996 7.508z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"m729.75 307.08c2.228-1.54 4.874-1.692 8.857-1.692h39.889v-261.75c0-23.553-18.586-42.638-41.512-42.638h-695.48c-22.917 0-41.512 19.089-41.512 42.638v124.31l26.505-62.085h57.077l7.351 15.422v-15.421h66.925l14.641 33.726 14.256-33.726h212.48c9.657 0 18.345 1.84 24.718 7.624v-7.624h58.159v7.624c10.018-5.611 22.417-7.625 36.524-7.625h84.278l7.819 15.422v-15.422h62.392l8.515 15.422v-15.421h60.804v130.58h-61.438l-11.611-19.753v19.753h-76.672l-8.327-20.795h-18.877l-8.515 20.795h-39.787c-15.612 0-27.478-3.767-35.106-7.947v7.947h-94.573v-29.631c0-4.182-0.723-4.417-3.201-4.504h-3.537l0.084 34.136h-182.86v-16.137l-6.568 16.202h-38.196l-6.568-15.965v15.898h-73.577l-8.41-20.795h-18.877l-8.432 20.795h-37.395v1e-3h-3e-3v223.92c0 23.553 18.585 42.637 41.512 42.637h695.48c22.917 0 41.512-19.089 41.512-42.638v-93.235l-1e-3 1e-3v24.343c-8.77 4.331-20.294 6.022-32.095 6.022h-56.712v-8.361c-6.569 5.39-18.436 8.361-29.787 8.361h-178.79v-30.198c0-3.703-0.357-3.854-3.979-3.854h-2.84v34.055h-58.854v-35.264c-9.852 4.354-21.019 4.744-30.486 4.594h-7.016v30.738l-71.355-0.065-17.542-20.313-18.624 20.313h-115.76v-130.67h117.98l16.928 20 18.071-20h78.981c9.13 0 24.11 0.978 30.846 7.648v-7.648h70.57c6.632 0 21 1.368 29.515 7.648v-7.648h106.99v7.648c5.319-5.219 16.564-7.648 26.146-7.648h59.913v7.648c6.288-4.652 15.168-7.648 27.391-7.648h40.507v18.633h-43.596c-22.968 0-31.403 14.297-31.403 27.88 0 29.635 25.64 28.271 46.189 28.986 3.812 0 6.122 0.648 7.711 2.079 1.609 1.298 2.674 3.463 2.674 6.192 0.017 2.319-0.924 4.538-2.588 6.107-1.76 1.758-4.681 2.317-8.857 2.317h-42.096v20.082h42.269c14.023 0 24.383-4.03 29.699-11.979v-1e-3h1e-3v-35.092c-0.61-0.803-1.145-1.604-2.03-2.318-4.872-5.131-12.861-7.235-24.831-7.537l-11.862-0.324c-3.646 0-6.126-0.242-8.435-1.062-2.836-1.039-4.785-3.443-4.785-6.975 0-2.971 0.888-5.221 3.197-6.737z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  discover: ({ className }) => (\n    // TODO: Copiar SVG de discover\n    <svg\n      viewBox=\"0 0 780 500\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      aria-label=\"Discover\"\n    ></svg>\n  ),\n  diners: ({ className }) => (\n    // TODO: Copiar SVG de diners\n    <svg\n      viewBox=\"0 0 780 500\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      aria-label=\"Diners Club\"\n    ></svg>\n  ),\n  jcb: ({ className }) => (\n    // TODO: Copiar SVG de jcb\n    <svg\n      viewBox=\"0 0 780 500\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      aria-label=\"JCB\"\n    ></svg>\n  ),\n  unionpay: ({ className }) => (\n    // TODO: Copiar SVG de unionpay\n    <svg\n      viewBox=\"0 0 780 500\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      aria-label=\"UnionPay\"\n    ></svg>\n  ),\n  unknown: ({ className }) => (\n    <CreditCard className={className} aria-label=\"Card\" />\n  ),\n};\n",
          "type": "registry:ui",
          "target": "components/ui/card-icons.tsx"
        }
      ],
      "dependencies": [
        "lucide-react"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "card-input",
      "type": "registry:ui",
      "title": "Card Input",
      "description": "Native formatted card inputs with auto-formatting, brand detection, and masking.",
      "files": [
        {
          "path": "src/registry/ui/card-input.tsx",
          "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useControllableState } from \"@/lib/use-controllable-state\";\nimport { cn } from \"@/lib/utils\";\nimport { type CardBrand, CardBrandIcons } from \"./card-icons\";\n\ninterface CardInputContextValue {\n  brand: CardBrand;\n  setBrand: (brand: CardBrand) => void;\n}\n\nconst CardInputContext = React.createContext<CardInputContextValue | null>(\n  null,\n);\n\nfunction useCardInputContext() {\n  return React.useContext(CardInputContext);\n}\n\nfunction detectCardBrand(cardNumber: string): CardBrand {\n  const number = cardNumber.replace(/\\s/g, \"\");\n\n  if (!number) return \"unknown\";\n\n  // Visa: starts with 4\n  if (/^4/.test(number)) return \"visa\";\n\n  // Mastercard: starts with 51-55 or 2221-2720\n  if (/^5[1-5]/.test(number) || /^2[2-7]/.test(number)) return \"mastercard\";\n\n  // Amex: starts with 34 or 37\n  if (/^3[47]/.test(number)) return \"amex\";\n\n  // Discover: starts with 6011, 644-649, 65\n  if (/^6011|^64[4-9]|^65/.test(number)) return \"discover\";\n\n  // Diners Club: starts with 36, 38, or 300-305\n  if (/^36|^38|^30[0-5]/.test(number)) return \"diners\";\n\n  // JCB: starts with 35\n  if (/^35/.test(number)) return \"jcb\";\n\n  // UnionPay: starts with 62\n  if (/^62/.test(number)) return \"unionpay\";\n\n  return \"unknown\";\n}\n\nfunction validateLuhn(cardNumber: string): boolean {\n  const number = cardNumber.replace(/\\s/g, \"\");\n\n  if (!number || !/^\\d+$/.test(number)) return false;\n\n  let sum = 0;\n  let isEven = false;\n\n  // Loop through digits from right to left\n  for (let i = number.length - 1; i >= 0; i--) {\n    let digit = parseInt(number[i], 10);\n\n    if (isEven) {\n      digit *= 2;\n      if (digit > 9) {\n        digit -= 9;\n      }\n    }\n\n    sum += digit;\n    isEven = !isEven;\n  }\n\n  return sum % 10 === 0;\n}\n\nfunction isCardNumberComplete(cardNumber: string, brand: CardBrand): boolean {\n  const number = cardNumber.replace(/\\s/g, \"\");\n  const maxLength = getCardMaxLength(brand);\n  return number.length === maxLength;\n}\n\nfunction validateCardNumber(cardNumber: string): {\n  isValid: boolean;\n  isComplete: boolean;\n  brand: CardBrand;\n} {\n  const brand = detectCardBrand(cardNumber);\n  const isComplete = isCardNumberComplete(cardNumber, brand);\n  const isValid = isComplete && validateLuhn(cardNumber);\n\n  return { isValid, isComplete, brand };\n}\n\nfunction getCardMaxLength(brand: CardBrand): number {\n  // Amex has 15 digits, others have 16\n  return brand === \"amex\" ? 15 : 16;\n}\n\nfunction getCvcMaxLength(brand: CardBrand): number {\n  // Amex has 4-digit CVC, others have 3\n  return brand === \"amex\" ? 4 : 3;\n}\n\nfunction formatCardNumber(value: string, brand: CardBrand): string {\n  const number = value.replace(/\\D/g, \"\");\n  const maxLength = getCardMaxLength(brand);\n  const truncated = number.slice(0, maxLength);\n\n  // Amex format: 4-6-5\n  if (brand === \"amex\") {\n    const parts = [\n      truncated.slice(0, 4),\n      truncated.slice(4, 10),\n      truncated.slice(10, 15),\n    ].filter(Boolean);\n    return parts.join(\" \");\n  }\n\n  // Standard format: 4-4-4-4\n  const parts = truncated.match(/.{1,4}/g) || [];\n  return parts.join(\" \");\n}\n\nfunction formatExpiry(value: string): string {\n  const number = value.replace(/\\D/g, \"\");\n  const truncated = number.slice(0, 4);\n\n  if (truncated.length >= 2) {\n    return `${truncated.slice(0, 2)}/${truncated.slice(2)}`;\n  }\n  return truncated;\n}\n\nfunction validateExpiry(expiry: string): {\n  isValid: boolean;\n  isComplete: boolean;\n  isExpired: boolean;\n} {\n  const cleaned = expiry.replace(/\\D/g, \"\");\n\n  // Not complete until we have 4 digits (MMYY)\n  if (cleaned.length < 4) {\n    return { isValid: false, isComplete: false, isExpired: false };\n  }\n\n  const month = parseInt(cleaned.slice(0, 2), 10);\n  const year = parseInt(cleaned.slice(2, 4), 10);\n\n  // Invalid month\n  if (month < 1 || month > 12) {\n    return { isValid: false, isComplete: true, isExpired: false };\n  }\n\n  // Check if expired\n  const now = new Date();\n  const currentYear = now.getFullYear() % 100; // Get last 2 digits\n  const currentMonth = now.getMonth() + 1; // 1-indexed\n\n  const isExpired =\n    year < currentYear || (year === currentYear && month < currentMonth);\n\n  return {\n    isValid: !isExpired,\n    isComplete: true,\n    isExpired,\n  };\n}\n\ninterface CardInputGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n  error?: boolean;\n}\n\nconst CardInputGroup = React.forwardRef<HTMLDivElement, CardInputGroupProps>(\n  ({ className, children, error, ...props }, ref) => {\n    const [brand, setBrand] = React.useState<CardBrand>(\"unknown\");\n\n    return (\n      <CardInputContext.Provider value={{ brand, setBrand }}>\n        <div\n          ref={ref}\n          className={cn(\n            \"flex items-center divide-x divide-border overflow-hidden rounded-lg border bg-background transition-all focus-within:ring-2 focus-within:ring-ring focus-within:border-ring\",\n            error &&\n              \"border-destructive focus-within:border-destructive focus-within:ring-destructive/20\",\n            className,\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </CardInputContext.Provider>\n    );\n  },\n);\nCardInputGroup.displayName = \"CardInputGroup\";\n\ninterface CardNumberInputProps\n  extends Omit<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    \"onChange\" | \"value\" | \"defaultValue\"\n  > {\n  /** The current value (controlled) */\n  value?: string;\n  /** The default value (uncontrolled) */\n  defaultValue?: string;\n  /** Callback fired when the value changes */\n  onValueChange?: (value: string) => void;\n  /** Callback fired when the card brand is detected */\n  onBrandChange?: (brand: CardBrand) => void;\n  /** Callback fired when validation state changes */\n  onValidationChange?: (validation: {\n    isValid: boolean;\n    isComplete: boolean;\n  }) => void;\n}\n\nconst CardNumberInput = React.forwardRef<\n  HTMLInputElement,\n  CardNumberInputProps\n>(\n  (\n    {\n      className,\n      id,\n      name,\n      value,\n      defaultValue = \"\",\n      onValueChange,\n      onBrandChange,\n      onValidationChange,\n      placeholder = \"1234 1234 1234 1234\",\n      disabled,\n      ...props\n    },\n    ref,\n  ) => {\n    const context = useCardInputContext();\n    const generatedId = React.useId();\n    const inputId = id ?? generatedId;\n    const errorId = `${inputId}-error`;\n    const inputName = name ?? \"cc-number\";\n\n    const [currentValue, setValue] = useControllableState(\n      value,\n      defaultValue,\n      onValueChange,\n    );\n\n    const [internalBrand, setInternalBrand] =\n      React.useState<CardBrand>(\"unknown\");\n    const [validationState, setValidationState] = React.useState<{\n      isComplete: boolean;\n      isValid: boolean;\n    }>({ isComplete: false, isValid: false });\n\n    // Use context brand if available, otherwise use internal state\n    const brand = context?.brand ?? internalBrand;\n    const BrandIcon = CardBrandIcons[brand];\n\n    const hasError = validationState.isComplete && !validationState.isValid;\n    const isValidComplete =\n      validationState.isComplete && validationState.isValid;\n\n    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n      const rawValue = e.target.value;\n      // Detect brand first to know how to format\n      const detectedBrand = detectCardBrand(rawValue);\n      // Format (and truncate) the value\n      const formatted = formatCardNumber(rawValue, detectedBrand);\n      // Validate with the formatted/truncated value to ensure correct state\n      const validation = validateCardNumber(formatted);\n\n      // Update both context (if available) and internal state\n      context?.setBrand(validation.brand);\n      setInternalBrand(validation.brand);\n      setValidationState({\n        isComplete: validation.isComplete,\n        isValid: validation.isValid,\n      });\n      onBrandChange?.(validation.brand);\n      onValidationChange?.({\n        isValid: validation.isValid,\n        isComplete: validation.isComplete,\n      });\n\n      setValue(formatted);\n    };\n\n    return (\n      <div\n        className={cn(\n          \"relative flex min-w-[200px] flex-1 items-center\",\n          className,\n        )}\n      >\n        <input\n          ref={ref}\n          id={inputId}\n          name={inputName}\n          type=\"text\"\n          inputMode=\"numeric\"\n          autoComplete=\"cc-number\"\n          value={currentValue}\n          onChange={handleChange}\n          placeholder={placeholder}\n          disabled={disabled}\n          className={cn(\n            \"h-10 w-full border-0 bg-transparent px-3 pr-18 text-sm outline-none placeholder:text-muted-foreground\",\n            hasError && \"text-destructive\",\n            disabled && \"cursor-not-allowed opacity-50\",\n          )}\n          aria-label=\"Card number\"\n          aria-invalid={hasError}\n          aria-describedby={hasError ? errorId : undefined}\n          {...props}\n        />\n        <div className=\"pointer-events-none absolute right-3 flex items-center gap-2\">\n          {validationState.isComplete && (\n            <div className=\"flex h-5 w-5 items-center justify-center\">\n              {hasError ? (\n                <svg\n                  className=\"h-4 w-4 text-destructive\"\n                  fill=\"none\"\n                  viewBox=\"0 0 24 24\"\n                  stroke=\"currentColor\"\n                  strokeWidth={2}\n                  aria-hidden=\"true\"\n                >\n                  <circle cx=\"12\" cy=\"12\" r=\"10\" />\n                  <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\" />\n                  <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\" />\n                </svg>\n              ) : isValidComplete ? (\n                <svg\n                  className=\"h-4 w-4 text-emerald-500\"\n                  fill=\"none\"\n                  viewBox=\"0 0 24 24\"\n                  stroke=\"currentColor\"\n                  strokeWidth={2}\n                  aria-hidden=\"true\"\n                >\n                  <path\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    d=\"M5 13l4 4L19 7\"\n                  />\n                </svg>\n              ) : null}\n            </div>\n          )}\n          <div className=\"flex h-6 w-9 items-center justify-center\">\n            <BrandIcon\n              className={cn(\n                \"h-full w-full\",\n                brand === \"unknown\" && \"h-5 w-5 text-muted-foreground\",\n              )}\n              aria-hidden=\"true\"\n            />\n          </div>\n        </div>\n        {hasError && (\n          <span id={errorId} className=\"sr-only\">\n            Invalid card number\n          </span>\n        )}\n      </div>\n    );\n  },\n);\nCardNumberInput.displayName = \"CardNumberInput\";\n\ninterface CardExpiryInputProps\n  extends Omit<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    \"onChange\" | \"value\" | \"defaultValue\"\n  > {\n  /** The current value (controlled) */\n  value?: string;\n  /** The default value (uncontrolled) */\n  defaultValue?: string;\n  /** Callback fired when the value changes */\n  onValueChange?: (value: string) => void;\n  /** Callback fired when validation state changes */\n  onValidationChange?: (validation: {\n    isValid: boolean;\n    isComplete: boolean;\n    isExpired: boolean;\n  }) => void;\n}\n\nconst CardExpiryInput = React.forwardRef<\n  HTMLInputElement,\n  CardExpiryInputProps\n>(\n  (\n    {\n      className,\n      id,\n      name,\n      value,\n      defaultValue = \"\",\n      onValueChange,\n      onValidationChange,\n      placeholder = \"MM/YY\",\n      disabled,\n      ...props\n    },\n    ref,\n  ) => {\n    const context = useCardInputContext();\n    const generatedId = React.useId();\n    const inputId = id ?? generatedId;\n    const errorId = `${inputId}-error`;\n    const inputName = name ?? \"cc-exp\";\n\n    const [currentValue, setValue] = useControllableState(\n      value,\n      defaultValue,\n      onValueChange,\n    );\n\n    const [validationState, setValidationState] = React.useState<{\n      isComplete: boolean;\n      isValid: boolean;\n      isExpired: boolean;\n    }>({ isComplete: false, isValid: false, isExpired: false });\n\n    // When inside CardInputGroup, center text; otherwise left-align\n    const isGrouped = context !== null;\n\n    const hasError = validationState.isComplete && !validationState.isValid;\n\n    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n      const formatted = formatExpiry(e.target.value);\n      const validation = validateExpiry(formatted);\n\n      setValidationState(validation);\n      onValidationChange?.(validation);\n\n      setValue(formatted);\n    };\n\n    return (\n      <div className={cn(\"relative flex items-center\", !isGrouped && \"flex-1\")}>\n        <input\n          ref={ref}\n          id={inputId}\n          name={inputName}\n          type=\"text\"\n          inputMode=\"numeric\"\n          autoComplete=\"cc-exp\"\n          value={currentValue}\n          onChange={handleChange}\n          placeholder={placeholder}\n          maxLength={5}\n          disabled={disabled}\n          className={cn(\n            \"h-10 border-0 bg-transparent px-3 text-sm outline-none placeholder:text-muted-foreground\",\n            isGrouped ? \"w-20 text-center\" : \"w-full\",\n            hasError && \"text-destructive\",\n            disabled && \"cursor-not-allowed opacity-50\",\n            className,\n          )}\n          aria-label=\"Expiration date\"\n          aria-invalid={hasError}\n          aria-describedby={hasError ? errorId : undefined}\n          {...props}\n        />\n        {validationState.isComplete && (\n          <div className=\"pointer-events-none absolute right-1 flex h-4 w-4 items-center justify-center\">\n            {hasError ? (\n              <svg\n                className=\"h-3.5 w-3.5 text-destructive\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke=\"currentColor\"\n                strokeWidth={2}\n                aria-hidden=\"true\"\n              >\n                <circle cx=\"12\" cy=\"12\" r=\"10\" />\n                <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\" />\n                <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\" />\n              </svg>\n            ) : (\n              <svg\n                className=\"h-3.5 w-3.5 text-emerald-500\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke=\"currentColor\"\n                strokeWidth={2}\n                aria-hidden=\"true\"\n              >\n                <path\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  d=\"M5 13l4 4L19 7\"\n                />\n              </svg>\n            )}\n          </div>\n        )}\n        {hasError && (\n          <span id={errorId} className=\"sr-only\">\n            {validationState.isExpired\n              ? \"Card has expired\"\n              : \"Invalid expiration date\"}\n          </span>\n        )}\n      </div>\n    );\n  },\n);\nCardExpiryInput.displayName = \"CardExpiryInput\";\n\ninterface CardCvcInputProps\n  extends Omit<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    \"onChange\" | \"value\" | \"defaultValue\"\n  > {\n  /** The current value (controlled) */\n  value?: string;\n  /** The default value (uncontrolled) */\n  defaultValue?: string;\n  /** Callback fired when the value changes */\n  onValueChange?: (value: string) => void;\n}\n\nconst CardCvcInput = React.forwardRef<HTMLInputElement, CardCvcInputProps>(\n  (\n    {\n      className,\n      id,\n      name,\n      value,\n      defaultValue = \"\",\n      onValueChange,\n      disabled,\n      ...props\n    },\n    ref,\n  ) => {\n    const context = useCardInputContext();\n    const generatedId = React.useId();\n    const inputId = id ?? generatedId;\n    const inputName = name ?? \"cc-csc\";\n\n    // Get brand from context (auto-detected from card number)\n    const brand = context?.brand ?? \"unknown\";\n    const maxLength = getCvcMaxLength(brand);\n\n    const [currentValue, setCurrentValue] = useControllableState(\n      value,\n      defaultValue,\n      onValueChange,\n    );\n\n    // When inside CardInputGroup, center text; otherwise left-align\n    const isGrouped = context !== null;\n\n    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n      const rawValue = e.target.value.replace(/\\D/g, \"\").slice(0, maxLength);\n      setCurrentValue(rawValue);\n    };\n\n    return (\n      <div className={cn(\"relative flex items-center\", !isGrouped && \"flex-1\")}>\n        <input\n          ref={ref}\n          id={inputId}\n          name={inputName}\n          type=\"text\"\n          inputMode=\"numeric\"\n          autoComplete=\"cc-csc\"\n          value={currentValue}\n          onChange={handleChange}\n          placeholder={brand === \"amex\" ? \"CVV\" : \"CVC\"}\n          maxLength={maxLength}\n          disabled={disabled}\n          className={cn(\n            \"h-10 border-0 bg-transparent px-3 text-sm outline-none placeholder:text-muted-foreground\",\n            isGrouped ? \"w-16 text-center\" : \"w-full\",\n            disabled && \"cursor-not-allowed opacity-50\",\n            className,\n          )}\n          aria-label=\"Security code\"\n          {...props}\n        />\n      </div>\n    );\n  },\n);\nCardCvcInput.displayName = \"CardCvcInput\";\n\nexport {\n  CardInputGroup,\n  CardNumberInput,\n  CardExpiryInput,\n  CardCvcInput,\n  CardBrandIcons,\n};\n\nexport type {\n  CardBrand,\n  CardInputGroupProps,\n  CardNumberInputProps,\n  CardExpiryInputProps,\n  CardCvcInputProps,\n};\n",
          "type": "registry:ui",
          "target": "components/ui/card-input.tsx"
        }
      ],
      "registryDependencies": [
        "https://billui.com/r/use-controllable-state.json",
        "https://billui.com/r/utils.json",
        "https://billui.com/r/card-icons.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "invoice-card",
      "type": "registry:ui",
      "title": "Invoice Card",
      "description": "A card for displaying invoice details and billing history.",
      "files": [
        {
          "path": "src/registry/ui/invoice-card.tsx",
          "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { Download, ExternalLink, FileText } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nconst invoiceCardVariants = cva(\n  \"relative flex items-center gap-4 rounded-xl border bg-card p-4 text-card-foreground transition-colors hover:bg-accent/50\",\n  {\n    variants: {\n      variant: {\n        default: \"border-border\",\n        compact: \"gap-3 p-3\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\ninterface InvoiceCardProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof invoiceCardVariants> {}\n\nconst InvoiceCard = React.forwardRef<HTMLDivElement, InvoiceCardProps>(\n  ({ className, variant, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(invoiceCardVariants({ variant, className }))}\n      {...props}\n    />\n  ),\n);\nInvoiceCard.displayName = \"InvoiceCard\";\n\nconst InvoiceCardIcon = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-muted\",\n      className,\n    )}\n    {...props}\n  >\n    <FileText className=\"h-5 w-5 text-muted-foreground\" />\n  </div>\n));\nInvoiceCardIcon.displayName = \"InvoiceCardIcon\";\n\nconst InvoiceCardContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex min-w-0 flex-1 flex-col gap-1\", className)}\n    {...props}\n  />\n));\nInvoiceCardContent.displayName = \"InvoiceCardContent\";\n\nconst InvoiceCardHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex items-center gap-2\", className)}\n    {...props}\n  />\n));\nInvoiceCardHeader.displayName = \"InvoiceCardHeader\";\n\nconst InvoiceCardNumber = React.forwardRef<\n  HTMLSpanElement,\n  React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\"font-medium text-foreground\", className)}\n    {...props}\n  />\n));\nInvoiceCardNumber.displayName = \"InvoiceCardNumber\";\n\ntype InvoiceStatus = \"paid\" | \"pending\" | \"failed\" | \"refunded\" | \"draft\";\n\nconst statusConfig: Record<\n  InvoiceStatus,\n  {\n    label: string;\n    variant: \"default\" | \"secondary\" | \"destructive\" | \"outline\";\n  }\n> = {\n  paid: { label: \"Paid\", variant: \"default\" },\n  pending: { label: \"Pending\", variant: \"secondary\" },\n  failed: { label: \"Failed\", variant: \"destructive\" },\n  refunded: { label: \"Refunded\", variant: \"outline\" },\n  draft: { label: \"Draft\", variant: \"outline\" },\n};\n\ninterface InvoiceCardStatusProps\n  extends Omit<React.ComponentPropsWithoutRef<typeof Badge>, \"variant\"> {\n  status: InvoiceStatus;\n}\n\nconst InvoiceCardStatus = React.forwardRef<\n  HTMLDivElement,\n  InvoiceCardStatusProps\n>(({ className, status, ...props }, ref) => {\n  const config = statusConfig[status];\n  return (\n    <Badge\n      ref={ref}\n      variant={config.variant}\n      className={cn(\"text-xs\", className)}\n      {...props}\n    >\n      {config.label}\n    </Badge>\n  );\n});\nInvoiceCardStatus.displayName = \"InvoiceCardStatus\";\n\ninterface InvoiceCardDateProps extends React.HTMLAttributes<HTMLSpanElement> {\n  date: Date | string;\n  format?: \"short\" | \"long\";\n}\n\nconst InvoiceCardDate = React.forwardRef<HTMLSpanElement, InvoiceCardDateProps>(\n  ({ className, date, format = \"short\", ...props }, ref) => {\n    const dateObj = typeof date === \"string\" ? new Date(date) : date;\n    const formatted = dateObj.toLocaleDateString(\"en-US\", {\n      year: \"numeric\",\n      month: format === \"long\" ? \"long\" : \"short\",\n      day: \"numeric\",\n    });\n\n    return (\n      <span\n        ref={ref}\n        className={cn(\"text-sm text-muted-foreground\", className)}\n        {...props}\n      >\n        {formatted}\n      </span>\n    );\n  },\n);\nInvoiceCardDate.displayName = \"InvoiceCardDate\";\n\ninterface InvoiceCardAmountProps extends React.HTMLAttributes<HTMLSpanElement> {\n  amount: number;\n  currency?: string;\n}\n\nconst InvoiceCardAmount = React.forwardRef<\n  HTMLSpanElement,\n  InvoiceCardAmountProps\n>(({ className, amount, currency = \"$\", ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\n      \"ml-auto shrink-0 text-base font-semibold tabular-nums\",\n      className,\n    )}\n    {...props}\n  >\n    {currency}\n    {amount.toLocaleString(\"en-US\", { minimumFractionDigits: 2 })}\n  </span>\n));\nInvoiceCardAmount.displayName = \"InvoiceCardAmount\";\n\nconst InvoiceCardActions = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex shrink-0 items-center gap-1\", className)}\n    {...props}\n  />\n));\nInvoiceCardActions.displayName = \"InvoiceCardActions\";\n\ninterface InvoiceCardActionProps\n  extends React.ComponentPropsWithoutRef<typeof Button> {\n  action?: \"download\" | \"view\";\n}\n\nconst InvoiceCardAction = React.forwardRef<\n  HTMLButtonElement,\n  InvoiceCardActionProps\n>(({ className, action = \"download\", children, ...props }, ref) => {\n  const Icon = action === \"download\" ? Download : ExternalLink;\n\n  return (\n    <Button\n      ref={ref}\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn(\"h-8 w-8\", className)}\n      {...props}\n    >\n      {children ?? <Icon className=\"h-4 w-4\" />}\n      <span className=\"sr-only\">\n        {action === \"download\" ? \"Download\" : \"View\"}\n      </span>\n    </Button>\n  );\n});\nInvoiceCardAction.displayName = \"InvoiceCardAction\";\n\nexport {\n  InvoiceCard,\n  InvoiceCardIcon,\n  InvoiceCardContent,\n  InvoiceCardHeader,\n  InvoiceCardNumber,\n  InvoiceCardStatus,\n  InvoiceCardDate,\n  InvoiceCardAmount,\n  InvoiceCardActions,\n  InvoiceCardAction,\n  invoiceCardVariants,\n  statusConfig,\n};\n\nexport type {\n  InvoiceCardProps,\n  InvoiceCardStatusProps,\n  InvoiceCardDateProps,\n  InvoiceCardAmountProps,\n  InvoiceCardActionProps,\n  InvoiceStatus,\n};\n",
          "type": "registry:ui",
          "target": "components/ui/invoice-card.tsx"
        }
      ],
      "dependencies": [
        "class-variance-authority",
        "lucide-react"
      ],
      "registryDependencies": [
        "badge",
        "button",
        "https://billui.com/r/utils.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "payment-method",
      "type": "registry:ui",
      "title": "Payment Method",
      "description": "A composable card for displaying saved payment methods like credit cards.",
      "files": [
        {
          "path": "src/registry/ui/payment-method.tsx",
          "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { MoreHorizontal, Pencil, Trash2 } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { brandLabels, type CardBrand, CardBrandIcons } from \"./card-icons\";\n\nconst paymentMethodVariants = cva(\n  \"relative flex items-center gap-4 rounded-xl border bg-card p-4 text-card-foreground transition-colors\",\n  {\n    variants: {\n      variant: {\n        default: \"border-border\",\n        selected: \"border-primary ring-1 ring-primary\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\ninterface PaymentMethodProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof paymentMethodVariants> {}\n\nconst PaymentMethod = React.forwardRef<HTMLDivElement, PaymentMethodProps>(\n  ({ className, variant, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(paymentMethodVariants({ variant, className }))}\n      {...props}\n    />\n  ),\n);\nPaymentMethod.displayName = \"PaymentMethod\";\n\ninterface PaymentMethodIconProps extends React.HTMLAttributes<HTMLDivElement> {\n  brand?: CardBrand;\n}\n\nconst PaymentMethodIcon = React.forwardRef<\n  HTMLDivElement,\n  PaymentMethodIconProps\n>(({ className, brand = \"unknown\", ...props }, ref) => {\n  const Logo = CardBrandIcons[brand] ?? CardBrandIcons.unknown;\n  const isGeneric = brand === \"unknown\";\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        \"flex shrink-0 items-center justify-center overflow-hidden rounded-[4px]\",\n        isGeneric ? \"h-8 w-8 text-muted-foreground\" : \"h-8 w-12\",\n        className,\n      )}\n      {...props}\n    >\n      <Logo className={isGeneric ? \"h-6 w-6\" : \"h-full w-full\"} />\n    </div>\n  );\n});\nPaymentMethodIcon.displayName = \"PaymentMethodIcon\";\n\nconst PaymentMethodDetails = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex min-w-0 flex-1 flex-col gap-0.5\", className)}\n    {...props}\n  />\n));\nPaymentMethodDetails.displayName = \"PaymentMethodDetails\";\n\ninterface PaymentMethodNumberProps\n  extends React.HTMLAttributes<HTMLSpanElement> {\n  last4?: string;\n}\n\nconst PaymentMethodNumber = React.forwardRef<\n  HTMLSpanElement,\n  PaymentMethodNumberProps\n>(({ className, last4, children, ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\"font-medium text-foreground\", className)}\n    {...props}\n  >\n    {children ?? `•••• ${last4}`}\n  </span>\n));\nPaymentMethodNumber.displayName = \"PaymentMethodNumber\";\n\ninterface PaymentMethodExpiryProps\n  extends React.HTMLAttributes<HTMLSpanElement> {\n  month?: number;\n  year?: number;\n  expired?: boolean;\n}\n\nconst PaymentMethodExpiry = React.forwardRef<\n  HTMLSpanElement,\n  PaymentMethodExpiryProps\n>(({ className, month, year, expired, children, ...props }, ref) => {\n  const expiryText =\n    children ??\n    (month && year\n      ? `Expires ${month.toString().padStart(2, \"0\")}/${year.toString().slice(-2)}`\n      : null);\n\n  return (\n    <span\n      ref={ref}\n      className={cn(\n        \"text-sm\",\n        expired ? \"text-destructive\" : \"text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      {expiryText}\n      {expired && \" (Expired)\"}\n    </span>\n  );\n});\nPaymentMethodExpiry.displayName = \"PaymentMethodExpiry\";\n\ninterface PaymentMethodBadgeProps\n  extends React.ComponentPropsWithoutRef<typeof Badge> {}\n\nconst PaymentMethodBadge = React.forwardRef<\n  HTMLDivElement,\n  PaymentMethodBadgeProps\n>(({ className, children, ...props }, ref) => (\n  <Badge\n    ref={ref}\n    variant=\"secondary\"\n    className={cn(\"ml-auto shrink-0\", className)}\n    {...props}\n  >\n    {children}\n  </Badge>\n));\nPaymentMethodBadge.displayName = \"PaymentMethodBadge\";\n\nconst PaymentMethodActions = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex shrink-0 items-center gap-1\", className)}\n    {...props}\n  />\n));\nPaymentMethodActions.displayName = \"PaymentMethodActions\";\n\ninterface PaymentMethodActionProps\n  extends React.ComponentPropsWithoutRef<typeof Button> {\n  action?: \"edit\" | \"delete\" | \"more\";\n}\n\nconst PaymentMethodAction = React.forwardRef<\n  HTMLButtonElement,\n  PaymentMethodActionProps\n>(({ className, action = \"more\", children, ...props }, ref) => {\n  const icons = {\n    edit: Pencil,\n    delete: Trash2,\n    more: MoreHorizontal,\n  };\n  const labels = {\n    edit: \"Edit\",\n    delete: \"Delete\",\n    more: \"More options\",\n  };\n  const Icon = icons[action];\n\n  return (\n    <Button\n      ref={ref}\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn(\n        \"h-8 w-8\",\n        action === \"delete\" && \"hover:text-destructive\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? <Icon className=\"h-4 w-4\" />}\n      <span className=\"sr-only\">{labels[action]}</span>\n    </Button>\n  );\n});\nPaymentMethodAction.displayName = \"PaymentMethodAction\";\n\nexport {\n  PaymentMethod,\n  PaymentMethodIcon,\n  PaymentMethodDetails,\n  PaymentMethodNumber,\n  PaymentMethodExpiry,\n  PaymentMethodBadge,\n  PaymentMethodActions,\n  PaymentMethodAction,\n  paymentMethodVariants,\n  brandLabels,\n  CardBrandIcons,\n};\n\nexport type {\n  PaymentMethodProps,\n  PaymentMethodIconProps,\n  PaymentMethodNumberProps,\n  PaymentMethodExpiryProps,\n  PaymentMethodBadgeProps,\n  PaymentMethodActionProps,\n  CardBrand,\n};\n",
          "type": "registry:ui",
          "target": "components/ui/payment-method.tsx"
        }
      ],
      "dependencies": [
        "class-variance-authority",
        "lucide-react"
      ],
      "registryDependencies": [
        "badge",
        "button",
        "https://billui.com/r/utils.json",
        "https://billui.com/r/card-icons.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "plan-card",
      "type": "registry:ui",
      "title": "Plan Card",
      "description": "A card for displaying pricing tiers and subscription plans.",
      "files": [
        {
          "path": "src/registry/ui/plan-card.tsx",
          "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { Check, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nconst planCardVariants = cva(\n  \"relative rounded-2xl border bg-card text-card-foreground transition-all duration-200\",\n  {\n    variants: {\n      variant: {\n        default: \"border-border\",\n        highlighted:\n          \"border-primary shadow-lg shadow-primary/10 ring-1 ring-primary\",\n        compact: \"border-border p-4\",\n      },\n      size: {\n        default: \"p-6\",\n        lg: \"p-8\",\n      },\n      layout: {\n        vertical: \"flex flex-col min-w-[280px]\",\n        horizontal: \"flex flex-row items-center gap-6 w-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n      layout: \"vertical\",\n    },\n  },\n);\n\ninterface PlanCardProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof planCardVariants> {}\n\nconst PlanCard = React.forwardRef<HTMLDivElement, PlanCardProps>(\n  ({ className, variant, size, layout, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(planCardVariants({ variant, size, layout, className }))}\n      {...props}\n    />\n  ),\n);\nPlanCard.displayName = \"PlanCard\";\n\nconst PlanCardHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"flex flex-col gap-2\", className)} {...props} />\n));\nPlanCardHeader.displayName = \"PlanCardHeader\";\n\ninterface PlanCardBadgeProps\n  extends React.ComponentPropsWithoutRef<typeof Badge> {}\n\nconst PlanCardBadge = React.forwardRef<HTMLDivElement, PlanCardBadgeProps>(\n  ({ className, ...props }, ref) => (\n    <Badge ref={ref} className={cn(\"w-fit\", className)} {...props} />\n  ),\n);\nPlanCardBadge.displayName = \"PlanCardBadge\";\n\nconst PlanCardTitle = React.forwardRef<\n  HTMLHeadingElement,\n  React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n  <h3\n    ref={ref}\n    className={cn(\"text-2xl font-bold tracking-tight\", className)}\n    {...props}\n  />\n));\nPlanCardTitle.displayName = \"PlanCardTitle\";\n\nconst PlanCardDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n  <p\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nPlanCardDescription.displayName = \"PlanCardDescription\";\n\ninterface PlanCardPriceProps extends React.HTMLAttributes<HTMLDivElement> {\n  amount: number;\n  currency?: string;\n  period?: \"month\" | \"year\" | \"once\";\n  originalAmount?: number;\n}\n\nconst PlanCardPrice = React.forwardRef<HTMLDivElement, PlanCardPriceProps>(\n  (\n    {\n      className,\n      amount,\n      currency = \"$\",\n      period = \"month\",\n      originalAmount,\n      ...props\n    },\n    ref,\n  ) => {\n    const periodLabel = {\n      month: \"/mo\",\n      year: \"/yr\",\n      once: \"\",\n    };\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"my-6 flex items-baseline gap-1\", className)}\n        {...props}\n      >\n        {originalAmount && (\n          <span className=\"text-lg text-muted-foreground line-through\">\n            {currency}\n            {originalAmount}\n          </span>\n        )}\n        <span className=\"text-4xl font-bold tracking-tight\">\n          {currency}\n          {amount}\n        </span>\n        {period !== \"once\" && (\n          <span className=\"text-muted-foreground\">{periodLabel[period]}</span>\n        )}\n      </div>\n    );\n  },\n);\nPlanCardPrice.displayName = \"PlanCardPrice\";\n\nconst PlanCardFeatures = React.forwardRef<\n  HTMLUListElement,\n  React.HTMLAttributes<HTMLUListElement>\n>(({ className, ...props }, ref) => (\n  <ul\n    ref={ref}\n    className={cn(\"flex flex-col gap-3 text-sm\", className)}\n    {...props}\n  />\n));\nPlanCardFeatures.displayName = \"PlanCardFeatures\";\n\ninterface PlanCardFeatureProps extends React.HTMLAttributes<HTMLLIElement> {\n  included?: boolean;\n}\n\nconst PlanCardFeature = React.forwardRef<HTMLLIElement, PlanCardFeatureProps>(\n  ({ className, included = true, children, ...props }, ref) => (\n    <li\n      ref={ref}\n      className={cn(\n        \"flex items-center gap-3\",\n        !included && \"text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      {included ? (\n        <Check className=\"h-4 w-4 shrink-0 text-primary\" />\n      ) : (\n        <X className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n      )}\n      <span>{children}</span>\n    </li>\n  ),\n);\nPlanCardFeature.displayName = \"PlanCardFeature\";\n\ninterface PlanCardActionProps\n  extends React.ComponentPropsWithoutRef<typeof Button> {}\n\nconst PlanCardAction = React.forwardRef<HTMLButtonElement, PlanCardActionProps>(\n  ({ className, ...props }, ref) => (\n    <Button ref={ref} className={cn(\"mt-6 w-full\", className)} {...props} />\n  ),\n);\nPlanCardAction.displayName = \"PlanCardAction\";\n\nexport {\n  PlanCard,\n  PlanCardHeader,\n  PlanCardBadge,\n  PlanCardTitle,\n  PlanCardDescription,\n  PlanCardPrice,\n  PlanCardFeatures,\n  PlanCardFeature,\n  PlanCardAction,\n  planCardVariants,\n};\n\nexport type {\n  PlanCardProps,\n  PlanCardBadgeProps,\n  PlanCardPriceProps,\n  PlanCardFeatureProps,\n  PlanCardActionProps,\n};\n",
          "type": "registry:ui",
          "target": "components/ui/plan-card.tsx"
        }
      ],
      "dependencies": [
        "class-variance-authority",
        "lucide-react"
      ],
      "registryDependencies": [
        "badge",
        "button",
        "https://billui.com/r/utils.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "plan-group",
      "type": "registry:ui",
      "title": "Plan Group",
      "description": "A container for displaying multiple pricing plans with a monthly/yearly toggle.",
      "files": [
        {
          "path": "src/registry/ui/plan-group.tsx",
          "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { PlanCardPrice } from \"./plan-card\";\n\nconst planGroupVariants = cva(\"w-full\", {\n  variants: {\n    layout: {\n      horizontal: \"grid gap-6\",\n      vertical: \"flex flex-col gap-6\",\n    },\n    columns: {\n      2: \"md:grid-cols-2\",\n      3: \"lg:grid-cols-3\",\n      4: \"lg:grid-cols-2 xl:grid-cols-4\",\n    },\n  },\n  defaultVariants: {\n    layout: \"horizontal\",\n    columns: 2,\n  },\n});\n\ntype BillingPeriod = \"month\" | \"year\";\n\ninterface PlanGroupContextValue {\n  period: BillingPeriod;\n  setPeriod: (period: BillingPeriod) => void;\n}\n\nconst PlanGroupContext = React.createContext<PlanGroupContextValue | null>(\n  null,\n);\n\nfunction usePlanGroup() {\n  const context = React.useContext(PlanGroupContext);\n  if (!context) {\n    throw new Error(\"usePlanGroup must be used within a PlanGroup\");\n  }\n  return context;\n}\n\ninterface PlanGroupProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof planGroupVariants> {\n  defaultPeriod?: BillingPeriod;\n  onPeriodChange?: (period: BillingPeriod) => void;\n}\n\nconst PlanGroup = React.forwardRef<HTMLDivElement, PlanGroupProps>(\n  (\n    {\n      className,\n      layout,\n      columns,\n      defaultPeriod = \"month\",\n      onPeriodChange,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const [period, setPeriodState] =\n      React.useState<BillingPeriod>(defaultPeriod);\n\n    const setPeriod = React.useCallback(\n      (newPeriod: BillingPeriod) => {\n        setPeriodState(newPeriod);\n        onPeriodChange?.(newPeriod);\n      },\n      [onPeriodChange],\n    );\n\n    return (\n      <PlanGroupContext.Provider value={{ period, setPeriod }}>\n        <div ref={ref} className={cn(\"space-y-8\", className)} {...props}>\n          {children}\n        </div>\n      </PlanGroupContext.Provider>\n    );\n  },\n);\nPlanGroup.displayName = \"PlanGroup\";\n\nconst PlanGroupHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex flex-col items-center gap-4 text-center\", className)}\n    {...props}\n  />\n));\nPlanGroupHeader.displayName = \"PlanGroupHeader\";\n\nconst PlanGroupTitle = React.forwardRef<\n  HTMLHeadingElement,\n  React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n  <h2\n    ref={ref}\n    className={cn(\"text-3xl font-bold tracking-tight\", className)}\n    {...props}\n  />\n));\nPlanGroupTitle.displayName = \"PlanGroupTitle\";\n\nconst PlanGroupDescription = React.forwardRef<\n  HTMLSpanElement,\n  React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\"block text-muted-foreground max-w-2xl\", className)}\n    {...props}\n  />\n));\nPlanGroupDescription.displayName = \"PlanGroupDescription\";\n\ninterface PlanGroupToggleProps extends React.HTMLAttributes<HTMLDivElement> {\n  monthLabel?: string;\n  yearLabel?: string;\n  yearDiscount?: string;\n}\n\nconst PlanGroupToggle = React.forwardRef<HTMLDivElement, PlanGroupToggleProps>(\n  (\n    {\n      className,\n      monthLabel = \"Monthly\",\n      yearLabel = \"Yearly\",\n      yearDiscount,\n      ...props\n    },\n    ref,\n  ) => {\n    const { period, setPeriod } = usePlanGroup();\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"inline-flex items-center gap-2 rounded-full border bg-muted p-1\",\n          className,\n        )}\n        {...props}\n      >\n        <button\n          type=\"button\"\n          onClick={() => setPeriod(\"month\")}\n          className={cn(\n            \"rounded-full px-4 py-2 text-sm font-medium transition-all\",\n            period === \"month\"\n              ? \"bg-background text-foreground shadow-sm\"\n              : \"text-muted-foreground hover:text-foreground\",\n          )}\n        >\n          {monthLabel}\n        </button>\n        <button\n          type=\"button\"\n          onClick={() => setPeriod(\"year\")}\n          className={cn(\n            \"inline-flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium transition-all\",\n            period === \"year\"\n              ? \"bg-background text-foreground shadow-sm\"\n              : \"text-muted-foreground hover:text-foreground\",\n          )}\n        >\n          {yearLabel}\n          {yearDiscount && (\n            <span className=\"rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary\">\n              {yearDiscount}\n            </span>\n          )}\n        </button>\n      </div>\n    );\n  },\n);\nPlanGroupToggle.displayName = \"PlanGroupToggle\";\n\ninterface PlanGroupContentProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof planGroupVariants> {}\n\nconst PlanGroupContent = React.forwardRef<\n  HTMLDivElement,\n  PlanGroupContentProps\n>(({ className, layout, columns, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      planGroupVariants({ layout, columns }),\n      \"items-stretch\",\n      className,\n    )}\n    {...props}\n  />\n));\nPlanGroupContent.displayName = \"PlanGroupContent\";\n\ninterface PlanPriceProps\n  extends Omit<\n    React.ComponentPropsWithoutRef<typeof PlanCardPrice>,\n    \"amount\" | \"originalAmount\" | \"period\"\n  > {\n  monthlyAmount: number;\n  yearlyAmount: number;\n  originalMonthlyAmount?: number;\n  originalYearlyAmount?: number;\n}\n\nconst PlanPrice = React.forwardRef<HTMLDivElement, PlanPriceProps>(\n  (\n    {\n      monthlyAmount,\n      yearlyAmount,\n      originalMonthlyAmount,\n      originalYearlyAmount,\n      ...props\n    },\n    ref,\n  ) => {\n    const { period } = usePlanGroup();\n\n    const amount = period === \"month\" ? monthlyAmount : yearlyAmount;\n    const originalAmount =\n      period === \"month\" ? originalMonthlyAmount : originalYearlyAmount;\n\n    return (\n      <PlanCardPrice\n        ref={ref}\n        amount={amount}\n        originalAmount={originalAmount}\n        period={period}\n        {...props}\n      />\n    );\n  },\n);\nPlanPrice.displayName = \"PlanPrice\";\n\nexport {\n  PlanGroup,\n  PlanGroupHeader,\n  PlanGroupTitle,\n  PlanGroupDescription,\n  PlanGroupToggle,\n  PlanGroupContent,\n  PlanPrice,\n  usePlanGroup,\n  planGroupVariants,\n};\n\nexport type {\n  PlanGroupProps,\n  PlanGroupToggleProps,\n  PlanGroupContentProps,\n  PlanPriceProps,\n  BillingPeriod,\n};\n",
          "type": "registry:ui",
          "target": "components/ui/plan-group.tsx"
        }
      ],
      "dependencies": [
        "class-variance-authority"
      ],
      "registryDependencies": [
        "https://billui.com/r/utils.json",
        "https://billui.com/r/plan-card.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "pricing-table",
      "type": "registry:ui",
      "title": "Pricing Table",
      "description": "A comparison table for displaying multiple pricing plans side by side with feature comparison.",
      "files": [
        {
          "path": "src/registry/ui/pricing-table.tsx",
          "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { Check, Minus, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nconst GRID_COLS = {\n  3: \"grid-cols-3\",\n  4: \"grid-cols-4\",\n  5: \"grid-cols-5\",\n} as const;\n\ntype ColumnCount = keyof typeof GRID_COLS;\n\nconst pricingTableVariants = cva(\n  \"relative w-full overflow-hidden rounded-2xl border bg-card text-card-foreground\",\n  {\n    variants: {\n      variant: {\n        default: \"border-border\",\n        elevated: \"border-border shadow-lg\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\ninterface PricingTableProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof pricingTableVariants> {\n  /** Accessible label for the pricing table */\n  \"aria-label\"?: string;\n}\n\nconst PricingTable = React.forwardRef<HTMLDivElement, PricingTableProps>(\n  (\n    {\n      className,\n      variant,\n      \"aria-label\": ariaLabel = \"Pricing comparison\",\n      ...props\n    },\n    ref,\n  ) => (\n    <div\n      ref={ref}\n      role=\"table\"\n      aria-label={ariaLabel}\n      className={cn(pricingTableVariants({ variant, className }))}\n      {...props}\n    />\n  ),\n);\nPricingTable.displayName = \"PricingTable\";\n\ninterface PricingTableHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n  columns?: ColumnCount;\n}\n\nconst PricingTableHeader = React.forwardRef<\n  HTMLDivElement,\n  PricingTableHeaderProps\n>(({ className, columns = 4, ...props }, ref) => (\n  <div\n    ref={ref}\n    role=\"rowgroup\"\n    className={cn(\"grid border-b bg-muted/30\", GRID_COLS[columns], className)}\n    {...props}\n  />\n));\nPricingTableHeader.displayName = \"PricingTableHeader\";\n\ninterface PricingTablePlanProps extends React.HTMLAttributes<HTMLDivElement> {\n  highlighted?: boolean;\n}\n\nconst PricingTablePlan = React.forwardRef<\n  HTMLDivElement,\n  PricingTablePlanProps\n>(({ className, highlighted, ...props }, ref) => (\n  <div\n    ref={ref}\n    role=\"columnheader\"\n    className={cn(\n      \"flex flex-col items-center gap-1 p-6 text-center\",\n      highlighted &&\n        \"bg-primary/5 ring-2 ring-inset ring-primary first:rounded-tl-2xl last:rounded-tr-2xl\",\n      className,\n    )}\n    {...props}\n  />\n));\nPricingTablePlan.displayName = \"PricingTablePlan\";\n\ninterface PricingTablePlanNameProps\n  extends React.HTMLAttributes<HTMLHeadingElement> {\n  /** Heading level (default: 3) */\n  as?: \"h2\" | \"h3\" | \"h4\";\n}\n\nconst PricingTablePlanName = React.forwardRef<\n  HTMLHeadingElement,\n  PricingTablePlanNameProps\n>(({ className, as: Component = \"h3\", ...props }, ref) => (\n  <Component\n    ref={ref}\n    className={cn(\"text-lg font-semibold tracking-tight\", className)}\n    {...props}\n  />\n));\nPricingTablePlanName.displayName = \"PricingTablePlanName\";\n\ninterface PricingTablePriceProps extends React.HTMLAttributes<HTMLDivElement> {\n  amount: number;\n  currency?: string;\n  period?: \"month\" | \"year\" | \"once\";\n}\n\nconst PricingTablePrice = React.forwardRef<\n  HTMLDivElement,\n  PricingTablePriceProps\n>(({ className, amount, currency = \"$\", period = \"month\", ...props }, ref) => {\n  const periodLabel = {\n    month: \"/mo\",\n    year: \"/yr\",\n    once: \"\",\n  };\n\n  const periodFullLabel = {\n    month: \"per month\",\n    year: \"per year\",\n    once: \"one-time payment\",\n  };\n\n  // Screen reader friendly price\n  const srLabel =\n    period === \"once\"\n      ? `${currency}${amount}, ${periodFullLabel[period]}`\n      : `${currency}${amount} ${periodFullLabel[period]}`;\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\"mt-2 flex items-baseline gap-0.5\", className)}\n      {...props}\n    >\n      <span className=\"sr-only\">{srLabel}</span>\n      <span className=\"text-3xl font-bold tracking-tight\" aria-hidden=\"true\">\n        {currency}\n        {amount}\n      </span>\n      {period !== \"once\" && (\n        <span className=\"text-sm text-muted-foreground\" aria-hidden=\"true\">\n          {periodLabel[period]}\n        </span>\n      )}\n    </div>\n  );\n});\nPricingTablePrice.displayName = \"PricingTablePrice\";\n\nconst PricingTablePlanDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n  <p\n    ref={ref}\n    className={cn(\"mt-1 text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nPricingTablePlanDescription.displayName = \"PricingTablePlanDescription\";\n\ninterface PricingTableActionProps\n  extends React.ComponentPropsWithoutRef<typeof Button> {}\n\nconst PricingTableAction = React.forwardRef<\n  HTMLButtonElement,\n  PricingTableActionProps\n>(({ className, ...props }, ref) => (\n  <Button ref={ref} className={cn(\"mt-4 w-full\", className)} {...props} />\n));\nPricingTableAction.displayName = \"PricingTableAction\";\n\nconst PricingTableBody = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    role=\"rowgroup\"\n    className={cn(\n      \"divide-y\",\n      // Apply rounded corners to highlighted cells in last row\n      \"[&>:last-child>[data-highlighted]:first-child]:rounded-bl-2xl\",\n      \"[&>:last-child>[data-highlighted]:last-child]:rounded-br-2xl\",\n      className,\n    )}\n    {...props}\n  />\n));\nPricingTableBody.displayName = \"PricingTableBody\";\n\ninterface PricingTableRowProps extends React.HTMLAttributes<HTMLDivElement> {\n  columns?: ColumnCount;\n}\n\nconst PricingTableRow = React.forwardRef<HTMLDivElement, PricingTableRowProps>(\n  ({ className, columns = 4, ...props }, ref) => (\n    <div\n      ref={ref}\n      role=\"row\"\n      className={cn(\"grid items-stretch\", GRID_COLS[columns], className)}\n      {...props}\n    />\n  ),\n);\nPricingTableRow.displayName = \"PricingTableRow\";\n\nconst PricingTableFeatureCell = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    role=\"rowheader\"\n    className={cn(\n      \"flex items-center self-stretch px-6 py-4 text-sm font-medium\",\n      className,\n    )}\n    {...props}\n  />\n));\nPricingTableFeatureCell.displayName = \"PricingTableFeatureCell\";\n\n/** Empty spacer cell for the header's first column */\nconst PricingTableSpacer = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    role=\"columnheader\"\n    aria-hidden=\"true\"\n    className={cn(\"p-6\", className)}\n    {...props}\n  />\n));\nPricingTableSpacer.displayName = \"PricingTableSpacer\";\n\ntype CellValue = boolean | string | \"partial\";\n\ninterface PricingTableCellProps extends React.HTMLAttributes<HTMLDivElement> {\n  value: CellValue;\n  highlighted?: boolean;\n}\n\nconst PricingTableCell = React.forwardRef<\n  HTMLDivElement,\n  PricingTableCellProps\n>(({ className, value, highlighted, ...props }, ref) => {\n  const renderValue = () => {\n    if (typeof value === \"boolean\") {\n      return value ? (\n        <Check className=\"h-5 w-5 text-primary\" aria-hidden=\"true\" />\n      ) : (\n        <X className=\"h-5 w-5 text-muted-foreground/50\" aria-hidden=\"true\" />\n      );\n    }\n    if (value === \"partial\") {\n      return (\n        <Minus className=\"h-5 w-5 text-muted-foreground\" aria-hidden=\"true\" />\n      );\n    }\n    return <span className=\"text-sm\">{value}</span>;\n  };\n\n  // Screen reader friendly value\n  const getAriaLabel = () => {\n    if (typeof value === \"boolean\") {\n      return value ? \"Included\" : \"Not included\";\n    }\n    if (value === \"partial\") {\n      return \"Partially included\";\n    }\n    return undefined; // Let the text content speak for itself\n  };\n\n  const ariaLabel = getAriaLabel();\n\n  return (\n    <div\n      ref={ref}\n      role=\"cell\"\n      aria-label={ariaLabel}\n      data-highlighted={highlighted || undefined}\n      className={cn(\n        \"flex items-center justify-center self-stretch px-6 py-4\",\n        highlighted && \"bg-primary/5\",\n        className,\n      )}\n      {...props}\n    >\n      {renderValue()}\n    </div>\n  );\n});\nPricingTableCell.displayName = \"PricingTableCell\";\n\ninterface PricingTableFeatureLabelProps\n  extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst PricingTableFeatureLabel = React.forwardRef<\n  HTMLDivElement,\n  PricingTableFeatureLabelProps\n>(({ className, children, ...props }, ref) => (\n  <div\n    ref={ref}\n    role=\"row\"\n    aria-label={\n      typeof children === \"string\" ? `${children} section` : undefined\n    }\n    className={cn(\n      \"px-6 py-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50\",\n      className,\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n));\nPricingTableFeatureLabel.displayName = \"PricingTableFeatureLabel\";\n\nexport {\n  PricingTable,\n  PricingTableHeader,\n  PricingTablePlan,\n  PricingTablePlanName,\n  PricingTablePrice,\n  PricingTablePlanDescription,\n  PricingTableAction,\n  PricingTableBody,\n  PricingTableRow,\n  PricingTableFeatureCell,\n  PricingTableSpacer,\n  PricingTableCell,\n  PricingTableFeatureLabel,\n  pricingTableVariants,\n};\n\nexport type {\n  PricingTableProps,\n  PricingTableHeaderProps,\n  PricingTablePlanProps,\n  PricingTablePriceProps,\n  PricingTablePlanNameProps,\n  PricingTableActionProps,\n  PricingTableRowProps,\n  PricingTableCellProps,\n  PricingTableFeatureLabelProps,\n  CellValue,\n  ColumnCount,\n};\n",
          "type": "registry:ui",
          "target": "components/ui/pricing-table.tsx"
        }
      ],
      "dependencies": [
        "class-variance-authority",
        "lucide-react"
      ],
      "registryDependencies": [
        "button",
        "https://billui.com/r/utils.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "usage-card",
      "type": "registry:ui",
      "title": "Usage Card",
      "description": "A card for displaying usage metrics, billing cycle progress and itemized charges.",
      "files": [
        {
          "path": "src/registry/ui/usage-card.tsx",
          "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { ChevronDown, ChevronUp } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { cn } from \"@/lib/utils\";\n\nconst usageCardVariants = cva(\n  \"relative min-w-[280px] rounded-2xl border bg-card text-card-foreground\",\n  {\n    variants: {\n      variant: {\n        default: \"border-border\",\n        elevated: \"border-border shadow-lg\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\ninterface UsageCardProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof usageCardVariants> {}\n\nconst UsageCard = React.forwardRef<HTMLDivElement, UsageCardProps>(\n  ({ className, variant, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(usageCardVariants({ variant, className }))}\n      {...props}\n    />\n  ),\n);\nUsageCard.displayName = \"UsageCard\";\n\ninterface UsageCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst UsageCardHeader = React.forwardRef<HTMLDivElement, UsageCardHeaderProps>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(\n        \"flex items-center justify-between gap-4 p-4 pb-0\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n);\nUsageCardHeader.displayName = \"UsageCardHeader\";\n\ninterface UsageCardPeriodProps extends React.HTMLAttributes<HTMLSpanElement> {\n  daysRemaining?: number;\n}\n\nconst UsageCardPeriod = React.forwardRef<HTMLSpanElement, UsageCardPeriodProps>(\n  ({ className, daysRemaining, children, ...props }, ref) => (\n    <span\n      ref={ref}\n      className={cn(\"text-sm font-medium text-foreground\", className)}\n      {...props}\n    >\n      {children ??\n        (daysRemaining !== undefined &&\n          `${daysRemaining} days remaining in cycle`)}\n    </span>\n  ),\n);\nUsageCardPeriod.displayName = \"UsageCardPeriod\";\n\ninterface UsageCardActionProps\n  extends React.ComponentPropsWithoutRef<typeof Button> {}\n\nconst UsageCardAction = React.forwardRef<\n  HTMLButtonElement,\n  UsageCardActionProps\n>(({ className, ...props }, ref) => (\n  <Button\n    ref={ref}\n    variant=\"outline\"\n    size=\"sm\"\n    className={cn(\"rounded-full\", className)}\n    {...props}\n  />\n));\nUsageCardAction.displayName = \"UsageCardAction\";\n\ninterface UsageCardSummaryProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst UsageCardSummary = React.forwardRef<\n  HTMLDivElement,\n  UsageCardSummaryProps\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"p-4 space-y-3\", className)} {...props} />\n));\nUsageCardSummary.displayName = \"UsageCardSummary\";\n\ninterface UsageCardLabelsProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst UsageCardLabels = React.forwardRef<HTMLDivElement, UsageCardLabelsProps>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(\"flex items-start justify-between gap-4\", className)}\n      {...props}\n    />\n  ),\n);\nUsageCardLabels.displayName = \"UsageCardLabels\";\n\ninterface UsageCardLabelProps extends React.HTMLAttributes<HTMLDivElement> {\n  label: string;\n  amount: number;\n  limit?: number;\n  currency?: string;\n  align?: \"left\" | \"right\";\n}\n\nconst UsageCardLabel = React.forwardRef<HTMLDivElement, UsageCardLabelProps>(\n  (\n    {\n      className,\n      label,\n      amount,\n      limit,\n      currency = \"$\",\n      align = \"left\",\n      ...props\n    },\n    ref,\n  ) => (\n    <div\n      ref={ref}\n      className={cn(\n        \"flex flex-col gap-0.5\",\n        align === \"right\" && \"items-end\",\n        className,\n      )}\n      {...props}\n    >\n      <span className=\"text-xs text-muted-foreground\">{label}</span>\n      <span className=\"text-sm font-medium tabular-nums\">\n        {currency}\n        {amount.toLocaleString(\"en-US\", {\n          minimumFractionDigits: 2,\n          maximumFractionDigits: 2,\n        })}\n        {limit !== undefined && (\n          <span className=\"text-muted-foreground\">\n            {\" / \"}\n            {currency}\n            {limit.toLocaleString(\"en-US\", {\n              minimumFractionDigits: 0,\n              maximumFractionDigits: 0,\n            })}\n          </span>\n        )}\n      </span>\n    </div>\n  ),\n);\nUsageCardLabel.displayName = \"UsageCardLabel\";\n\ninterface UsageCardProgressProps\n  extends Omit<React.ComponentPropsWithoutRef<typeof Progress>, \"value\"> {\n  value: number;\n  max?: number;\n  showOverage?: boolean;\n}\n\nconst UsageCardProgress = React.forwardRef<\n  React.ComponentRef<typeof Progress>,\n  UsageCardProgressProps\n>(({ className, value, max = 100, showOverage = false, ...props }, ref) => {\n  const percentage = Math.min((value / max) * 100, 100);\n  const isOverage = value > max;\n\n  return (\n    <Progress\n      ref={ref}\n      value={percentage}\n      className={cn(\n        \"h-2 bg-muted\",\n        isOverage && \"*:data-[slot=progress-indicator]:bg-destructive\",\n        className,\n      )}\n      {...props}\n    />\n  );\n});\nUsageCardProgress.displayName = \"UsageCardProgress\";\n\ninterface UsageCardListContextValue {\n  expanded: boolean;\n  setExpanded: (expanded: boolean) => void;\n  itemCount: number;\n  collapsedHeight: number;\n  expandedHeight: number;\n}\n\nconst UsageCardListContext =\n  React.createContext<UsageCardListContextValue | null>(null);\n\nfunction useUsageCardList() {\n  const context = React.useContext(UsageCardListContext);\n  if (!context) {\n    throw new Error(\"useUsageCardList must be used within UsageCardList\");\n  }\n  return context;\n}\n\nconst ITEM_HEIGHT = 44;\n\ninterface UsageCardListProps extends React.HTMLAttributes<HTMLDivElement> {\n  defaultExpanded?: boolean;\n  /** Number of items to show when collapsed (can be decimal like 1.5 to show partial item) */\n  visibleItems?: number;\n  /** Enable collapse/expand functionality */\n  collapsible?: boolean;\n  /** Show dividers between items */\n  dividers?: boolean;\n}\n\nconst UsageCardList = React.forwardRef<HTMLDivElement, UsageCardListProps>(\n  (\n    {\n      className,\n      defaultExpanded = false,\n      visibleItems = 1.5,\n      collapsible = false,\n      dividers = true,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const [expanded, setExpanded] = React.useState(defaultExpanded);\n    const childrenArray = React.Children.toArray(children).filter((child) =>\n      React.isValidElement(child),\n    );\n    const itemCount = childrenArray.length;\n\n    const collapsedHeight = visibleItems * ITEM_HEIGHT;\n    const expandedHeight = itemCount * ITEM_HEIGHT;\n\n    return (\n      <UsageCardListContext.Provider\n        value={{\n          expanded,\n          setExpanded,\n          itemCount,\n          collapsedHeight,\n          expandedHeight,\n        }}\n      >\n        <div\n          ref={ref}\n          className={cn(\"relative\", collapsible && \"pb-4\", className)}\n          {...props}\n        >\n          {/* Content container */}\n          <div\n            className={cn(\n              \"overflow-hidden transition-[max-height] duration-300 ease-out\",\n              dividers && \"divide-y divide-border\",\n            )}\n            style={{\n              maxHeight:\n                collapsible && !expanded\n                  ? `${collapsedHeight}px`\n                  : `${expandedHeight}px`,\n            }}\n          >\n            {childrenArray}\n          </div>\n\n          {/* Fade overlay when collapsed */}\n          {collapsible && (\n            <div\n              className={cn(\n                \"pointer-events-none absolute inset-x-0 bottom-4 bg-linear-to-t from-card via-card/80 to-transparent transition-opacity duration-300\",\n                expanded ? \"opacity-0\" : \"opacity-100\",\n              )}\n              style={{ height: `${collapsedHeight * 0.6}px` }}\n            />\n          )}\n\n          {/* Toggle button - centered on bottom border */}\n          {collapsible && itemCount > 1 && (\n            <button\n              type=\"button\"\n              onClick={() => setExpanded(!expanded)}\n              className=\"absolute left-1/2 bottom-0 -translate-x-1/2 translate-y-1/2 z-10 flex h-7 w-7 items-center justify-center rounded-full border border-border bg-card text-muted-foreground shadow-sm hover:bg-muted hover:text-foreground transition-colors\"\n              aria-label={expanded ? \"Collapse\" : \"Expand\"}\n            >\n              {expanded ? (\n                <ChevronUp className=\"h-4 w-4\" />\n              ) : (\n                <ChevronDown className=\"h-4 w-4\" />\n              )}\n            </button>\n          )}\n        </div>\n      </UsageCardListContext.Provider>\n    );\n  },\n);\nUsageCardList.displayName = \"UsageCardList\";\n\ninterface UsageCardItemProps extends React.HTMLAttributes<HTMLDivElement> {\n  highlighted?: boolean;\n}\n\nconst UsageCardItem = React.forwardRef<HTMLDivElement, UsageCardItemProps>(\n  ({ className, highlighted = false, children, ...props }, ref) => {\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"flex items-center justify-between gap-4 px-4 py-3 transition-colors\",\n          highlighted && \"bg-muted/50\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  },\n);\nUsageCardItem.displayName = \"UsageCardItem\";\n\ninterface UsageCardItemLabelProps\n  extends React.HTMLAttributes<HTMLSpanElement> {}\n\nconst UsageCardItemLabel = React.forwardRef<\n  HTMLSpanElement,\n  UsageCardItemLabelProps\n>(({ className, ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\"text-sm text-foreground truncate\", className)}\n    {...props}\n  />\n));\nUsageCardItemLabel.displayName = \"UsageCardItemLabel\";\n\ninterface UsageCardItemValueProps\n  extends React.HTMLAttributes<HTMLSpanElement> {\n  amount?: number;\n  currency?: string;\n  unit?: string;\n}\n\nconst UsageCardItemValue = React.forwardRef<\n  HTMLSpanElement,\n  UsageCardItemValueProps\n>(({ className, amount, currency = \"$\", unit, children, ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\n      \"text-sm font-medium tabular-nums text-muted-foreground shrink-0\",\n      className,\n    )}\n    {...props}\n  >\n    {children ??\n      (amount !== undefined ? (\n        <>\n          {currency}\n          {amount.toLocaleString(\"en-US\", {\n            minimumFractionDigits: 2,\n            maximumFractionDigits: 2,\n          })}\n          {unit && <span className=\"text-xs ml-0.5\">{unit}</span>}\n        </>\n      ) : null)}\n  </span>\n));\nUsageCardItemValue.displayName = \"UsageCardItemValue\";\n\n// Metric bar for showing usage with visual indicator\ninterface UsageCardMeterProps extends React.HTMLAttributes<HTMLDivElement> {\n  used: number;\n  limit: number;\n  label: string;\n  unit?: string;\n  showPercentage?: boolean;\n}\n\nconst UsageCardMeter = React.forwardRef<HTMLDivElement, UsageCardMeterProps>(\n  (\n    { className, used, limit, label, unit, showPercentage = false, ...props },\n    ref,\n  ) => {\n    const percentage = Math.min((used / limit) * 100, 100);\n    const isWarning = percentage >= 80;\n    const isCritical = percentage >= 95;\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"px-4 py-3 space-y-2\", className)}\n        {...props}\n      >\n        <div className=\"flex items-center justify-between gap-2\">\n          <span className=\"text-sm text-foreground\">{label}</span>\n          <span className=\"text-sm tabular-nums text-muted-foreground\">\n            {used.toLocaleString()}\n            {unit && ` ${unit}`}\n            <span className=\"text-muted-foreground/60\">\n              {\" \"}\n              / {limit.toLocaleString()}\n              {unit && ` ${unit}`}\n            </span>\n            {showPercentage && (\n              <span\n                className={cn(\n                  \"ml-2\",\n                  isCritical\n                    ? \"text-destructive\"\n                    : isWarning\n                      ? \"text-amber-500\"\n                      : \"text-muted-foreground\",\n                )}\n              >\n                ({percentage.toFixed(0)}%)\n              </span>\n            )}\n          </span>\n        </div>\n        <div className=\"relative h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n          <div\n            className={cn(\n              \"h-full rounded-full transition-all duration-500 ease-out\",\n              isCritical\n                ? \"bg-destructive\"\n                : isWarning\n                  ? \"bg-amber-500\"\n                  : \"bg-primary\",\n            )}\n            style={{ width: `${percentage}%` }}\n          />\n        </div>\n      </div>\n    );\n  },\n);\nUsageCardMeter.displayName = \"UsageCardMeter\";\n\n// Total row for the bottom\ninterface UsageCardTotalProps extends React.HTMLAttributes<HTMLDivElement> {\n  label?: string;\n  amount: number;\n  currency?: string;\n}\n\nconst UsageCardTotal = React.forwardRef<HTMLDivElement, UsageCardTotalProps>(\n  ({ className, label = \"Total\", amount, currency = \"$\", ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(\n        \"flex items-center justify-between gap-4 px-4 py-3 bg-muted/30 border-t border-border\",\n        className,\n      )}\n      {...props}\n    >\n      <span className=\"text-sm font-medium text-foreground\">{label}</span>\n      <span className=\"text-sm font-semibold tabular-nums\">\n        {currency}\n        {amount.toLocaleString(\"en-US\", {\n          minimumFractionDigits: 2,\n          maximumFractionDigits: 2,\n        })}\n      </span>\n    </div>\n  ),\n);\nUsageCardTotal.displayName = \"UsageCardTotal\";\n\nexport {\n  UsageCard,\n  UsageCardHeader,\n  UsageCardPeriod,\n  UsageCardAction,\n  UsageCardSummary,\n  UsageCardLabels,\n  UsageCardLabel,\n  UsageCardProgress,\n  UsageCardList,\n  UsageCardItem,\n  UsageCardItemLabel,\n  UsageCardItemValue,\n  UsageCardMeter,\n  UsageCardTotal,\n  usageCardVariants,\n  useUsageCardList,\n};\n\nexport type {\n  UsageCardProps,\n  UsageCardHeaderProps,\n  UsageCardPeriodProps,\n  UsageCardActionProps,\n  UsageCardSummaryProps,\n  UsageCardLabelsProps,\n  UsageCardLabelProps,\n  UsageCardProgressProps,\n  UsageCardListProps,\n  UsageCardItemProps,\n  UsageCardItemLabelProps,\n  UsageCardItemValueProps,\n  UsageCardMeterProps,\n  UsageCardTotalProps,\n  UsageCardListContextValue,\n};\n",
          "type": "registry:ui",
          "target": "components/ui/usage-card.tsx"
        }
      ],
      "dependencies": [
        "class-variance-authority",
        "lucide-react"
      ],
      "registryDependencies": [
        "button",
        "progress",
        "https://billui.com/r/utils.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "animated-usage-card",
      "type": "registry:ui",
      "title": "Animated Usage Card",
      "description": "An animated version of usage-card with smooth spring animations powered by motion.",
      "files": [
        {
          "path": "src/registry/animated/animated-usage-card.tsx",
          "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { ChevronDown } from \"lucide-react\";\nimport { motion, useMotionValue, useSpring, useTransform } from \"motion/react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nconst animatedUsageCardVariants = cva(\n  \"relative min-w-[280px] rounded-2xl border bg-card text-card-foreground\",\n  {\n    variants: {\n      variant: {\n        default: \"border-border\",\n        elevated: \"border-border shadow-lg\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\ninterface AnimatedUsageCardProps\n  extends VariantProps<typeof animatedUsageCardVariants> {\n  className?: string;\n  children?: React.ReactNode;\n}\n\nconst AnimatedUsageCard = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardProps\n>(({ className, variant, children }, ref) => (\n  <motion.div\n    ref={ref}\n    initial={{ opacity: 0, y: 20, scale: 0.95 }}\n    animate={{ opacity: 1, y: 0, scale: 1 }}\n    transition={{\n      type: \"spring\",\n      bounce: 0.3,\n      duration: 0.6,\n    }}\n    className={cn(animatedUsageCardVariants({ variant, className }))}\n  >\n    {children}\n  </motion.div>\n));\nAnimatedUsageCard.displayName = \"AnimatedUsageCard\";\n\ninterface AnimatedUsageCardHeaderProps\n  extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst AnimatedUsageCardHeader = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardHeaderProps\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"flex items-center justify-between gap-4 p-4 pb-0\",\n      className,\n    )}\n    {...props}\n  />\n));\nAnimatedUsageCardHeader.displayName = \"AnimatedUsageCardHeader\";\n\ninterface AnimatedUsageCardPeriodProps\n  extends React.HTMLAttributes<HTMLSpanElement> {\n  daysRemaining?: number;\n}\n\nconst AnimatedUsageCardPeriod = React.forwardRef<\n  HTMLSpanElement,\n  AnimatedUsageCardPeriodProps\n>(({ className, daysRemaining, children, ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\"text-sm font-medium text-foreground\", className)}\n    {...props}\n  >\n    {children ??\n      (daysRemaining !== undefined &&\n        `${daysRemaining} days remaining in cycle`)}\n  </span>\n));\nAnimatedUsageCardPeriod.displayName = \"AnimatedUsageCardPeriod\";\n\ninterface AnimatedUsageCardActionProps\n  extends React.ComponentPropsWithoutRef<typeof Button> {}\n\nconst AnimatedUsageCardAction = React.forwardRef<\n  HTMLButtonElement,\n  AnimatedUsageCardActionProps\n>(({ className, ...props }, ref) => (\n  <Button\n    ref={ref}\n    variant=\"outline\"\n    size=\"sm\"\n    className={cn(\"rounded-full\", className)}\n    {...props}\n  />\n));\nAnimatedUsageCardAction.displayName = \"AnimatedUsageCardAction\";\n\ninterface AnimatedUsageCardSummaryProps\n  extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst AnimatedUsageCardSummary = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardSummaryProps\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"p-4 space-y-3\", className)} {...props} />\n));\nAnimatedUsageCardSummary.displayName = \"AnimatedUsageCardSummary\";\n\ninterface AnimatedUsageCardLabelsProps\n  extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst AnimatedUsageCardLabels = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardLabelsProps\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex items-start justify-between gap-4\", className)}\n    {...props}\n  />\n));\nAnimatedUsageCardLabels.displayName = \"AnimatedUsageCardLabels\";\n\ninterface AnimatedUsageCardLabelProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  label: string;\n  amount: number;\n  limit?: number;\n  currency?: string;\n  align?: \"left\" | \"right\";\n}\n\nconst AnimatedUsageCardLabel = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardLabelProps\n>(\n  (\n    {\n      className,\n      label,\n      amount,\n      limit,\n      currency = \"$\",\n      align = \"left\",\n      ...props\n    },\n    ref,\n  ) => (\n    <div\n      ref={ref}\n      className={cn(\n        \"flex flex-col gap-0.5\",\n        align === \"right\" && \"items-end\",\n        className,\n      )}\n      {...props}\n    >\n      <span className=\"text-xs text-muted-foreground\">{label}</span>\n      <span className=\"text-sm font-medium tabular-nums\">\n        {currency}\n        {amount.toLocaleString(\"en-US\", {\n          minimumFractionDigits: 2,\n          maximumFractionDigits: 2,\n        })}\n        {limit !== undefined && (\n          <span className=\"text-muted-foreground\">\n            {\" / \"}\n            {currency}\n            {limit.toLocaleString(\"en-US\", {\n              minimumFractionDigits: 0,\n              maximumFractionDigits: 0,\n            })}\n          </span>\n        )}\n      </span>\n    </div>\n  ),\n);\nAnimatedUsageCardLabel.displayName = \"AnimatedUsageCardLabel\";\n\ninterface AnimatedUsageCardProgressProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  value: number;\n  max?: number;\n  showOverage?: boolean;\n}\n\nconst AnimatedUsageCardProgress = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardProgressProps\n>(({ className, value, max = 100, showOverage = false, ...props }, ref) => {\n  const percentage = Math.min((value / max) * 100, 100);\n  const isOverage = value > max;\n\n  const motionValue = useMotionValue(0);\n  const springValue = useSpring(motionValue, {\n    stiffness: 120,\n    damping: 14,\n    mass: 0.8,\n  });\n  const width = useTransform(springValue, (v) => `${v}%`);\n\n  React.useEffect(() => {\n    motionValue.set(percentage);\n  }, [percentage, motionValue]);\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        \"relative h-2 w-full overflow-hidden rounded-full bg-muted\",\n        className,\n      )}\n      {...props}\n    >\n      <motion.div\n        className={cn(\n          \"h-full rounded-full\",\n          isOverage ? \"bg-destructive\" : \"bg-primary\",\n        )}\n        style={{ width }}\n      />\n    </div>\n  );\n});\nAnimatedUsageCardProgress.displayName = \"AnimatedUsageCardProgress\";\n\ninterface AnimatedUsageCardListContextValue {\n  expanded: boolean;\n  setExpanded: (expanded: boolean) => void;\n  itemCount: number;\n  collapsedHeight: number;\n  expandedHeight: number;\n}\n\nconst AnimatedUsageCardListContext =\n  React.createContext<AnimatedUsageCardListContextValue | null>(null);\n\nfunction useAnimatedUsageCardList() {\n  const context = React.useContext(AnimatedUsageCardListContext);\n  if (!context) {\n    throw new Error(\n      \"useAnimatedUsageCardList must be used within AnimatedUsageCardList\",\n    );\n  }\n  return context;\n}\n\nconst ITEM_HEIGHT = 44;\n\ninterface AnimatedUsageCardListProps {\n  className?: string;\n  children?: React.ReactNode;\n  defaultExpanded?: boolean;\n  /** Number of items to show when collapsed (can be decimal like 1.5 to show partial item) */\n  visibleItems?: number;\n  /** Enable collapse/expand functionality */\n  collapsible?: boolean;\n  /** Show dividers between items */\n  dividers?: boolean;\n}\n\nconst AnimatedUsageCardList = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardListProps\n>(\n  (\n    {\n      className,\n      defaultExpanded = false,\n      visibleItems = 1.5,\n      collapsible = false,\n      dividers = true,\n      children,\n    },\n    ref,\n  ) => {\n    const [expanded, setExpanded] = React.useState(defaultExpanded);\n    const childrenArray = React.Children.toArray(children).filter((child) =>\n      React.isValidElement(child),\n    );\n    const itemCount = childrenArray.length;\n\n    const bounceTransition = {\n      type: \"spring\" as const,\n      stiffness: 400,\n      damping: 25,\n      mass: 1,\n    };\n\n    const collapsedHeight = visibleItems * ITEM_HEIGHT;\n    const expandedHeight = itemCount * ITEM_HEIGHT;\n\n    return (\n      <AnimatedUsageCardListContext.Provider\n        value={{\n          expanded,\n          setExpanded,\n          itemCount,\n          collapsedHeight,\n          expandedHeight,\n        }}\n      >\n        <motion.div\n          ref={ref}\n          className={cn(\"relative\", collapsible && \"pb-2\", className)}\n          layout\n          transition={bounceTransition}\n        >\n          <motion.div\n            className={cn(\n              \"overflow-hidden\",\n              dividers && \"divide-y divide-border\",\n            )}\n            initial={false}\n            animate={{\n              height:\n                collapsible && !expanded ? collapsedHeight : expandedHeight,\n            }}\n            transition={bounceTransition}\n          >\n            {childrenArray}\n          </motion.div>\n\n          {collapsible && (\n            <motion.div\n              className=\"pointer-events-none absolute inset-x-0 bottom-2 bg-linear-to-t from-card via-card/60 to-transparent\"\n              style={{ height: `${collapsedHeight * 0.6}px` }}\n              initial={false}\n              animate={{\n                opacity: expanded ? 0 : 1,\n              }}\n              transition={{\n                duration: 0.25,\n              }}\n            />\n          )}\n\n          {collapsible && itemCount > 1 && (\n            <motion.button\n              type=\"button\"\n              onClick={() => setExpanded(!expanded)}\n              className=\"absolute left-1/2 bottom-0 -translate-x-1/2 translate-y-1/2 z-10 flex h-7 w-7 items-center justify-center rounded-full border border-border bg-card text-muted-foreground shadow-sm hover:bg-muted hover:text-foreground transition-colors\"\n              aria-label={expanded ? \"Collapse\" : \"Expand\"}\n              whileHover={{ scale: 1.15 }}\n              whileTap={{ scale: 0.9 }}\n              transition={{\n                type: \"spring\",\n                stiffness: 500,\n                damping: 20,\n              }}\n              layout\n            >\n              <motion.div\n                initial={false}\n                animate={{ rotate: expanded ? 180 : 0 }}\n                transition={{\n                  type: \"spring\",\n                  stiffness: 400,\n                  damping: 20,\n                }}\n              >\n                <ChevronDown className=\"h-4 w-4\" />\n              </motion.div>\n            </motion.button>\n          )}\n        </motion.div>\n      </AnimatedUsageCardListContext.Provider>\n    );\n  },\n);\nAnimatedUsageCardList.displayName = \"AnimatedUsageCardList\";\n\ninterface AnimatedUsageCardItemProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  highlighted?: boolean;\n}\n\nconst AnimatedUsageCardItem = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardItemProps\n>(({ className, highlighted = false, children, ...props }, ref) => {\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        \"flex items-center justify-between gap-4 px-4 py-3\",\n        highlighted && \"bg-muted/50\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n});\nAnimatedUsageCardItem.displayName = \"AnimatedUsageCardItem\";\n\ninterface AnimatedUsageCardItemLabelProps\n  extends React.HTMLAttributes<HTMLSpanElement> {}\n\nconst AnimatedUsageCardItemLabel = React.forwardRef<\n  HTMLSpanElement,\n  AnimatedUsageCardItemLabelProps\n>(({ className, ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\"text-sm text-foreground truncate\", className)}\n    {...props}\n  />\n));\nAnimatedUsageCardItemLabel.displayName = \"AnimatedUsageCardItemLabel\";\n\ninterface AnimatedUsageCardItemValueProps\n  extends React.HTMLAttributes<HTMLSpanElement> {\n  amount?: number;\n  currency?: string;\n  unit?: string;\n}\n\nconst AnimatedUsageCardItemValue = React.forwardRef<\n  HTMLSpanElement,\n  AnimatedUsageCardItemValueProps\n>(({ className, amount, currency = \"$\", unit, children, ...props }, ref) => (\n  <span\n    ref={ref}\n    className={cn(\n      \"text-sm font-medium tabular-nums text-muted-foreground shrink-0\",\n      className,\n    )}\n    {...props}\n  >\n    {children ??\n      (amount !== undefined ? (\n        <>\n          {currency}\n          {amount.toLocaleString(\"en-US\", {\n            minimumFractionDigits: 2,\n            maximumFractionDigits: 2,\n          })}\n          {unit && <span className=\"text-xs ml-0.5\">{unit}</span>}\n        </>\n      ) : null)}\n  </span>\n));\nAnimatedUsageCardItemValue.displayName = \"AnimatedUsageCardItemValue\";\n\ninterface AnimatedUsageCardMeterProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  used: number;\n  limit: number;\n  label: string;\n  unit?: string;\n  showPercentage?: boolean;\n}\n\nconst AnimatedUsageCardMeter = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardMeterProps\n>(\n  (\n    { className, used, limit, label, unit, showPercentage = false, ...props },\n    ref,\n  ) => {\n    const percentage = Math.min((used / limit) * 100, 100);\n    const isWarning = percentage >= 80;\n    const isCritical = percentage >= 95;\n\n    const motionValue = useMotionValue(0);\n    const springValue = useSpring(motionValue, {\n      stiffness: 120,\n      damping: 12,\n      mass: 0.8,\n    });\n    const width = useTransform(springValue, (v) => `${v}%`);\n\n    React.useEffect(() => {\n      motionValue.set(percentage);\n    }, [percentage, motionValue]);\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"px-4 py-3 space-y-2\", className)}\n        {...props}\n      >\n        <div className=\"flex items-center justify-between gap-2\">\n          <span className=\"text-sm text-foreground\">{label}</span>\n          <span className=\"text-sm tabular-nums text-muted-foreground\">\n            <AnimatedNumber value={used} />\n            {unit && ` ${unit}`}\n            <span className=\"text-muted-foreground/60\">\n              {\" \"}\n              / {limit.toLocaleString()}\n              {unit && ` ${unit}`}\n            </span>\n            {showPercentage && (\n              <span\n                className={cn(\n                  \"ml-2\",\n                  isCritical\n                    ? \"text-destructive\"\n                    : isWarning\n                      ? \"text-amber-500\"\n                      : \"text-muted-foreground\",\n                )}\n              >\n                (<AnimatedNumber value={percentage} decimals={0} />\n                %)\n              </span>\n            )}\n          </span>\n        </div>\n        <div className=\"relative h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n          <motion.div\n            className={cn(\n              \"h-full rounded-full\",\n              isCritical\n                ? \"bg-destructive\"\n                : isWarning\n                  ? \"bg-amber-500\"\n                  : \"bg-primary\",\n            )}\n            style={{ width }}\n          />\n        </div>\n      </div>\n    );\n  },\n);\nAnimatedUsageCardMeter.displayName = \"AnimatedUsageCardMeter\";\n\ninterface AnimatedNumberProps {\n  value: number;\n  decimals?: number;\n}\n\nfunction AnimatedNumber({ value, decimals = 0 }: AnimatedNumberProps) {\n  const motionValue = useMotionValue(0);\n  const springValue = useSpring(motionValue, {\n    stiffness: 150,\n    damping: 15,\n    mass: 0.5,\n  });\n  const display = useTransform(springValue, (v) =>\n    decimals > 0 ? v.toFixed(decimals) : Math.round(v).toLocaleString(),\n  );\n\n  React.useEffect(() => {\n    motionValue.set(value);\n  }, [value, motionValue]);\n\n  return <motion.span>{display}</motion.span>;\n}\n\ninterface AnimatedUsageCardTotalProps {\n  className?: string;\n  label?: string;\n  amount: number;\n  currency?: string;\n}\n\nconst AnimatedUsageCardTotal = React.forwardRef<\n  HTMLDivElement,\n  AnimatedUsageCardTotalProps\n>(({ className, label = \"Total\", amount, currency = \"$\" }, ref) => {\n  const motionValue = useMotionValue(0);\n  const springValue = useSpring(motionValue, {\n    stiffness: 150,\n    damping: 15,\n    mass: 0.5,\n  });\n  const display = useTransform(springValue, (v) =>\n    v.toLocaleString(\"en-US\", {\n      minimumFractionDigits: 2,\n      maximumFractionDigits: 2,\n    }),\n  );\n\n  React.useEffect(() => {\n    motionValue.set(amount);\n  }, [amount, motionValue]);\n\n  return (\n    <motion.div\n      ref={ref}\n      className={cn(\n        \"flex items-center justify-between gap-4 px-4 py-3 bg-muted/30 border-t border-border\",\n        className,\n      )}\n      initial={{ opacity: 0, y: 10 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={{\n        type: \"spring\",\n        bounce: 0.4,\n        duration: 0.5,\n        delay: 0.1,\n      }}\n    >\n      <span className=\"text-sm font-medium text-foreground\">{label}</span>\n      <span className=\"text-sm font-semibold tabular-nums\">\n        {currency}\n        <motion.span>{display}</motion.span>\n      </span>\n    </motion.div>\n  );\n});\nAnimatedUsageCardTotal.displayName = \"AnimatedUsageCardTotal\";\n\nexport {\n  AnimatedUsageCard,\n  AnimatedUsageCardHeader,\n  AnimatedUsageCardPeriod,\n  AnimatedUsageCardAction,\n  AnimatedUsageCardSummary,\n  AnimatedUsageCardLabels,\n  AnimatedUsageCardLabel,\n  AnimatedUsageCardProgress,\n  AnimatedUsageCardList,\n  AnimatedUsageCardItem,\n  AnimatedUsageCardItemLabel,\n  AnimatedUsageCardItemValue,\n  AnimatedUsageCardMeter,\n  AnimatedUsageCardTotal,\n  animatedUsageCardVariants,\n  useAnimatedUsageCardList,\n};\n\nexport type {\n  AnimatedUsageCardProps,\n  AnimatedUsageCardHeaderProps,\n  AnimatedUsageCardPeriodProps,\n  AnimatedUsageCardActionProps,\n  AnimatedUsageCardSummaryProps,\n  AnimatedUsageCardLabelsProps,\n  AnimatedUsageCardLabelProps,\n  AnimatedUsageCardProgressProps,\n  AnimatedUsageCardListProps,\n  AnimatedUsageCardItemProps,\n  AnimatedUsageCardItemLabelProps,\n  AnimatedUsageCardItemValueProps,\n  AnimatedUsageCardMeterProps,\n  AnimatedUsageCardTotalProps,\n  AnimatedUsageCardListContextValue,\n};\n",
          "type": "registry:ui",
          "target": "components/ui/animated-usage-card.tsx"
        }
      ],
      "dependencies": [
        "class-variance-authority",
        "lucide-react"
      ],
      "registryDependencies": [
        "button",
        "https://billui.com/r/utils.json"
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "billing-address-data",
      "type": "registry:lib",
      "title": "Billing Address Data",
      "description": "Billing Address Data component",
      "files": [
        {
          "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"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "use-controllable-state",
      "type": "registry:lib",
      "title": "Use Controllable State",
      "description": "Use Controllable State component",
      "files": [
        {
          "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"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "utils",
      "type": "registry:lib",
      "title": "Utils",
      "description": "Utils component",
      "files": [
        {
          "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"
      ]
    }
  ]
}