Виджеты
FilterBar
Панель фильтров, собираемая из описания полей.
Панель фильтров, собираемая из описания полей. Что фильтровать, панель не знает — знает массив fields. Поэтому новое условие добавляется одной записью, а не правкой пяти файлов.
Установка
npx @toimetdev/pathlogs-ui add filter-barПример
- UI-12БагИмпорт досок из Trello падает на больших проектахМТ
- UI-14ФичаЖивые обновления доски по SSEАСДК
- UI-15РефакторингВынести разбор Markdown в отдельный модульДК
- UI-16ФичаWIP-лимиты у колонокМТ
- UI-17ФичаКритический путь на диаграмме ГантаАС
- UI-9БагТултипы обрезались в колонках доскиДК
const [filter, setFilter] = useState(emptyFilter(taskFilterFields));
const matched = tasks.filter((t) => matchesFilter(taskFilterFields, filter, t));
<FilterBar
fields={taskFilterFields}
value={filter}
onChange={setFilter}
matchedCount={matched.length}
totalCount={tasks.length}
savedFilters={presets}
onSaveFilter={(name, query) => saveFilterAction(projectId, name, query)}
onDeleteFilter={deleteFilterAction}
/>Описание полей
Поле — это подпись, тип контрола и функция «подходит ли элемент». Готовые матчеры покрывают почти всё:
import {
textMatcher, equalsMatcher, includesMatcher, type FilterField,
} from "@/components/ui/filter-bar/filterModel";
export const taskFilterFields: FilterField<Task>[] = [
{
key: "q", label: "Поиск", kind: "text", placeholder: "название или номер",
matches: textMatcher((t) => [t.title, t.number]),
},
{
key: "status", label: "Статус", kind: "select", anyLabel: "Любой",
options: statusOptions,
matches: equalsMatcher((t) => t.status),
},
{
key: "assignee", label: "Исполнитель", kind: "select", anyLabel: "Любой",
options: members.map((m) => ({ value: m.id, label: m.name })),
matches: includesMatcher((t) => t.assignees),
},
];| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
textMatcher | (pick: (item) => (string | number | null)[]) => Matcher | — | Подстрока без учёта регистра сразу по нескольким свойствам: «12» находит задачу №12, «оплат» — «Страница оплаты». |
equalsMatcher | (pick: (item) => string | null) => Matcher | — | Точное совпадение одного свойства. |
includesMatcher | (pick: (item) => { id: string }[]) => Matcher | — | Значение есть среди связанных сущностей: исполнители, метки. |
textMatcher(pick: (item) => (string | number | null)[]) => MatcherПодстрока без учёта регистра сразу по нескольким свойствам: «12» находит задачу №12, «оплат» — «Страница оплаты».
equalsMatcher(pick: (item) => string | null) => MatcherТочное совпадение одного свойства.
includesMatcher(pick: (item) => { id: string }[]) => MatcherЗначение есть среди связанных сущностей: исполнители, метки.
Свой матчер — обычная функция (item, value) => boolean: диапазон дат, «просрочено», «без исполнителя».
Строка запроса
Состояние сериализуется в обычный query string. В этом же виде оно лежит в сохранённых пресетах и в адресной строке:
serializeFilter(fields, filter); // "status=TODO&assignee=u1"
parseFilter(fields, query); // обратно в состояниеПоэтому ссылка на отфильтрованный список открывается ровно тем же, чем была, а сохранённый фильтр — это просто строка в базе.
Пропсы
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
fields* | FilterField<T>[] | — | Описание полей. |
value* | FilterState | — | Текущее состояние. |
onChange* | (next: FilterState) => void | — | Изменение. |
savedFilters | { id, name, query }[] | — | Пресеты. Без обработчиков они только применяются. |
onSaveFilter | (name, query) => void | Promise | — | Сохранение пресета. Без него нет кнопки «сохранить фильтр». |
onDeleteFilter | (id: string) => void | Promise | — | Удаление пресета. |
matchedCount / totalCount | number | — | Сколько прошло отбор и сколько всего — подпись под панелью. |
compact | boolean | false | Плотная раскладка: панель встраивается в шапку, а не стоит отдельно. |
labels | FilterBarLabels | — | Подписи. По умолчанию английские. |
fields*FilterField<T>[]Описание полей.
value*FilterStateТекущее состояние.
onChange*(next: FilterState) => voidИзменение.
savedFilters{ id, name, query }[]Пресеты. Без обработчиков они только применяются.
onSaveFilter(name, query) => void | PromiseСохранение пресета. Без него нет кнопки «сохранить фильтр».
onDeleteFilter(id: string) => void | PromiseУдаление пресета.
matchedCount / totalCountnumberСколько прошло отбор и сколько всего — подпись под панелью.
compactbooleanпо умолчанию: false
Плотная раскладка: панель встраивается в шапку, а не стоит отдельно.
labelsFilterBarLabelsПодписи. По умолчанию английские.