Filter Bar
The row that pins above a log list: a search field for an id, then one chip
per filter. A chip with nothing chosen reads + Status; with a value it reads
Status: Failed and carries its own clear. Each chip opens a menu, a
keyboard-first list of options or presets and a from / to pair for a date
range. The bar is built from the library’s own controls: a chip is an outlined
Button, and what it opens is a Menu, so it wears the same hairline, shadow
and entrance as every other menu in both themes. The bar is fully controlled:
the page owns the query and hands it back changed, so the list can refetch
from one place (the URL, a store).
Query: {"search":"","status":"failed"}
Import
import { FilterBar } from "@photon-ai/pho-ui/components/filter-bar";
Definitions
definitions describes the chips, in order. A select filter lists options
(value and label); multiple lets several be chosen at once, and past
eight options the menu grows a search field (searchable says so either
way). A long option (a route, a path) widens the menu up to a cap and
truncates past it, with the whole text on hover. A date-range filter (type: "date-range") lists relative
presets (last hour, 24 hours, 7 days, 30 days unless told otherwise) over a
from / to pair.
const definitions: FilterDefinition[] = [
{ key: "date", label: "Date", type: "date-range" },
{
key: "status",
label: "Status",
options: [
{ value: "succeeded", label: "Succeeded" },
{ value: "failed", label: "Failed" },
],
},
{
key: "type",
label: "Event type",
multiple: true,
options: EVENT_TYPES,
},
];
Values
filters is a map from definition key to value: a string for a select,
string[] when multiple, and { from, to, preset? } for a date range.
An absent key, an empty string, an empty list, or a range with both sides
open all count as unset. onFiltersChange receives the whole map; keys the
bar does not own pass through untouched. A preset stores an absolute ISO
timestamp in from and its label in preset; the from / to fields store
plain YYYY-MM-DD days.
const [filters, setFilters] = useState<FilterValues>({});
const [search, setSearch] = useState("");
<FilterBar
definitions={definitions}
filters={filters}
onFiltersChange={setFilters}
search={search}
onSearchChange={setSearch}
searchPlaceholder="Find an event by id"
/>;
Pinning above a list
The bar has no scroll container of its own; chips wrap. sticky pins it to
the top of the nearest scroll pane and paints the surface it sits on
(--pho-surface), so rows scroll under it. Put it directly above the
LogList in the same pane.
<div className="overflow-y-auto">
<FilterBar sticky … />
<LogList.Root>…</LogList.Root>
</div>
Props
- definitions —
FilterDefinition[]— one chip per entry, in order - filters —
FilterValues— the values by key; absent or empty means unset - onFiltersChange —
(next: FilterValues) => void— the whole map, changed - search / onSearchChange —
string/(value: string) => void— the field renders only withonSearchChange; Escape clears it - searchPlaceholder —
string(defaultSearch) — a teaching phrase, “Find an event by id” - searchLabel —
string— the field’s accessible name when the placeholder is too short to be one - sticky —
boolean— pin to the top of the nearest scroll pane - children — controls after the chips (an export button)
- …plus any
<div>attribute
Helpers: isFilterSet(value), formatFilterValue(definition, value) (the words a set chip shows), and FILTER_DATE_PRESETS.
Accessibility
- Chips are buttons. The trigger opens a menu (
aria-haspopup,aria-expanded); the clear beside it is its own button, namedClear status filter. An unset chip is namedStatus filter; a set one reads its visible words,Status: Failed. - The menu is keyboard-first. Focus lands on the search field when
there is one, else on the rows; arrows move, Enter picks, Escape closes
back onto the chip. Options are
menuitemradiorows (ormenuitemcheckboxwhenmultiple) and carryaria-checked. - Date fields are native. The from / to pair are
<input type="date">with visible labels; they stand outside the preset list so the arrow keys step dates as usual.
Best practices
- Keep chip labels to a word or two in sentence case:
Status,Event type. - Sort options the way the user thinks, and let
multiplecarry the cases where a user wants two states at once (failed and pending). - Send the filter state to the server as it is; the bar formats nothing but
the chip. Parse
from/towithnew Date()on either side. - One bar per list, pinned above it. A second row of controls goes in
children, after the chips.