{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "search-field",
  "type": "registry:ui",
  "title": "SearchField",
  "description": "Search pill: a rounded-full text input with a leading magnifier and a trailing clear button.",
  "categories": [
    "forms"
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://whiskeyjack.net/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/search-field.tsx",
      "type": "registry:ui",
      "target": "components/ui/search-field.tsx",
      "content": "import * as React from 'react'\nimport { cva } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\n\nconst inputVariants = cva(\n  cn(\n    // The shared field shape -- same regular radius, solid border, no fill and\n    // deboss recess as Input and Select. The full-round pill belongs to\n    // `frosted`, which is a floating bar rather than a field on a surface.\n    'wj-focus-ring w-full rounded-lg pl-9 pr-8 text-sm',\n    'bg-transparent shadow-deboss',\n    'border border-[var(--color-border-light)] dark:border-[var(--color-border-dark)]',\n    'hover:border-[var(--color-warm-300)] dark:hover:border-[var(--color-neutral-700)] transition-colors',\n    'text-[var(--color-text-primary-light)] dark:text-[var(--color-text-primary-dark)]',\n    'placeholder:text-[var(--color-text-muted-light)] dark:placeholder:text-[var(--color-text-muted-dark)]',\n  ),\n  {\n    variants: {\n      size: {\n        sm: 'h-control-sm',\n        md: 'h-control-md',\n      },\n    },\n    defaultVariants: { size: 'md' },\n  },\n)\n\n// Floating-pill chrome for `variant=\"frosted\"` -- the recipe Chip Away's\n// MobileFloatingSearch hand-rolled: frosted surface, border, and the focus\n// indicator, which belongs to the pill because the input inside it has no\n// border of its own to carry one.\nconst FROSTED_ROOT =\n  'wj-focus-ring-within wj-frost flex items-center gap-2 h-12 px-4 rounded-full shadow-lg ' +\n  'border border-[var(--color-border-light)] dark:border-[var(--color-border-dark)] ' +\n  'hover:border-[var(--color-warm-300)] dark:hover:border-[var(--color-neutral-700)] transition-colors'\n\nconst FROSTED_INPUT =\n  'flex-1 min-w-0 bg-transparent text-sm focus:outline-none ' +\n  'text-[var(--color-text-primary-light)] dark:text-[var(--color-text-primary-dark)] ' +\n  'placeholder:text-[var(--color-text-muted-light)] dark:placeholder:text-[var(--color-text-muted-dark)]'\n\nexport interface SearchFieldProps {\n  value: string\n  onChange: (value: string) => void\n  /** Accessible name for the input -- the placeholder is not a label. */\n  label: string\n  /** Accessible name AND hover title for the icon-only clear button. */\n  clearLabel: string\n  placeholder?: string\n  /** False takes the field (and its clear button) out of the tab order while\n   * a wrapper keeps it mounted but visually collapsed -- a zero-width input\n   * still catches Tab, and a focus ring on an invisible field reads as the\n   * focus having vanished. Pair with `HeaderStandIn`'s `shown`. */\n  tabbable?: boolean\n  /** `md` (44px) is the DS control baseline; `sm` (36px) sits flush in denser\n   * chrome (a 64px header row, a sidebar rail). Applies only to\n   * `variant=\"solid\"` -- `frosted` is always `h-12`. */\n  size?: 'sm' | 'md'\n  /**\n   * `solid` (default) is the bordered field on a surface, with the magnifier\n   * and clear button absolutely positioned inside a bordered text input.\n   * `frosted` is the floating blurred pill for a bar over scrolling content\n   * (Chip Away's mobile floating search): the chrome moves to the root, the\n   * input goes transparent and borderless, and the magnifier (18px) and clear\n   * button (16px, vs 16px/14px in solid) render as row children instead of\n   * overlaying the input.\n   */\n  variant?: 'solid' | 'frosted'\n  autoFocus?: boolean\n  className?: string\n  /** Inline styles for the root element. */\n  style?: React.CSSProperties\n}\n\n/**\n * Search pill: a rounded-full text input with a leading magnifier and a\n * trailing clear button that appears once there is anything to clear.\n * Controlled and i18n-agnostic -- pass translated `label`, `clearLabel` and\n * `placeholder`. Inline SVG glyphs so the design system stays icon-free\n * (apps own that decision).\n */\nexport const SearchField = React.forwardRef<HTMLDivElement, SearchFieldProps>(\n  (\n    {\n      value,\n      onChange,\n      label,\n      clearLabel,\n      placeholder,\n      tabbable = true,\n      size,\n      variant = 'solid',\n      autoFocus,\n      className,\n      style,\n    },\n    ref,\n  ) => {\n    const tabIndex = tabbable ? undefined : -1\n    const frosted = variant === 'frosted'\n    return (\n      <div\n        ref={ref}\n        className={cn(frosted ? FROSTED_ROOT : 'relative', className)}\n        style={style}\n      >\n        <svg\n          className={\n            frosted\n              ? 'shrink-0 w-[18px] h-[18px] text-[var(--color-text-muted-light)] dark:text-[var(--color-text-muted-dark)]'\n              : 'absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none text-[var(--color-text-muted-light)] dark:text-[var(--color-text-muted-dark)]'\n          }\n          viewBox=\"0 0 16 16\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n          strokeLinecap=\"round\"\n          aria-hidden\n        >\n          <circle cx=\"7\" cy=\"7\" r=\"4.5\" />\n          <path d=\"M10.5 10.5L14 14\" />\n        </svg>\n        <input\n          type=\"text\"\n          value={value}\n          autoFocus={autoFocus}\n          tabIndex={tabIndex}\n          onChange={(e) => onChange(e.target.value)}\n          placeholder={placeholder}\n          aria-label={label}\n          className={frosted ? FROSTED_INPUT : inputVariants({ size })}\n        />\n        {value && (\n          <button\n            type=\"button\"\n            onClick={() => onChange('')}\n            aria-label={clearLabel}\n            title={clearLabel}\n            tabIndex={tabIndex}\n            className={\n              frosted\n                ? 'wj-focus-ring shrink-0 rounded-full p-1 text-[var(--color-text-muted-light)] dark:text-[var(--color-text-muted-dark)] hover:text-[var(--color-text-primary-light)] dark:hover:text-[var(--color-text-primary-dark)]'\n                : 'wj-focus-ring absolute right-2 top-1/2 -translate-y-1/2 rounded-full p-1 text-[var(--color-text-muted-light)] dark:text-[var(--color-text-muted-dark)] hover:text-[var(--color-text-primary-light)] dark:hover:text-[var(--color-text-primary-dark)]'\n            }\n          >\n            <svg\n              className={frosted ? 'w-4 h-4' : 'w-3.5 h-3.5'}\n              viewBox=\"0 0 14 14\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              aria-hidden\n            >\n              <path d=\"M3 3l8 8M11 3l-8 8\" />\n            </svg>\n          </button>\n        )}\n      </div>\n    )\n  },\n)\nSearchField.displayName = 'SearchField'\n"
    }
  ],
  "docs": "Use this instead of hand-rolling the pill-with-magnifier-and-clear recipe. Controlled (value/onChange) and i18n-agnostic - pass translated label, clearLabel and placeholder; label is the accessible name, since a placeholder is not a label. Pass tabbable={shown} when a HeaderStandIn keeps it mounted but collapsed, so a zero-width field never catches Tab. size sm (h-9) sits flush in dense chrome; md (h-10) matches the control baseline - applies only to variant solid (the default). variant frosted is a floating blurred pill for a bar over scrolling content, always h-12 regardless of size.",
  "meta": {
    "group": "forms",
    "related": [
      "input",
      "header-stand-in",
      "use-stand-in-target"
    ],
    "exports": [
      "SearchField"
    ],
    "siteSlug": "search-field"
  }
}
