{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "type": "registry:ui",
  "title": "Combobox",
  "description": "Type to find one of many: a field well that opens the menu's frosted plate; rows filter at once while the plate's height settles to the new count, and the highlight glides row to row.",
  "dependencies": [
    "@base-ui/react@^1.8.0"
  ],
  "registryDependencies": [
    "https://metalui.dev/r/tokens.json"
  ],
  "files": [
    {
      "path": "packages/metalui/src/components/combobox/combobox.tsx",
      "type": "registry:ui",
      "target": "components/metalui/combobox/combobox.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { Combobox as BaseCombobox } from '@base-ui/react/combobox';\nimport { menuParts, ListGlide } from '../menu/menu';\n\n/* ─────────────────────────────────────────────────────────\n * COMBOBOX, type to find one of many, on Base UI Combobox\n *\n *   rest      a field well with a placeholder\n *   type      the menu's frosted plate opens (fades in on settle); rows filter at once, never\n *             lagging the fingers, while the plate's height settles to the new count on the\n *             settle spring, so it never snaps size\n *   move      ↑ ↓ or the pointer: the one highlight glides row to row (the menu's live list)\n *   choose    ↩ or a click fills the field; the plate fades out on release\n *   clear     a clear mark fades in once a value is chosen; it takes the choice away\n *   nothing   one quiet row: \"No matches\"\n *   focus     the flush green ring on the well\n *   invalid   the foundation's invalid ring; disabled 40 % (the form field's looks and sizes)\n * Reduce Motion: the height snaps; fades stay.\n * The well is the form field's (sizes, rings, states), the plate and rows the menu recipe; combobox adds the fit.\n * ───────────────────────────────────────────────────────── */\n\nconst GROUP = 'mu-combobox relative flex items-center min-w-combobox-min-width box-border recipe-well-field focus-within:focus-ring-flush data-disabled:opacity-field-state-disabled data-invalid:invalid-ring';\nconst SIZE = {\n  regular: 'gap-field-regular-gap h-field-regular-height pl-field-regular-pad-left pr-field-regular-pad-right rounded-field-regular-radius',\n  compact: 'gap-field-compact-gap h-field-compact-height pl-field-compact-pad-left pr-field-compact-pad-right rounded-field-compact-radius',\n};\nconst INPUT = 'mu-combobox-input flex-1 min-w-0 h-full p-0 border-0 outline-none bg-transparent type-ui text-field-field-ink caret-field-field-caret placeholder:text-field-field-hint';\nconst CLEAR = 'mu-combobox-clear inline-grid place-items-center flex-none size-combobox-clear-size rounded-full border-0 bg-transparent text-ink3 hover:text-ink cursor-pointer transition-opacity duration-settle ease-settle data-starting-style:opacity-0 data-ending-style:opacity-0 focus-visible:focus-ring';\nconst POSITIONER = 'mu-menu-positioner z-menu-z';\nconst POP = `${menuParts.PLATE} relative mu-combobox-pop combobox-pop-width`;\nconst FIT = 'mu-combobox-fit combobox-fit';\nconst LIST = 'mu-combobox-list overflow-y-auto outline-none';\nconst EMPTY = 'mu-combobox-empty px-menu-row-pad py-menu-heading-pad-bottom type-ui text-ink3 empty:hidden';\n\nfunction offset() {\n  if (typeof window === 'undefined') return 6;\n  return parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--mu-menu-offset')) || 6;\n}\n\n/** The plate's height follows its content on the settle spring. */\nfunction Fit({ children }: { children: React.ReactNode }) {\n  const inner = React.useRef<HTMLDivElement>(null);\n  const [height, setHeight] = React.useState<number>();\n  React.useLayoutEffect(() => {\n    const el = inner.current;\n    if (!el) return;\n    setHeight(el.offsetHeight);\n    if (typeof ResizeObserver === 'undefined') return;\n    const ro = new ResizeObserver(() => setHeight(el.offsetHeight));\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, []);\n  return <div className={FIT} style={{ height }}><div ref={inner}>{children}</div></div>;\n}\n\nexport interface ComboboxProps<Item extends string = string> {\n  /** Everything that can be found. */\n  items: Item[];\n  value?: Item | null;\n  defaultValue?: Item | null;\n  onValueChange?: (value: Item | null) => void;\n  placeholder?: string;\n  /** Names the field for assistive tech when there is no visible label. */\n  'aria-label'?: string;\n  disabled?: boolean;\n  /** The field's form sizes: regular (32, the default) or compact (28). */\n  size?: 'regular' | 'compact';\n  /** The value will not be accepted: the invalid ring, and aria-invalid on the input. */\n  invalid?: boolean;\n  /** Said in the one quiet row when nothing matches. */\n  emptyText?: string;\n  className?: string;\n}\n\n/** Type to find one of many. */\nexport function Combobox<Item extends string = string>({ items, value, defaultValue, onValueChange, placeholder, disabled, size = 'regular', invalid, emptyText = 'No matches', className, ...aria }: ComboboxProps<Item>) {\n  const group = `${GROUP} ${SIZE[size]}`;\n  return (\n    <BaseCombobox.Root<Item> items={items} value={value} defaultValue={defaultValue} onValueChange={(v) => onValueChange?.(v as Item | null)} disabled={disabled}>\n      <BaseCombobox.InputGroup data-invalid={invalid ? '' : undefined} className={className ? `${group} ${className}` : group}>\n        <BaseCombobox.Input className={INPUT} placeholder={placeholder} aria-label={aria['aria-label']} aria-invalid={invalid || undefined} />\n        <BaseCombobox.Clear className={CLEAR} aria-label=\"Clear\">\n          <svg aria-hidden viewBox=\"0 0 10 10\" className=\"size-combobox-clear-glyph\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5} strokeLinecap=\"round\"><path d=\"M2 2l6 6M8 2 2 8\" /></svg>\n        </BaseCombobox.Clear>\n      </BaseCombobox.InputGroup>\n      <BaseCombobox.Portal>\n        <BaseCombobox.Positioner className={POSITIONER} sideOffset={offset()} collisionPadding={8}>\n          <BaseCombobox.Popup className={POP}>\n            <Fit>\n              <ListGlide />\n              <BaseCombobox.Empty className={EMPTY}>{emptyText}</BaseCombobox.Empty>\n              <BaseCombobox.List className={LIST} style={{ maxHeight: 'calc(var(--mu-r-combobox-self-max-rows) * var(--mu-r-menu-row-height))' }}>\n                {(item: Item) => (\n                  <BaseCombobox.Item key={item} value={item} className={menuParts.LIVE_ROW}>\n                    <span className={menuParts.LABEL}>{item}</span>\n                  </BaseCombobox.Item>\n                )}\n              </BaseCombobox.List>\n            </Fit>\n          </BaseCombobox.Popup>\n        </BaseCombobox.Positioner>\n      </BaseCombobox.Portal>\n    </BaseCombobox.Root>\n  );\n}\n"
    }
  ],
  "docs": "Agent guide: https://metalui.dev/r/combobox.md. SwiftUI: MetalCombobox in the MetalUI Swift package."
}
