PathLogs UI
Русский

Widgets

FilterBar

A filter bar assembled from a description of the fields.

A filter bar assembled from a description of the fields. The bar does not know what to filter — the fields array does. So a new condition is added with one entry rather than edits across five files.

Installation

terminal
npx @toimetdev/pathlogs-ui add filter-bar

Example

Сохранённые:
  • UI-12БагИмпорт досок из Trello падает на больших проектахМТ
  • UI-14ФичаЖивые обновления доски по SSEАСДК
  • UI-15РефакторингВынести разбор Markdown в отдельный модульДК
  • UI-16ФичаWIP-лимиты у колонокМТ
  • UI-17ФичаКритический путь на диаграмме ГантаАС
  • UI-9БагТултипы обрезались в колонках доскиДК

Describing the fields

A field is a label, a control type and a «does this item match» function. The ready-made matchers cover almost everything:

tsx
import {
  textMatcher, equalsMatcher, includesMatcher, type FilterField,
} from "@/components/ui/filter-bar/filterModel";

export const taskFilterFields: FilterField<Task>[] = [
  {
    key: "q", label: "Search", kind: "text", placeholder: "title or number",
    matches: textMatcher((t) => [t.title, t.number]),
  },
  {
    key: "status", label: "Status", kind: "select", anyLabel: "Any",
    options: statusOptions,
    matches: equalsMatcher((t) => t.status),
  },
  {
    key: "assignee", label: "Assignee", kind: "select", anyLabel: "Any",
    options: members.map((m) => ({ value: m.id, label: m.name })),
    matches: includesMatcher((t) => t.assignees),
  },
];
textMatcher(pick: (item) => (string | number | null)[]) => Matcher

A case-insensitive substring across several properties at once: «12» finds task №12, «pay» finds «Payment page».

equalsMatcher(pick: (item) => string | null) => Matcher

An exact match on one property.

includesMatcher(pick: (item) => { id: string }[]) => Matcher

The value is among the related entities: assignees, labels.

A matcher of your own is an ordinary (item, value) => booleanfunction: a date range, «overdue», «unassigned».

The query string

The state serialises to an ordinary query string. Saved presets and the address bar hold it in exactly that form:

tsx
serializeFilter(fields, filter);  // "status=TODO&assignee=u1"
parseFilter(fields, query);       // back into state

So a link to a filtered list opens as precisely what it was, and a saved filter is just a string in the database.

Props

fields*FilterField<T>[]

The field descriptions.

value*FilterState

The current state.

onChange*(next: FilterState) => void

A change.

savedFilters{ id, name, query }[]

Presets. Without handlers they can only be applied.

onSaveFilter(name, query) => void | Promise

Saving a preset. Without it there is no «save filter» button.

onDeleteFilter(id: string) => void | Promise

Deleting a preset.

matchedCount / totalCountnumber

How many passed the filter and how many there are — the caption under the bar.

compactboolean

defaults to: false

A dense layout: the bar sits inside a header rather than standing on its own.

labelsFilterBarLabels

Captions. English by default.