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"
}