Filter Bar
alphav0.2.0Composite filter bar — search, category pills, date-range picker, and results count, each independently controlled or hidden.
Context
Browse-and-filter UI workhorse: news, blogs, docs, dashboards, e-commerce facets. Three sub-controls (search / chips / date-range) with independent state, plus a combined onChange emitting `{ search, category, dateRange }`. Search debounces 250ms in uncontrolled mode. Date-range Popover composes shadcn Calendar (which transitively brings react-day-picker + date-fns into the project — first user). Migration origin: kasder kas-social-front-v0 NewsMagazineGrid.tsx header section. Composed by `magazine-layout` in the news-domain family.
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/filter-barAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/filter-bar-fixturesPreview
42 items found
Demo source
Usage
When to use
Reach for FilterBar in any browse-and-filter UI: news landing pages, blog archives, doc indexes, file browsers, dashboards, e-commerce category pages. Three sub-controls in one bar — search + category chips + date-range — plus an optional results count. Each is independently controlled-or-uncontrolled.
Minimal example
import { FilterBar } from "@/components/filter-bar";
<FilterBar
categories={[
{ value: "tech", label: "Technology" },
{ value: "design", label: "Design" },
]}
resultsCount={42}
/>;With no controlled props, all 3 sub-controls run uncontrolled with a 250ms debounce on the search. The bar emits no events but the UI works for visual demos.
Controlled (the common case)
const [filters, setFilters] = useState<FilterBarValue>({
search: "",
category: null,
dateRange: { from: undefined, to: undefined },
});
<FilterBar
categories={categories}
search={filters.search}
onSearchChange={(s) => setFilters((v) => ({ ...v, search: s }))}
category={filters.category}
onCategoryChange={(c) => setFilters((v) => ({ ...v, category: c }))}
dateRange={filters.dateRange}
onDateRangeChange={(d) => setFilters((v) => ({ ...v, dateRange: d }))}
resultsCount={filteredItems.length}
/>;Or use the combined onChange emitter:
<FilterBar
categories={categories}
onChange={({ search, category, dateRange }) => {
setFilters({ search, category, dateRange });
}}
/>;Hide sub-controls
<FilterBar categories={categories} hideDateRange />
<FilterBar hideCategories hideSearch />Localization
<FilterBar
categories={categories}
labels={{
searchPlaceholder: "Haber ara...",
allLabel: "Tümü",
dateButtonText: "Tarih Filtrele",
clearDateText: "Tarihi Temizle",
resultsCountText: (n) => `${n} haber bulundu`,
}}
formatDateRange={({ from, to }) => {
const fmt = new Intl.DateTimeFormat("tr-TR", { day: "numeric", month: "short" });
return `${fmt.format(from)} - ${fmt.format(to)}`;
}}
/>;Search debounce
In uncontrolled mode, search is debounced 250ms by default — set searchDebounceMs={0} for instant or increase for slower-changing dropdowns. In controlled mode the debounce is bypassed; consumer's onChange fires on every keystroke. Debounce yourself if you need it.
Accessibility
- Search input is wrapped in
<div role="search">for landmark. - Chip row uses
role="group"witharia-label; each chip is a real<button>witharia-pressed. - Results count has
aria-live="polite"so updates announce. - Clear-date button has
aria-labelfromlabels.clearDateLabel. - Date Popover inherits ARIA from shadcn primitives.
Features
- 3 sub-controls in one bar — search input + category pill row + date-range Popover
- Independently controlled-or-uncontrolled per sub-control
- Combined `onChange` emits `{ search, category, dateRange }` on any change
- Internal 250ms debounce on search in uncontrolled mode (configurable)
- Sub-control hide flags — `hideSearch` / `hideCategories` / `hideDateRange` for partial usage
- "All" sentinel chip clears the category filter (maps to null internally)
- Optional results count with `aria-live='polite'` announcements
- i18n via `labels` prop + `formatDate` / `formatDateRange` callbacks (English defaults; Turkish in dummy-data)
- Layout `align` prop (left | center | right; default center)
- Native `<button aria-pressed>` chips; `role=group` on chip row; `role=search` on search input
- Date-range uses shadcn Popover + Calendar primitives (react-day-picker; date-fns transitive)
- React.memo wrapped