dodil/erp-ui

public/r/filter-bar.json

{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "filter-bar",
  "title": "FilterBar",
  "description": "Controlled search and select filters with clear.",
  "dependencies": [
    "@radix-ui/themes"
  ],
  "files": [
    {
      "path": "src/components/filter-bar/FilterBar.tsx",
      "content": "/**\n * FilterBar — search and select filters above a list. Controlled: the screen owns the values (so they can\n * live in the URL and survive a reload), the bar only shows and changes them.\n */\nimport { Button, Flex, Select, TextField } from \"@radix-ui/themes\";\nimport type { ReactNode } from \"react\";\n\nexport interface FilterOption { value: string; label: string }\n\nexport interface SelectFilter {\n  key: string;\n  label: string;\n  options: FilterOption[];\n  value: string | null;\n  onChange: (value: string | null) => void;\n}\n\nexport interface FilterBarProps {\n  search?: { value: string; onChange: (v: string) => void; placeholder?: string };\n  filters?: SelectFilter[];\n  /** Buttons on the right, e.g. \"New partner\". */\n  actions?: ReactNode;\n}\n\nconst ALL = \"__all__\";\n\nexport function FilterBar({ search, filters = [], actions }: FilterBarProps) {\n  const active = (search?.value ?? \"\") !== \"\" || filters.some((f) => f.value !== null);\n  return (\n    <Flex gap=\"2\" align=\"center\" wrap=\"wrap\" role=\"search\">\n      {search && (\n        <TextField.Root\n          aria-label={search.placeholder ?? \"Search\"}\n          placeholder={search.placeholder ?? \"Search\"}\n          value={search.value}\n          onChange={(e) => search.onChange(e.target.value)}\n          style={{ minWidth: 220 }}\n        />\n      )}\n      {filters.map((f) => (\n        <Select.Root key={f.key} value={f.value ?? ALL} onValueChange={(v) => f.onChange(v === ALL ? null : v)}>\n          <Select.Trigger aria-label={f.label} placeholder={f.label} />\n          <Select.Content>\n            <Select.Item value={ALL}>All {f.label.toLowerCase()}</Select.Item>\n            {f.options.map((o) => (\n              <Select.Item key={o.value} value={o.value}>{o.label}</Select.Item>\n            ))}\n          </Select.Content>\n        </Select.Root>\n      ))}\n      {active && (\n        <Button variant=\"ghost\" color=\"gray\" onClick={() => { search?.onChange(\"\"); filters.forEach((f) => f.onChange(null)); }}>\n          Clear\n        </Button>\n      )}\n      {actions && <Flex ml=\"auto\" gap=\"2\">{actions}</Flex>}\n    </Flex>\n  );\n}\n",
      "type": "registry:component",
      "target": "erp-ui/components/filter-bar/FilterBar.tsx"
    }
  ],
  "type": "registry:component"
}