Pho Design System

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.

200GET
40 ms2 min ago
200POST
43 ms5 min ago
200POST
46 ms12 min ago
500GET
49 ms1 h ago
200POST
52 ms3 h ago
200POST
55 ms2 min ago
200GET
58 ms5 min ago
200POST
61 ms12 min ago
200POST
64 ms1 h ago
200GET
67 ms3 h ago
500POST
70 ms2 min ago
200POST
73 ms5 min ago
200GET
76 ms12 min ago
200POST
79 ms1 h ago
200POST
82 ms3 h ago
200GET
85 ms2 min ago
200POST
88 ms5 min ago
500POST
91 ms12 min ago
200GET
94 ms1 h ago
200POST
97 ms3 h ago
200POST
100 ms2 min ago
200GET
103 ms5 min ago
200POST
106 ms12 min ago
200POST
109 ms1 h ago
<div className="overflow-y-auto">
  <FilterBar sticky … />
  <LogList.Root>…</LogList.Root>
</div>

Props

Helpers: isFilterSet(value), formatFilterValue(definition, value) (the words a set chip shows), and FILTER_DATE_PRESETS.

Accessibility

Best practices