Filter Bar
v0.2.0Composable toolbar: debounce-ready search input, single/multi-select dropdown filters with removable badges, clear-all, and an action slot.
Install
npx shadcn@latest add @signalos/filter-barRequires a configured @signalos registry and a valid SIGNALOS_REGISTRY_TOKEN - get access. Registry dependencies (@signalos/tokens, @signalos/utils, @signalos/badge, @signalos/button, @signalos/checkbox, @signalos/input, @signalos/popover, @signalos/select) are pulled automatically.
Preview
Example & code
filter-bar.example.tsx
// Example: controlled filter bar - search, single + multi select, clear-all.
import { useState } from "react"
import { Flag, User } from "lucide-react"
import { FilterBar } from "@/components/widgets/filter-bar/FilterBar"
import type { DropdownOption } from "@/components/widgets/filter-bar/FilterBar.types"
const severityOptions: DropdownOption[] = [
{ key: "critical", value: "Critical" },
{ key: "high", value: "High" },
{ key: "medium", value: "Medium" },
]
const ownerOptions: DropdownOption[] = [
{ key: "chen", value: "M. Chen" },
{ key: "okafor", value: "A. Okafor" },
]
export default function Example() {
const [query, setQuery] = useState("")
const [severity, setSeverity] = useState<string>()
const [owners, setOwners] = useState<string[]>([])
// Feed these three values into your list query (thunk, hook, etc.).
return (
<FilterBar
search={{
value: query,
onChange: setQuery,
placeholder: "Search signals...",
}}
filters={[
{
id: "severity",
options: severityOptions,
placeholder: "Severity",
icon: <Flag className="h-4 w-4 text-muted-foreground" />,
value: severity,
onChange: (option) => setSeverity(option.key),
},
{
id: "owner",
multiSelect: true,
options: ownerOptions,
placeholder: "Owner",
icon: <User className="h-4 w-4 text-muted-foreground" />,
value: owners,
onChange: (options) => setOwners(options.map((o) => o.key)),
},
]}
onClearAll={() => {
setQuery("")
setSeverity(undefined)
setOwners([])
}}
/>
)
}
Props
DropdownOption
| Prop | Type | Default | Description |
|---|---|---|---|
| key* | string | - | |
| value* | string | - | |
| icon | ReactNode | - |
FilterBarSearchConfig
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | - | |
| onChange* | (value: string) => void | - | |
| placeholder | string | undefined | "Search..." |
FilterBarProps
| Prop | Type | Default | Description |
|---|---|---|---|
| search | FilterBarSearchConfig | undefined | - | Controlled search input; omit to hide the search box. |
| filters | FilterConfig[] | undefined | - | Dropdown filters, single- or multi-select. |
| onClearAll | (() => void) | undefined | - | Shows a Clear-all button (when anything is active) and receives the click. |
| children | ReactNode | - | Extra actions rendered at the right edge (buttons, toggles). |
| className | string | undefined | - | |
| data-testid | string | undefined | "filter-bar" | Test identifier rendered as `data-testid` on the root element. |
Supporting types
export type DropdownFilterProps = {
options: DropdownOption[]
triggerIcon?: ReactNode
/** @default "Select..." */
placeholder?: string
className?: string
style?: CSSProperties
} & (
| {
multiSelect?: false
selectedKey?: string
onValueChange: (option: DropdownOption) => void
}
| {
multiSelect: true
selectedKey?: string[]
onValueChange: (options: DropdownOption[]) => void
}
)export type FilterConfig = {
/** Stable id, also used as the filter's data-testid suffix. */
id: string
options: DropdownOption[]
placeholder?: string
icon?: ReactNode
className?: string
} & (
| {
multiSelect?: false
value?: string
onChange: (option: DropdownOption) => void
}
| {
multiSelect: true
value?: string[]
onChange: (options: DropdownOption[]) => void
}
)npm dependencies
lucide-react@^1.7.0Changelog
filter-bar
0.2.0
- Add a
"data-testid"prop toFilterBar(@default "filter-bar"); the search input's id now derives from it (${dataTestId}-search,${dataTestId}-clear). Default behavior is unchanged. - Fix
DropdownFilter's trigger stripping the focus ring (focus:ring-0) with no replacement; keyboard users now get a visiblefocus-visible:ring-2indicator on both the single- and multi-select trigger. - The multi-select "remove" chip is now keyboard-operable (
tabIndex={0}+ Enter/Space), not just clickable. - Swap raw brand-tint utilities (
bg-brand-muted-blue/5,bg-brand-teal-subtle) used for ordinary hover/selected states to the semanticbg-accent/text-accent-foregroundtokens, so a client theme override reaches them. Values are unchanged today since--bg-accentmaps to the same brand-teal color; only the indirection changes.
0.1.1
- Content sync fix to bring source in line with published registry payload.
0.1.0
DropdownFilterlifted near-verbatim fromsignal-core-uicommon/dropdown-filter.tsx(single/multi discriminated union preserved). Non-token badge classes (bg-primary-50etc.) normalized tobrand-teal-subtle/text-accenttokens.- New
FilterBarcomposition: search input, N dropdown filters, conditional clear-all, and a right-aligned action slot.