{
  "schemaVersion": 1,
  "name": "tag-filter",
  "useFor": "Connect onAddTag and onRemoveTag to the filter state, then apply those filters to the results.",
  "constraints": [
    "Filters that hide which tags are on",
    "Colored tags per category"
  ],
  "documentation": "",
  "source": "Extension from opencommerce-agentcard-web src/components/pricing.tsx (the plan chip), monitoring.tsx (#merchant-filter)",
  "exports": [
    "TagFilter"
  ],
  "client": true,
  "docsUrl": "https://opencommerce-ds.vercel.app/components/tag-filter",
  "markdownUrl": "https://opencommerce-ds.vercel.app/r/tag-filter.md",
  "examples": [
    {
      "path": "examples/tag-filter.tsx",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\n\nimport { TagFilter } from \"@/components/ui/tag-filter\";\n\nexport function Site() {\n  const [tags, setTags] = useState([\"travel\", \"software\"]);\n  return (\n    <TagFilter\n      tags={tags}\n      tagOptions={[\"travel\", \"software\", \"office\", \"meals\", \"ads\"]}\n      onAddTag={(tag) => setTags((t) => [...t, tag])}\n      onRemoveTag={(tag) => setTags((t) => t.filter((x) => x !== tag))}\n    />\n  );\n}\n",
      "exports": [
        "Site"
      ],
      "requires": [
        "tag-filter"
      ],
      "packages": [],
      "previews": [
        {
          "name": "Site",
          "url": "https://opencommerce-ds.vercel.app/preview/tag-filter/Site"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Tag filter",
  "description": "Searchable tag picker with removable selected filters.",
  "surface": "site",
  "kinds": [
    "chip"
  ],
  "requires": [
    "theme",
    "popover",
    "utils"
  ],
  "packages": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/ui/tag-filter.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Check, X } from \"lucide-react\";\n\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\n// A tag filter. The picked tags are the brand's chips (the mono in blue on the pale blue, 2px corners), each clearing\n// itself; then a small white key, Tags, opens the brand's floating panel with a small search inside it (a search mark\n// and a borderless field over a 9 percent rule; rows that take the pale blue under the pointer), each row ticked in\n// blue when picked.\nfunction TagFilter({\n  tags,\n  tagOptions,\n  onAddTag,\n  onRemoveTag,\n  className,\n}: {\n  tags: string[];\n  tagOptions: string[];\n  onAddTag: (tag: string) => void;\n  onRemoveTag: (tag: string) => void;\n  className?: string;\n}) {\n  const [open, setOpen] = React.useState(false);\n  const [query, setQuery] = React.useState(\"\");\n  const options = [...new Set([...tags, ...tagOptions])].filter((tag) => tag.toLowerCase().includes(query.trim().toLowerCase()));\n  const selected = new Set(tags);\n\n  return (\n    <div data-slot=\"tag-filter\" className={cn(\"flex flex-wrap items-center gap-1.5\", className)}>\n      {tags.map((tag) => (\n        <button\n          key={tag}\n          type=\"button\"\n          onClick={() => onRemoveTag(tag)}\n          aria-label={`Clear tag filter ${tag}`}\n          className=\"flex h-7 items-center gap-1 rounded-chip bg-blue-5 pr-2 pl-2.5 font-mono text-bar leading-none text-fg-brand outline-none transition-[background-color] duration-160 ease-out hover:bg-blue-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus-ring motion-reduce:transition-none [&_svg]:opacity-70\"\n        >\n          {tag}\n          <X className=\"size-3\" strokeWidth={1.75} />\n        </button>\n      ))}\n      <Popover open={open} onOpenChange={setOpen}>\n        <PopoverTrigger\n          aria-label=\"Filter by tags\"\n          className=\"flex h-8 items-center gap-2 rounded-control border border-hairline bg-surface px-3 font-sans text-body-sm whitespace-nowrap text-fg outline-none transition-[background-color,scale] duration-160 ease-out hover:bg-subtle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus-ring active:scale-97 data-[state=open]:bg-subtle motion-reduce:transition-[background-color] motion-reduce:active:scale-100\"\n        >\n          Tags\n          <svg aria-hidden viewBox=\"0 0 10 6\" className=\"h-1.5 w-2.5 fill-current text-fg-muted\">\n            <path d=\"M0 0h10L5 6z\" />\n          </svg>\n        </PopoverTrigger>\n        <PopoverContent align=\"end\" sideOffset={6} className=\"w-64 p-0\">\n          <label className=\"flex items-center gap-2.5 border-b border-line px-3.5 text-fg-muted\">\n            <svg aria-hidden viewBox=\"0 0 16 16\" className=\"size-3.75 shrink-0 fill-none stroke-current [stroke-linecap:round] [stroke-width:1.5]\">\n              <circle cx=\"7\" cy=\"7\" r=\"4.75\" />\n              <path d=\"M10.5 10.5 14 14\" />\n            </svg>\n            <input\n              autoFocus\n              type=\"search\"\n              value={query}\n              onChange={(event) => setQuery(event.target.value)}\n              placeholder=\"Search tags\"\n              aria-label=\"Search tags\"\n              className=\"h-11 min-w-0 flex-1 bg-transparent font-sans text-body-sm text-fg caret-primary outline-none placeholder:text-fg-faint [&::-webkit-search-cancel-button]:hidden\"\n            />\n          </label>\n          <ul className=\"max-h-64 overflow-y-auto p-1\">\n            {options.length === 0 && <li className=\"px-2.5 py-2 font-sans text-body-sm text-fg-muted\">{tagOptions.length === 0 ? \"No tags yet\" : \"No matching tags\"}</li>}\n            {options.map((tag) => {\n              const isSelected = selected.has(tag);\n              return (\n                <li key={tag}>\n                  <button\n                    type=\"button\"\n                    aria-pressed={isSelected}\n                    onClick={() => (isSelected ? onRemoveTag(tag) : onAddTag(tag))}\n                    className={cn(\n                      \"flex h-9 w-full items-center gap-2.5 rounded-menu px-2.5 text-left font-sans text-body-sm outline-none hover:bg-blue-5 focus-visible:bg-blue-5\",\n                      isSelected ? \"text-fg\" : \"text-fg-muted\",\n                    )}\n                  >\n                    <span className=\"min-w-0 flex-1 truncate\">{tag}</span>\n                    <Check className={cn(\"size-3.5 shrink-0 text-fg-brand\", !isSelected && \"invisible\")} strokeWidth={1.75} />\n                  </button>\n                </li>\n              );\n            })}\n          </ul>\n        </PopoverContent>\n      </Popover>\n    </div>\n  );\n}\n\nexport { TagFilter };\n"
    }
  ]
}
