Filter Bar

v0.2.0

Composable toolbar: debounce-ready search input, single/multi-select dropdown filters with removable badges, clear-all, and an action slot.

View as Markdown

Install

npx shadcn@latest add @signalos/filter-bar

Requires 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

PropTypeDefaultDescription
key*string-
value*string-
iconReactNode-

FilterBarSearchConfig

PropTypeDefaultDescription
value*string-
onChange*(value: string) => void-
placeholderstring | undefined"Search..."

FilterBarProps

PropTypeDefaultDescription
searchFilterBarSearchConfig | undefined-Controlled search input; omit to hide the search box.
filtersFilterConfig[] | undefined-Dropdown filters, single- or multi-select.
onClearAll(() => void) | undefined-Shows a Clear-all button (when anything is active) and receives the click.
childrenReactNode-Extra actions rendered at the right edge (buttons, toggles).
classNamestring | undefined-
data-testidstring | 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.0

Changelog

filter-bar

0.2.0

  • Add a "data-testid" prop to FilterBar (@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 visible focus-visible:ring-2 indicator 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 semantic bg-accent / text-accent-foreground tokens, so a client theme override reaches them. Values are unchanged today since --bg-accent maps 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

  • DropdownFilter lifted near-verbatim from signal-core-ui common/dropdown-filter.tsx (single/multi discriminated union preserved). Non-token badge classes (bg-primary-50 etc.) normalized to brand-teal-subtle / text-accent tokens.
  • New FilterBar composition: search input, N dropdown filters, conditional clear-all, and a right-aligned action slot.