{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input",
  "title": "Input",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://ui.hotfix.jobs/r/spinner.json",
    "https://ui.hotfix.jobs/r/tokens.json",
    "https://ui.hotfix.jobs/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/components/ui/input.tsx",
      "content": "\"use client\";\n\nimport { Input as InputPrimitive } from \"@base-ui/react/input\";\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Spinner } from \"@/components/ui/spinner\";\n\nexport type InputSize = \"sm\" | \"md\" | \"lg\";\nexport type InputVariant = \"default\" | \"unstyled\";\n\nexport type InputProps = Omit<\n  InputPrimitive.Props & React.RefAttributes<HTMLInputElement>,\n  \"size\" | \"prefix\"\n> & {\n  size?: InputSize;\n  /**\n   * Visual variant of the input container.\n   * - `default` (unset): filled, borderless, rounded, hover + focus outline.\n   * - `unstyled`: no border, no rounded, no hover, no focus outline.\n   *   Use when embedding an Input inside a surface that owns its\n   *   own container styling (panels, table cells, composite fields).\n   */\n  variant?: InputVariant;\n  /** Content rendered at the start (icon, unit symbol, or text like `https://`). */\n  prefix?: React.ReactNode;\n  /** Content rendered at the end (icon, unit label, or text like `.com`). */\n  suffix?: React.ReactNode;\n  /** Disables the input and renders a spinner in the trailing slot. */\n  loading?: boolean;\n};\n\n// Heights per DESIGN.md: sm=24, md=32, lg=40. Text drops to body-13 at\n// sm so the label fits comfortably inside 24px; md and lg carry body-14\n// and body-16 respectively.\nconst heightBySize: Record<InputSize, string> = {\n  sm: \"h-6 text-small\",\n  md: \"h-8 text-small\",\n  lg: \"h-10 text-regular\",\n};\n\n// Padding ramps stored as literal ps-/pe- strings so Tailwind's JIT\n// picks them up in source (a computed `\"px-2.5\".replace(...)` would\n// yield `ps-2.5` at runtime but the class rule would never get emitted).\n// Padding scales with height. sm/md/lg heights are 24/32/40, so 8/12/14\n// keeps roughly the same padding-to-height ratio (~33-37%) across sizes.\nconst startPadBySize: Record<InputSize, string> = {\n  sm: \"ps-3\",\n  md: \"ps-3\",\n  lg: \"ps-3.5\",\n};\n\nconst endPadBySize: Record<InputSize, string> = {\n  sm: \"pe-3\",\n  md: \"pe-3\",\n  lg: \"pe-3.5\",\n};\n\n/** Inline affix. No background, no border, no hover -- just a muted\n *  glyph or short label rendered inline with the input text. When a\n *  prefix or suffix is present, it takes over the leading / trailing\n *  padding on that side so nothing double-pads. */\nfunction Affix({\n  side,\n  children,\n}: {\n  side: \"start\" | \"end\";\n  children: React.ReactNode;\n}) {\n  return (\n    <span\n      className={cn(\n        \"inline-flex shrink-0 items-center text-ink-muted\",\n        side === \"start\" ? \"ps-3 pe-1.5\" : \"ps-1.5 pe-3\",\n        \"[&_svg]:size-4\",\n      )}\n      data-slot={`input-${side === \"start\" ? \"prefix\" : \"suffix\"}`}\n    >\n      {children}\n    </span>\n  );\n}\n\nexport function Input({\n  className,\n  size = \"md\",\n  variant = \"default\",\n  prefix,\n  suffix,\n  loading,\n  disabled,\n  value,\n  ...props\n}: InputProps): React.ReactElement {\n  const isDisabled = disabled || loading;\n  const trailingSpinner = loading ? <Spinner size=\"sm\" /> : null;\n  const unstyled = variant === \"unstyled\";\n\n  const hasTrailing = Boolean(suffix) || Boolean(trailingSpinner);\n\n  const inputElement = (\n    <InputPrimitive\n      className={cn(\n        \"w-full min-w-0 bg-transparent border-none shadow-none outline-none ring-0\",\n        \"placeholder:text-ink-subtle focus:outline-none focus:ring-0\",\n        heightBySize[size],\n        // Prefix owns the leading padding on that side; otherwise the\n        // input carries its own padding.\n        prefix ? \"ps-0\" : startPadBySize[size],\n        // Suffix / spinner owns the trailing padding on that side.\n        hasTrailing ? \"pe-0\" : endPadBySize[size],\n        props.type === \"search\" &&\n          \"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none\",\n      )}\n      data-slot=\"input\"\n      disabled={isDisabled}\n      value={value}\n      {...props}\n    />\n  );\n\n  const control = (\n    <span\n      className={cn(\n        \"relative inline-flex w-full items-center overflow-hidden text-ink\",\n        !unstyled && \"rounded-[var(--radius-8)]\",\n        !unstyled && [\n          \"bg-fill-1 hover:bg-fill-2\",\n          \"outline-none has-focus-visible:[outline-style:solid] has-focus-visible:outline-[length:var(--focus-ring-width)] has-focus-visible:outline-[var(--focus-ring-color)] has-focus-visible:outline-offset-0\",\n          \"transition-[color,background-color,outline-color] duration-[var(--duration-state)] ease-[var(--ease-standard)]\",\n        ],\n        \"has-disabled:opacity-50 has-disabled:cursor-not-allowed\",\n        \"group-data-[invalid]/field:[outline-style:solid] group-data-[invalid]/field:outline-[length:1px] group-data-[invalid]/field:outline-error\",\n        \"has-[[aria-invalid=true]]:[outline-style:solid] has-[[aria-invalid=true]]:outline-[length:1px] has-[[aria-invalid=true]]:outline-error\",\n        className,\n      )}\n      data-slot=\"input-control\"\n    >\n      {prefix && <Affix side=\"start\">{prefix}</Affix>}\n      {inputElement}\n      {trailingSpinner && (\n        <span className=\"flex shrink-0 items-center pe-3 text-ink-muted\" data-slot=\"input-loading\">\n          {trailingSpinner}\n        </span>\n      )}\n      {suffix && !trailingSpinner && <Affix side=\"end\">{suffix}</Affix>}\n    </span>\n  );\n  return control;\n}\n\nexport { InputPrimitive };\n",
      "type": "registry:ui",
      "target": "components/ui/input.tsx"
    }
  ],
  "type": "registry:ui"
}