{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "search-input",
  "title": "Search Input",
  "author": "AniBT <https://anibt.net>",
  "description": "AniBT search box: an input with the magnifier, a clear key that keeps focus, a key-cap shortcut hint and the phone keyboard's search key.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@anibt/theme",
    "@anibt/input",
    "@anibt/kbd"
  ],
  "files": [
    {
      "path": "registry/anibt/ui/search-input.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { SearchIcon, XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Input } from \"@/registry/anibt/ui/input\"\nimport { Kbd } from \"@/registry/anibt/ui/kbd\"\n\n/**\n * SearchInput — the one AniBT 「贴纸手账」 search box.\n *\n * An `Input` with the magnifier inside its left edge and a clear key at its\n * right end. It owns the details every hand-rolled search box gets wrong:\n *\n * - `type=\"search\"` plus `enterKeyHint=\"search\"`, so a phone keyboard shows\n *   its search key; the browser's own cancel button is hidden, so the themed\n *   clear key is the only one.\n * - The clear key (a quiet 24px square, named `clearLabel`) empties the field\n *   and puts the caret back in it, so clearing by keyboard keeps focus.\n * - `shortcut` shows a key cap (⌘K) while the field is empty — a hint only;\n *   binding the key is the page's job.\n *\n * Controlled (`value` + `onValueChange`) or not (`defaultValue`). Every other\n * prop, `ref` included, reaches the `<input>`. 32 high like every A field;\n * give it `inputClassName=\"h-11 md:h-8\"` where a phone needs a bigger target.\n */\ntype SearchInputProps = Omit<React.ComponentProps<\"input\">, \"type\" | \"value\" | \"defaultValue\" | \"onChange\"> & {\n  value?: string\n  defaultValue?: string\n  onValueChange?: (value: string) => void\n  onChange?: React.ChangeEventHandler<HTMLInputElement>\n  /** Classes for the `<input>`; `className` sizes the wrapper. */\n  inputClassName?: string\n  /** A key cap shown while the field is empty, e.g. \"⌘K\". */\n  shortcut?: React.ReactNode\n  /** The clear key's accessible name. */\n  clearLabel?: string\n}\n\nfunction SearchInput({\n  value: valueProp,\n  defaultValue = \"\",\n  onValueChange,\n  onChange,\n  className,\n  inputClassName,\n  shortcut,\n  clearLabel = \"清除\",\n  placeholder = \"搜索\",\n  disabled,\n  readOnly,\n  ref,\n  ...props\n}: SearchInputProps) {\n  const innerRef = React.useRef<HTMLInputElement>(null)\n  const [uncontrolled, setUncontrolled] = React.useState(defaultValue)\n  const controlled = valueProp !== undefined\n  const value = controlled ? valueProp : uncontrolled\n\n  const setRefs = React.useCallback(\n    (node: HTMLInputElement | null) => {\n      innerRef.current = node\n      if (typeof ref === \"function\") return ref(node)\n      if (ref) ref.current = node\n    },\n    [ref]\n  )\n\n  const commit = (next: string) => {\n    if (!controlled) setUncontrolled(next)\n    onValueChange?.(next)\n  }\n\n  const showClear = value.length > 0 && !disabled && !readOnly\n\n  return (\n    <div data-slot=\"search-input\" className={cn(\"relative w-full min-w-0\", className)}>\n      <SearchIcon\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground\"\n      />\n      <Input\n        ref={setRefs}\n        type=\"search\"\n        enterKeyHint=\"search\"\n        autoComplete=\"off\"\n        placeholder={placeholder}\n        value={value}\n        disabled={disabled}\n        readOnly={readOnly}\n        onChange={(event) => {\n          onChange?.(event)\n          commit(event.target.value)\n        }}\n        className={cn(\n          \"pl-9 [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\",\n          showClear || shortcut ? \"pr-9\" : null,\n          inputClassName\n        )}\n        {...props}\n      />\n      {showClear ? (\n        <button\n          type=\"button\"\n          aria-label={clearLabel}\n          title={clearLabel}\n          onClick={() => {\n            commit(\"\")\n            innerRef.current?.focus()\n          }}\n          className=\"absolute top-1/2 right-1.5 flex size-6 -translate-y-1/2 cursor-pointer items-center justify-center rounded-[6px] text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-accent-foreground focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-solid\"\n        >\n          <XIcon className=\"size-4\" aria-hidden=\"true\" />\n        </button>\n      ) : shortcut ? (\n        <Kbd className=\"absolute top-1/2 right-2 -translate-y-1/2\">{shortcut}</Kbd>\n      ) : null}\n    </div>\n  )\n}\n\nexport { SearchInput }\nexport type { SearchInputProps }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}