{
  "name": "search-input",
  "title": "SearchInput",
  "description": "Search bar with debounced results dropdown.",
  "type": "component",
  "registryDependencies": [
    "cn"
  ],
  "files": [
    {
      "path": "search-input.tsx",
      "content": "\"use client\";\n\nimport React, { useCallback, useMemo } from \"react\";\nimport { Field } from \"@base-ui/react/field\";\nimport { Input } from \"@base-ui/react/input\";\nimport type { Product } from \"@cimplify/sdk\";\nimport { useSearch } from \"@cimplify/sdk/react\";\nimport type { UseSearchOptions } from \"@cimplify/sdk/react\";\nimport { prefetchProduct } from \"./hooks/use-product\";\nimport { speculate } from \"./utils/prefetch-policy\";\nimport { useOptionalCimplifyClient } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\nimport { formatPrice } from \"@cimplify/sdk\";\n\nexport interface SearchInputClassNames {\n  root?: string;\n  input?: string;\n  clearButton?: string;\n  results?: string;\n  resultItem?: string;\n  empty?: string;\n  loading?: string;\n  resultImage?: string;\n  resultName?: string;\n  resultPrice?: string;\n  categoryFilter?: string;\n}\n\nexport interface SearchInputProps {\n  /** Placeholder text for the input. */\n  placeholder?: string;\n  /** Search options forwarded to useSearch. */\n  searchOptions?: UseSearchOptions;\n  /** Called when a product result is clicked. */\n  onResultClick?: (product: Product) => void;\n  /** Custom result item renderer. */\n  renderResult?: (product: Product) => React.ReactNode;\n  /** Show inline results dropdown. Default: true. */\n  showResults?: boolean;\n  /** Optional category ID to scope search results. */\n  categoryId?: string;\n  /** Display name for the active category filter. */\n  categoryName?: string;\n  className?: string;\n  classNames?: SearchInputClassNames;\n}\n\nconst SearchIcon = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"16\"\n    height=\"16\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    aria-hidden=\"true\"\n    data-cimplify-search-icon\n  >\n    <circle cx=\"11\" cy=\"11\" r=\"8\" />\n    <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\" />\n  </svg>\n);\n\n/**\n * SearchInput — search bar with debounced results dropdown.\n *\n * Wraps `useSearch` with a controlled input and optional inline results list.\n * Uses Base UI Field.Root + Input for accessible form semantics.\n */\nexport function SearchInput({\n  placeholder = \"Search products...\",\n  searchOptions,\n  onResultClick,\n  renderResult,\n  showResults = true,\n  categoryId,\n  categoryName,\n  className,\n  classNames,\n}: SearchInputProps): React.ReactElement {\n  const mergedOptions = useMemo<UseSearchOptions>(\n    () => (categoryId ? { ...searchOptions, category: categoryId } : searchOptions ?? {}),\n    [searchOptions, categoryId],\n  );\n  const { results, isLoading, query, setQuery, clear } = useSearch(mergedOptions);\n\n  const context = useOptionalCimplifyClient();\n  const warmResult = useCallback(\n    (product: Product) => {\n      const client = mergedOptions.client ?? context?.client;\n      if (!client || context?.prefetch.mode === \"off\") return;\n      speculate(() => prefetchProduct(client, product.slug ?? product.id));\n    },\n    [mergedOptions.client, context],\n  );\n\n  const handleValueChange = useCallback(\n    (value: string) => {\n      setQuery(value);\n    },\n    [setQuery],\n  );\n\n  return (\n    <Field.Root\n      data-cimplify-search\n      className={cn(className, classNames?.root)}\n      style={{ position: \"relative\" }}\n    >\n      <Field.Label className=\"sr-only\">Search products</Field.Label>\n\n      <div style={{ position: \"relative\", display: \"flex\", alignItems: \"center\" }}>\n        <span\n          data-cimplify-search-icon-wrapper\n          style={{\n            position: \"absolute\",\n            left: \"0.5rem\",\n            pointerEvents: \"none\",\n            display: \"flex\",\n            alignItems: \"center\",\n          }}\n        >\n          <SearchIcon />\n        </span>\n\n        <Input\n          type=\"search\"\n          value={query}\n          onValueChange={handleValueChange}\n          placeholder={placeholder}\n          data-cimplify-search-input\n          className={classNames?.input}\n        />\n\n        {query.length > 0 && (\n          <button\n            type=\"button\"\n            onClick={clear}\n            data-cimplify-search-clear\n            className={classNames?.clearButton}\n            aria-label=\"Clear search\"\n          >\n            &times;\n          </button>\n        )}\n      </div>\n\n      {categoryId && (\n        <div data-cimplify-search-category-filter className={classNames?.categoryFilter}>\n          Filtered by: {categoryName || categoryId}\n        </div>\n      )}\n\n      {showResults && query.length > 0 && (\n        <div data-cimplify-search-results className={classNames?.results}>\n          {isLoading && (\n            <div data-cimplify-search-loading className={classNames?.loading} aria-busy=\"true\">\n              Searching...\n            </div>\n          )}\n\n          {!isLoading && results.length === 0 && query.length >= 2 && (\n            <div data-cimplify-search-empty className={classNames?.empty}>\n              No results found\n            </div>\n          )}\n\n          {!isLoading &&\n            results.map((product) => (\n              <button\n                key={product.id}\n                type=\"button\"\n                onClick={() => onResultClick?.(product)}\n                onPointerEnter={() => warmResult(product)}\n                onFocus={() => warmResult(product)}\n                data-cimplify-search-result\n                className={classNames?.resultItem}\n                style={!renderResult ? { display: \"flex\", alignItems: \"center\", gap: \"0.5rem\", width: \"100%\" } : undefined}\n              >\n                {renderResult ? (\n                  renderResult(product)\n                ) : (\n                  <>\n                    {(product.images?.[0] || product.image_url) && (\n                      <img\n                        src={product.images?.[0] || product.image_url}\n                        alt=\"\"\n                        data-cimplify-search-result-image\n                        className={classNames?.resultImage}\n                        style={{ width: \"2rem\", height: \"2rem\", objectFit: \"cover\", borderRadius: \"0.25rem\", flexShrink: 0 }}\n                      />\n                    )}\n                    <span data-cimplify-search-result-name className={classNames?.resultName} style={{ flex: 1, textAlign: \"left\" }}>\n                      {product.name}\n                    </span>\n                    {product.default_price !== undefined && (\n                      <span data-cimplify-search-result-price className={classNames?.resultPrice} style={{ flexShrink: 0 }}>\n                        {formatPrice(product.default_price)}\n                      </span>\n                    )}\n                  </>\n                )}\n              </button>\n            ))}\n        </div>\n      )}\n    </Field.Root>\n  );\n}\n"
    }
  ]
}
