{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "filter-bar",
  "title": "Filter Bar",
  "description": "Toolbar above a table or list with search, filter controls, active filters and actions.",
  "dependencies": [
    "class-variance-authority@^0.7.1",
    "lucide-react@^1.27.0"
  ],
  "registryDependencies": [
    "@pestoui/button",
    "@pestoui/input",
    "@pestoui/style",
    "@pestoui/tag",
    "@pestoui/utils"
  ],
  "files": [
    {
      "path": "pestoui/components/filter-bar.tsx",
      "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Search } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Tag } from \"@/components/ui/tag\"\nimport { cn } from \"@/lib/utils\"\n\n/* Toolbar above a table or list — search, a row of filter controls, and a\n   right-aligned actions cluster. Everything is a slot: the bar itself only\n   owns the wrapping rhythm, so a narrow column drops the actions onto their\n   own line instead of squeezing the controls. */\nconst filterBarVariants = cva(\n  \"group/filter-bar flex w-full flex-wrap items-center gap-2 sm:gap-3\",\n  {\n    variants: {\n      /* Pins the bar to the top of the scroll container. The translucent fill\n         + blur keeps the rows underneath legible as they pass behind it. */\n      sticky: {\n        true: \"sticky top-0 z-20 bg-background/85 py-3 backdrop-blur-sm\",\n        false: \"\",\n      },\n      border: {\n        true: \"border-b border-border pb-3\",\n        false: \"\",\n      },\n    },\n    defaultVariants: {\n      sticky: false,\n      border: false,\n    },\n  }\n)\n\nfunction FilterBar({\n  className,\n  sticky = false,\n  border = false,\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof filterBarVariants>) {\n  return (\n    <div\n      data-slot=\"filter-bar\"\n      data-sticky={sticky ? \"true\" : undefined}\n      className={cn(filterBarVariants({ sticky, border }), className)}\n      {...props}\n    />\n  )\n}\n\n/* Search field with a leading glyph. Sits at h-10 so it lines up with the\n   default button and chip height rather than the taller standalone Input. */\nfunction FilterBarSearch({\n  className,\n  containerClassName,\n  type = \"search\",\n  placeholder = \"Search\",\n  \"aria-label\": ariaLabel = \"Search\",\n  ...props\n}: React.ComponentProps<\"input\"> & { containerClassName?: string }) {\n  return (\n    <div\n      data-slot=\"filter-bar-search\"\n      role=\"search\"\n      className={cn(\"relative min-w-48 flex-1 sm:max-w-72\", containerClassName)}\n    >\n      <Search\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute top-1/2 left-3.5 size-4 -translate-y-1/2 text-muted-foreground\"\n      />\n      <Input\n        type={type}\n        placeholder={placeholder}\n        aria-label={ariaLabel}\n        className={cn(\"h-10 pl-10\", className)}\n        {...props}\n      />\n    </div>\n  )\n}\n\n/* Right-hand cluster — export / new record / view switcher. Takes the full\n   width below sm so it wraps to its own line and stays right-aligned. */\nfunction FilterBarActions({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"filter-bar-actions\"\n      className={cn(\n        \"flex flex-wrap items-center gap-2 max-sm:w-full max-sm:justify-end sm:ml-auto\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\ntype FilterBarActiveFilter = {\n  /** Stable key — handed back to onRemove. */\n  id: string\n  /** Optional field name, rendered before the value as \"Status: Active\". */\n  field?: React.ReactNode\n  value: React.ReactNode\n}\n\n/* Applied-filter readout. Renders nothing at all when nothing is applied, so\n   it can sit unconditionally inside the bar without reserving a dead row. */\nfunction FilterBarActiveFilters({\n  filters,\n  onRemove,\n  onClearAll,\n  label = \"Filters\",\n  clearLabel = \"Clear all\",\n  className,\n  ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  filters: FilterBarActiveFilter[]\n  onRemove?: (id: string) => void\n  onClearAll?: () => void\n  label?: React.ReactNode\n  clearLabel?: React.ReactNode\n}) {\n  if (filters.length === 0) {\n    return null\n  }\n\n  return (\n    <div\n      data-slot=\"filter-bar-active-filters\"\n      className={cn(\"flex w-full flex-wrap items-center gap-2\", className)}\n      {...props}\n    >\n      {label ? (\n        <span className=\"text-caption leading-none font-medium text-muted-foreground\">\n          {label}\n        </span>\n      ) : null}\n\n      <ul\n        data-slot=\"filter-bar-active-filters-list\"\n        /* Preflight strips list-style, which drops list semantics in Safari —\n           restore them explicitly, same as the feed and the stepper do. */\n        role=\"list\"\n        aria-label=\"Applied filters\"\n        className=\"flex min-w-0 flex-wrap items-center gap-2\"\n      >\n        {/* An applied filter arriving is a real event — the list it describes\n            just changed underneath. Keyed by id, so the enter runs on the one\n            tag that was added and never replays on the ones already standing.\n            Enter only, and pitched wider than the tag's own dismissal travels,\n            so if the consumer wires up removal the pair stays asymmetric — the\n            filter lands from further out than it leaves from. */}\n        {filters.map((filter) => (\n          <li\n            key={filter.id}\n            data-slot=\"filter-bar-active-filter\"\n            className=\"animate-in fade-in zoom-in-90 blur-in-3 duration-spring-swap ease-spring-swap\"\n          >\n            {/* secondary, not the solid default — an applied filter is a\n                readout, it must not out-shout the controls above it */}\n            <Tag\n              variant=\"secondary\"\n              onDismiss={onRemove ? () => onRemove(filter.id) : undefined}\n            >\n              {filter.field ? (\n                <>\n                  <span className=\"text-muted-foreground\">{filter.field}:</span>{\" \"}\n                  {filter.value}\n                </>\n              ) : (\n                filter.value\n              )}\n            </Tag>\n          </li>\n        ))}\n      </ul>\n\n      {onClearAll ? (\n        <Button\n          type=\"button\"\n          variant=\"ghost\"\n          size=\"xs\"\n          onClick={onClearAll}\n          className=\"text-muted-foreground hover:text-foreground\"\n        >\n          {clearLabel}\n        </Button>\n      ) : null}\n    </div>\n  )\n}\n\nexport {\n  FilterBar,\n  FilterBarSearch,\n  FilterBarActions,\n  FilterBarActiveFilters,\n  filterBarVariants,\n}\nexport type { FilterBarActiveFilter }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}