{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "search-bar",
  "title": "Search Bar",
  "description": "The Material search bar, with leading and trailing actions.",
  "dependencies": [
    "lucide-react@^1.33.0"
  ],
  "registryDependencies": [
    "https://guillermo-rebolledo.github.io/materialcn/r/button.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/input-group.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/materialcn-theme.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/ui/search-bar.tsx",
      "content": "import {\n  useCallback,\n  useRef,\n  useState,\n  type ChangeEvent,\n  type ComponentProps,\n  type FormEvent,\n} from \"react\"\nimport { SearchIcon, XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"./button\"\nimport { InputGroup, InputGroupAddon, InputGroupInput } from \"./input-group\"\nimport { SearchBarContext, useSearchBar } from \"./search-bar-context\"\n\ntype SearchBarBaseProps = Omit<\n  ComponentProps<\"form\">,\n  \"defaultValue\" | \"onSubmit\"\n> & {\n  disabled?: boolean\n  invalid?: boolean\n  onClear?: () => void\n  onSubmit?: (value: string, event: FormEvent<HTMLFormElement>) => void\n  readOnly?: boolean\n}\n\ntype SearchBarValueProps =\n  | {\n      defaultValue?: string\n      onValueChange?: (value: string) => void\n      value?: never\n    }\n  | {\n      defaultValue?: never\n      onValueChange: (value: string) => void\n      value: string\n    }\n\ntype SearchBarProps = SearchBarBaseProps & SearchBarValueProps\n\ntype SearchBarInputAccessibleName =\n  | { \"aria-label\": string; \"aria-labelledby\"?: string }\n  | { \"aria-label\"?: never; \"aria-labelledby\": string }\n\ntype SearchBarInputProps = Omit<\n  ComponentProps<typeof InputGroupInput>,\n  | \"aria-label\"\n  | \"aria-labelledby\"\n  | \"defaultValue\"\n  | \"disabled\"\n  | \"onChange\"\n  | \"readOnly\"\n  | \"type\"\n  | \"value\"\n> &\n  SearchBarInputAccessibleName & {\n    onChange?: (event: ChangeEvent<HTMLInputElement>) => void\n  }\n\nfunction assignInputRef(\n  ref: SearchBarInputProps[\"ref\"],\n  node: HTMLInputElement | null,\n) {\n  if (typeof ref === \"function\") ref(node)\n  else if (ref) ref.current = node\n}\n\nfunction SearchBar({\n  \"aria-label\": ariaLabel = \"Search\",\n  children,\n  className,\n  defaultValue = \"\",\n  disabled = false,\n  invalid = false,\n  onClear,\n  onSubmit,\n  onValueChange,\n  readOnly = false,\n  value: valueProp,\n  ...props\n}: SearchBarProps) {\n  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue)\n  const inputRef = useRef<HTMLInputElement>(null)\n  const value = valueProp ?? uncontrolledValue\n\n  const setValue = useCallback(\n    (nextValue: string) => {\n      if (valueProp === undefined) setUncontrolledValue(nextValue)\n      onValueChange?.(nextValue)\n    },\n    [onValueChange, valueProp],\n  )\n\n  const clear = useCallback(() => {\n    setValue(\"\")\n    onClear?.()\n    inputRef.current?.focus()\n  }, [onClear, setValue])\n\n  const setInputNode = useCallback((node: HTMLInputElement | null) => {\n    inputRef.current = node\n  }, [])\n\n  return (\n    <SearchBarContext.Provider\n      value={{\n        clear,\n        disabled,\n        invalid,\n        readOnly,\n        setInputNode,\n        setValue,\n        value,\n      }}\n    >\n      <form\n        {...props}\n        role=\"search\"\n        aria-label={ariaLabel}\n        data-disabled={disabled || undefined}\n        data-invalid={invalid || undefined}\n        data-slot=\"search-bar\"\n        className={cn(\"w-full\", className)}\n        onSubmit={(event) => {\n          event.preventDefault()\n          if (disabled || readOnly) return\n          onSubmit?.(value, event)\n        }}\n      >\n        <fieldset className=\"contents\" disabled={disabled}>\n          <InputGroup\n            className={cn(\n              // Kit: 56dp, 28dp radius, Surface Container High, 4dp inset —\n              // and no elevation in any state.\n              \"h-14 rounded-[28px] border border-transparent bg-m3-surface-container-high px-1\",\n              \"transition-[border-color,background-color] duration-(--m3-spring-effects-fast-duration) ease-(--m3-spring-effects-fast)\",\n              \"has-[[aria-invalid=true]]:border-m3-error\",\n              \"has-disabled:cursor-not-allowed has-disabled:bg-m3-on-surface/12\",\n            )}\n          >\n            {children}\n          </InputGroup>\n        </fieldset>\n      </form>\n    </SearchBarContext.Provider>\n  )\n}\n\nfunction SearchBarInput({\n  className,\n  onChange,\n  ref,\n  ...props\n}: SearchBarInputProps) {\n  const context = useSearchBar()\n\n  return (\n    <InputGroupInput\n      {...props}\n      ref={(node) => {\n        context.setInputNode(node)\n        assignInputRef(ref, node)\n      }}\n      type=\"search\"\n      value={context.value}\n      disabled={context.disabled}\n      readOnly={context.readOnly}\n      aria-invalid={context.invalid || undefined}\n      className={cn(\n        \"h-full appearance-none px-1 text-m3-body-lg text-foreground placeholder:text-muted-foreground disabled:text-m3-on-surface/38\",\n        \"[&::-webkit-search-cancel-button]:hidden\",\n        className,\n      )}\n      onChange={(event) => {\n        onChange?.(event)\n        if (!event.defaultPrevented) context.setValue(event.currentTarget.value)\n      }}\n    />\n  )\n}\n\nfunction SearchBarLeading({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <InputGroupAddon\n      {...props}\n      align=\"inline-start\"\n      data-slot=\"search-bar-leading\"\n      className={cn(\"size-12 [&>svg]:size-6\", className)}\n    />\n  )\n}\n\nfunction SearchBarTrailing({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <InputGroupAddon\n      {...props}\n      align=\"inline-end\"\n      data-slot=\"search-bar-trailing\"\n      className={cn(\"min-h-12 gap-0 [&>svg]:size-6\", className)}\n    />\n  )\n}\n\nfunction SearchBarClear({\n  \"aria-label\": ariaLabel = \"Clear search\",\n  onClick,\n  ...props\n}: ComponentProps<typeof Button>) {\n  const context = useSearchBar()\n  if (!context.value) return null\n\n  return (\n    <Button\n      {...props}\n      type=\"button\"\n      aria-label={ariaLabel}\n      data-slot=\"search-bar-clear\"\n      disabled={context.disabled}\n      size=\"icon\"\n      variant=\"ghost\"\n      onClick={(event) => {\n        onClick?.(event)\n        if (!event.defaultPrevented) context.clear()\n      }}\n    >\n      <XIcon aria-hidden=\"true\" />\n    </Button>\n  )\n}\n\nfunction SearchBarSubmit({\n  \"aria-label\": ariaLabel = \"Submit search\",\n  children,\n  disabled,\n  ...props\n}: ComponentProps<typeof Button>) {\n  const context = useSearchBar()\n\n  return (\n    <Button\n      {...props}\n      type=\"submit\"\n      aria-label={ariaLabel}\n      data-slot=\"search-bar-submit\"\n      disabled={disabled || context.disabled || context.readOnly}\n      size=\"icon\"\n      variant=\"ghost\"\n    >\n      {children ?? <SearchIcon aria-hidden=\"true\" />}\n    </Button>\n  )\n}\n\nexport {\n  SearchBar,\n  SearchBarClear,\n  SearchBarInput,\n  SearchBarLeading,\n  SearchBarSubmit,\n  SearchBarTrailing,\n}\nexport type { SearchBarInputProps, SearchBarProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/search-bar-context.ts",
      "content": "import { createContext, useContext } from \"react\"\n\ntype SearchBarContextValue = {\n  clear: () => void\n  disabled: boolean\n  invalid: boolean\n  readOnly: boolean\n  setInputNode: (node: HTMLInputElement | null) => void\n  setValue: (value: string) => void\n  value: string\n}\n\nconst SearchBarContext = createContext<SearchBarContextValue | null>(null)\n\nfunction useSearchBar() {\n  const context = useContext(SearchBarContext)\n  if (!context) {\n    throw new Error(\"SearchBar components must be used within <SearchBar />\")\n  }\n  return context\n}\n\nexport { SearchBarContext, useSearchBar }\nexport type { SearchBarContextValue }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}