Skip to content

feat(data-table): redesign Filters — add-filter panel + segmented chips#395

Open
itsprade wants to merge 13 commits into
mainfrom
feat/data-table-filter-panel-variant
Open

feat(data-table): redesign Filters — add-filter panel + segmented chips#395
itsprade wants to merge 13 commits into
mainfrom
feat/data-table-filter-panel-variant

Conversation

@itsprade

@itsprade itsprade commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Redesigns DataTable.Filters for 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.

Supersedes #385. That draft explored a nested-menu add flow (variant A). After trying both, we're shipping the panel (variant B) as the single design and this PR drops the variant-A code.

Claude 2026-07-20 17 46 12

What changed

  • Add-filter panel — one popover laid out in up to three columns: field ▸ condition ▸ value. The condition column appears for fields with more than one operator (number, date/time, string); single-operator fields (enum, uuid) go straight to the value. Values are drafted and committed with an Apply button, and the panel stays open (fixed height, pinned in place) so several filters can be added in a row without it jumping around.
  • Segmented filter chips — each active filter renders as 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.
  • Friendlier operator labels (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.
  • Date filters use app-shell's own date components — single-date operators (exact date / after / before) render the inline Calendar; the is between range renders two DatePicker From / To fields.

Date range — temporary From/To

The is between date range currently uses two DatePicker From / 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

  • Public API surface is unchanged — <DataTable.Filters /> takes no new props; all changes are internal to the toolbar plus new i18n label keys (en + ja).
  • Incidental shared-component changes made while exploring variant A (Menu anchor-tracking, PositionProps, FilterConfig.chooseOperator) have been reverted to keep this PR focused.
  • Docs (docs/components/data-table.md) and the vite example page are updated for the new flow.

Verification

  • type-check clean · lint 0 warnings / 0 errors · test 1376 passed · build ✓ · oxfmt --check clean
  • Please try the DataTable + Filters page in the vite example to review the interaction.

🤖 Generated with Claude Code

itsprade and others added 6 commits July 20, 2026 18:07
… 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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/To DatePicker stopgap) 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.

Comment on lines +177 to +185
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]);
};
Comment on lines +374 to +379
control.addFilter(
field,
operator,
toAddFilterSubmittedValue(type, operator, text),
type === "string" ? { caseSensitive: false } : undefined,
);
Comment on lines +603 to +620
} 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),
);
}
Comment on lines +1689 to +1692
// Summarize multiple selections as "2 statuses" rather than listing them.
if (labels.length > 1 && label) {
return `${labels.length} ${pluralizeNoun(label)}`;
}
Comment on lines +792 to +794
{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>

@interacsean interacsean left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Ideally when the data is invalid, the Apply button would be disabled:
Image

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

itsprade and others added 6 commits July 21, 2026 14:19
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants