feat(data-table): redesign Filters — add-filter panel + segmented chips#395
feat(data-table): redesign Filters — add-filter panel + segmented chips#395itsprade wants to merge 13 commits into
Conversation
… chips - Replace the add-filter popover (nested selects) with a Menu flyout: all filterable fields visible on open; number/date/time fields get a condition step (field ▸ condition ▸ value), others go straight to value. - Segmented filter chips: field │ operator │ value │ ✕, with a searchable operator dropdown on the operator segment. - Add `FilterConfig.chooseOperator` (defaults true for number/date/datetime/time, false otherwise) to opt a field in/out of the condition step. - Friendlier operator labels (is / is not / is between / is any of …), enum multi-select summarized as "N items", compact date ranges. - Extract shared `FilterCheckbox` + `EnumOptionList` for a consistent primary-colored checkbox across all filter surfaces. - `Menu.Content` gains `position.trackAnchor` to pin the menu when opening it shifts the trigger. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second add-filter UI for A/B testing alongside the nested-menu variant:
a single popover with three columns — field ▸ condition ▸ value. Live-commit
(enum toggles, text/number on Enter, date on pick); reuses the shared value
editors and helpers. Gated behind `DataTable.Filters` `addFilterVariant`
("menu" default | "panel"). Example page gets an A/B toggle to compare.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ned, 3 cols for all multi-op fields Feedback from testing variant B: - Add an explicit Apply button (draft state per type; panel stays open). - Fixed popup height so switching field/condition never resizes/jumps. - disableAnchorTracking so the panel stays put when adding a chip shifts the trigger. - Show the condition column for every field with >1 operator (e.g. string), not just number/date. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er jump) The popover DatePicker (and an inline Calendar) opened/re-rendered inside the panel popover, causing it to jump and even dismiss on interaction. Use the typed segmented DateField instead — no nested popover, stable panel. Reverts the panel height back to the compact fixed size. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r button The chip used border-input + flat bg-background, so in dark mode it read flatter than the outline "Add filter" button. Align its border/background tokens with the outline Button (border-border / dark:border-input, dark:bg-input/30) so the pill matches the add-filter container in both themes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ariant A Consolidate the two-variant filter experiment into the single panel design (option B) and drop the nested-menu variant A code. - Remove variant-A pieces from toolbar.tsx (AddFilterMenu, field/operator submenus, submenu editors) — DataTableFilters now always renders the 3-column add-filter panel (field ▸ condition ▸ value, Apply) + segmented chips - Revert incidental shared-component changes made for variant A: Menu.trackAnchor, PositionProps.trackAnchor, and FilterConfig.chooseOperator - Date filters: single = inline Calendar, range = From/To DatePicker (stopgap until the date-range calendar lands) - Update tests for the panel-only flow - Rewrite the Filters/FilterConfig docs and refresh the vite example page Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR redesigns the public DataTable.Filters experience in @tailor-platform/app-shell by replacing the old “add filter” flow with a 3-column add-filter panel and rendering active filters as segmented chips (field / operator / value / remove), while updating i18n labels, docs, and the Vite example to match.
Changes:
- Replaces the previous add-filter popover with a single pinned AddFilterPanel (field ▸ condition ▸ value) and adds a searchable operator picker in the chip operator segment.
- Updates chip/value editors (date uses inline
Calendar; date ranges use From/ToDatePickerstopgap) and adds new label keys + test rewrites for the new interaction model. - Updates theme transparency rules, Vite example copy/comments, and DataTable docs; adds a changeset.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/components/data-table/toolbar.tsx | Implements the new add-filter panel + segmented chips, operator search, updated value formatting, and shared checkbox UI. |
| packages/core/src/components/data-table/toolbar.test.tsx | Updates/extends tests to drive the new segmented chip structure and add-filter panel behavior. |
| packages/core/src/components/data-table/i18n.ts | Adds new placeholder labels and updates operator wording (notably in en). |
| packages/core/src/assets/themes/cream.css | Extends “transparent chrome” styling to the new filter chip surface. |
| packages/core/src/assets/themes/bloom.css | Extends “transparent chrome” styling to the new filter chip surface. |
| examples/vite-app/src/pages/data-table/page.tsx | Updates example comments/copy for the new date filter behavior (Calendar vs From/To DatePickers). |
| docs/components/data-table.md | Updates docs to describe the add-filter panel and segmented chips; revises date filter documentation accordingly. |
| .changeset/data-table-filter-redesign.md | Adds a minor changeset describing the redesign and behavior changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const [operator, setOperator] = useState<FilterOperator>( | ||
| config ? DEFAULT_OPERATOR[config.type] : "eq", | ||
| ); | ||
|
|
||
| const selectField = (name: string) => { | ||
| setFieldName(name); | ||
| const col = columns.find((c) => c.filter.field === name); | ||
| if (col) setOperator(DEFAULT_OPERATOR[col.filter.type]); | ||
| }; |
| control.addFilter( | ||
| field, | ||
| operator, | ||
| toAddFilterSubmittedValue(type, operator, text), | ||
| type === "string" ? { caseSensitive: false } : undefined, | ||
| ); |
| } else if (nextOp === "between") { | ||
| const v = filter.value; | ||
| if (config.type === "number") { | ||
| const n = typeof v === "number" ? v : Number(v); | ||
| control.addFilter(config.field, nextOp, { min: n, max: n }); | ||
| } else { | ||
| const s = v == null ? "" : String(v); | ||
| control.addFilter(config.field, nextOp, { min: s, max: s }); | ||
| } | ||
| } else { | ||
| const range = (filter.value ?? {}) as { min?: unknown; max?: unknown }; | ||
| const lower = range.min ?? range.max ?? ""; | ||
| control.addFilter( | ||
| config.field, | ||
| nextOp, | ||
| config.type === "number" ? Number(lower) : String(lower), | ||
| ); | ||
| } |
| // Summarize multiple selections as "2 statuses" rather than listing them. | ||
| if (labels.length > 1 && label) { | ||
| return `${labels.length} ${pluralizeNoun(label)}`; | ||
| } |
| {items.length === 0 && ( | ||
| <div className="astw:px-2 astw:py-1.5 astw:text-sm astw:text-muted-foreground">—</div> | ||
| )} |
- Add panel now seeds a field's operator/value/caseSensitive from an active filter, so re-opening a filtered field and hitting Apply preserves it instead of resetting to the default (which could overwrite or remove the filter) - Localize the enum count summary via a `filterEnumCount` i18n label (en plural, ja counter) — no more "2 ステータスs" - Guard the chip's operator switch against NaN/Infinity when seeding numbers - Localize the operator-search empty state (`filterOperatorNoResults`) and render it as <output> for assistive tech - Add regression tests for the panel seeding + case-sensitivity preservation Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
- Ideally when the data is invalid, the Apply button would be disabled:
Currently it is enabled but clicking it does nothing
Related: min value can be >= max and nothing complains
It's a little odd to be able to:
- Add a filter on "Amount" (e.g.)
- Click "Add filter"
- Then try to add another filter on "Amount"
- Doing this updates the existing Amount filter - even though we clicked the "Add filter" button
It may not be obvious it will update the existing filter – the blue dot helps
3a.
For the Time filter, "Between" uses native time inputs, but any of the other operators just use a basic text input
3b.
For DateTime filter, it's just text inputs; I appreciate we're waiting on a DateTimeField / ..Picker but just flagging this is not a good UX and any application already using a DateTime filter may regress. End-customers are unlikely to be able to reliably enter a RFC timestamp
…rror, layout
- Reversed "between" range shows an inline error ("Max must be greater than or
equal to Min") beneath the inputs and keeps the commit disabled
- Add a per-field Clear (icon-only, tooltip) beside Apply/Update and a sticky
"Clear all filters" at the bottom of the field column (shown only when active)
- Right-align the Add filter trigger (funnel icon) so it stays put as chips are
added; panel right-aligns to it with a fixed width so it never jumps between
2- and 3-column fields and fits the inline calendar (now centered)
- Soften the Min/Max label cells (bg-muted) so they aren't near-black in dark mode
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…chip polish - Datetime filter now uses a date DatePicker + native time box (combined into a local "YYYY-MM-DDTHH:mm:ss") instead of a raw ISO text field — single and between. Stopgap until a dedicated DateTime picker component lands (swaps 1:1). - Relax the datetime validator to accept the local no-zone value; format the datetime chip value as a locale date + time to match the column renderer. - Truncate long chip values (uuid / long strings) so they don't stretch the toolbar. - Expand the vite demo to one column per supported filter type (adds uuid, boolean, datetime, time) so every editor/operator path is exercisable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… time Match the single-date experience: the panel's single-datetime editor now shows the inline Calendar up front with a labelled "Choose time" picker beneath it, instead of the compact segmented date field. The chip editor and the "between" range keep the compact date-picker + time box so they stay short. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rom/To tabs Replace the two stacked From/To fields in the panel's date and datetime range editors with a single inline calendar and a From/To tab bar on top. Picking a date edits the active tab; each tab shows its current value. datetime tabs also carry the "Choose time" picker. Reuses the single-value inline editors; the chip editor keeps the compact From/To fields. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r fits Bump the fixed panel height (h-96 → h-[32rem]) so the tallest editor — the datetime "between" (From/To tabs + inline calendar + "Choose time") — shows the time picker without clipping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
32rem left a large empty gap below the datetime range's time picker; 28rem fits the tallest editor snugly without clipping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Redesigns
DataTable.Filtersfor a faster, more direct filtering experience. This replaces the old popover-with-nested-selects add flow with a single add-filter panel and turns each active filter into a segmented chip.What changed
field │ operator │ value │ ✕. The operator segment opens a searchable dropdown to switch the condition; the value segment reopens the type-specific editor;✕removes the filter.is,is not,is between,is any of,is none of, …), multi-select enum values summarized as "N items" (e.g. "2 statuses"), compact locale-formatted date ranges, and a single consistent primary-colored checkbox across every filter surface.exact date/after/before) render the inlineCalendar; theis betweenrange renders twoDatePickerFrom / To fields.Date range — temporary From/To
The
is betweendate range currently uses twoDatePickerFrom / To fields as a stopgap. Once #388 (DateRangePicker+RangeCalendar) merges, this will be swapped for a single range calendar. The filter value shape ({ min, max }) is unchanged, so that swap is UI-only.Notes
<DataTable.Filters />takes no new props; all changes are internal to the toolbar plus new i18n label keys (en + ja).Menuanchor-tracking,PositionProps,FilterConfig.chooseOperator) have been reverted to keep this PR focused.docs/components/data-table.md) and the vite example page are updated for the new flow.Verification
type-checkclean ·lint0 warnings / 0 errors ·test1376 passed ·build✓ ·oxfmt --checkclean🤖 Generated with Claude Code