feat(table): unified filter pill UI with overflow strip#9638
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
459bdd8 to
9a009bd
Compare
|
@kirangadhave I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
2 issues found across 17 files
Architecture diagram
sequenceDiagram
participant Table as DataTable
participant Ctx as FilterEditorProvider
participant Header as ColumnHeader
participant Pills as FilterPills
participant Ghost as Ghost Row (hidden)
participant AddBtn as AddFilterButton
participant Editor as FilterPillEditor
participant ChipPicker as CompactChipRow / Chip
participant Overflow as Overflow Popover
Note over Table,Overflow: Unified filter pill UI with overflow strip
Table->>Ctx: Provide requestAddFilter callback
Table->>Pills: Render with filters, addFilterSnapshot, onAddFilterSnapshotChange
Table->>Pills: Pass table, calculateTopKRows
Header->>Ctx: useFilterEditor()
Ctx-->>Header: requestAddFilter
alt User clicks "Filter" in column header
Header->>+Ctx: requestAddFilter({ columnId })
Ctx->>Table: setAddFilterSnapshot(buildEditorSnapshot(column))
Table->>Pills: update addFilterSnapshot prop
Pills->>AddBtn: snapshot != null
AddBtn->>Editor: render FilterPillEditor with snapshot
else User clicks "Filter by values" in column header
Header->>+Ctx: requestAddFilter({ columnId, operator: "in" })
Ctx->>Table: setAddFilterSnapshot(buildEditorSnapshot(column, { operator: "in" }))
Table->>Pills: update addFilterSnapshot prop
Pills->>AddBtn: snapshot != null
AddBtn->>Editor: render FilterPillEditor with "in" operator
end
alt User clicks "+" button on pills strip
AddBtn->>AddBtn: buildEditorSnapshot(first editable column)
AddBtn->>AddBtn: set open = true
AddBtn->>Editor: render FilterPillEditor
else User clicks existing filter pill
Pills->>Editor: open FilterPillEditor with pill's snapshot & editIndex
end
Editor->>Editor: User modifies operator/value
Editor->>Table: onApply → setColumnFilters(updater)
alt editIndex undefined (new filter)
Table->>Table: prepend { id: draftColumnId, value } to filters
else editIndex defined (edit existing)
Table->>Table: replace filters[editIndex] = { id: draftColumnId, value }
end
Editor->>Pills: onClose → close popover
Note over Pills,Ghost: Overflow detection uses hidden ghost row
Pills->>Ghost: Always render all pills at natural width (hidden, not displayed)
Ghost-->>Pills: Provide scrollWidth measurement
Pills->>Pills: Compare scrollWidth > clientWidth via ResizeObserver
alt hasOverflow
Pills->>Pills: Apply CSS mask gradient on container
Pills->>Pills: Show +N button at right edge
Pills->>Overflow: open DraggablePopover with full filter list
Overflow-->>Table: "Clear all" → setColumnFilters([])
end
Note over Pills,ChipPicker: Pill rendering
Pills->>ChipPicker: Format value (scalar or multi-value)
alt scalar value
ChipPicker-->>Pills: Render truncated text with tooltip on hover
else multi-value (in/not_in)
ChipPicker->>ChipPicker: CompactChipRow with max 3 visible chips
ChipPicker-->>Pills: Render chips with +N overflow indicator
end
Note over Table: Column header shows persistent filter indicator
Header->>Header: hasFilter → show FilterIcon next to header text
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Introduces the filter pill strip with a + button to add filters and an overflow popover with a 'See all' button gated by scrollWidth detection. Pills open the filter editor directly from a column-header menu, show a persistent indicator on filtered headers, and 'Filter by values' extends to numeric, date, and time columns. Adds value truncation with a full-segments tooltip, inline is_in/not_in chip rendering, and a shared chip primitive reused by the editor picker.
Pills now use the same dateToISODate, exactDateTime, and dateToISOTime helpers as the column cells, and hide 'Filter by values' on date, datetime, and time columns since those should use the range picker. parsePastedRange also accepts 'and' as a separator alongside hyphen, en-dash, em-dash, and 'to'.
Also consolidates the filter test harness and adds column-header menu coverage.
Splits filters.ts into a filters/ folder by concern (types, operators, guards, builders, defaults, serialize, format). Removes the 'select' filter type; text and number columns now carry their own in/not_in membership shape. Consolidates operator groups so OPERATORS_WITHOUT_VALUE and per-type op lists derive from one source, and promotes supportsMembership to a type guard (isMembershipFilterType) used at all call sites. columnEditableType now throws on invalid filterType instead of silently falling back to text, and is reused in handleColumnChange. Drops the trivial getEditableType wrapper and the ColumnFilterValue cast in defaultFilterValueFor's membership branch. Renames renderSortFilterIcon to renderSortIcon and narrows filterType once in context-menu. Editor defaults for date/datetime are now '=='.
dateToISODate/Time/DateTime were generic Date-to-string helpers stranded in filters/serialize.ts after the folder split. Move them to utils/dates alongside the existing date formatters, rename to make the local-time semantics explicit (dateToLocalISO*), and add docstrings clarifying when to use them versus Date.toISOString().
ebdf69d to
9c1a06f
Compare
There was a problem hiding this comment.
Nice! Couple of observations, some are not important now.
- The tooltip seems to appear unexpectedly based on your video. One instance i can reproduce is clicking Accept triggers it to open.
Screen.Recording.2026-05-25.at.12.57.06.PM.mov
- Future: It'd be nice if the blue icon is clickable / redirects to the modal?
- Tiny issue, validation only happens after I select another element / escape out. So, it takes me 2 clicks to apply filter.
- Do you think its possible for 'Filter by values' to automatically open up the list to filter by? Previously was 1 click to filter, now its 2 clicks.
For the PR, I really like the keyboard navigation, pretty sweet.
| items: value.values | ||
| .map((v) => stringifyUnknownValue({ value: v })) | ||
| .toSorted((a, b) => a.localeCompare(b)), | ||
| }; |
There was a problem hiding this comment.
may add
default:
logNever(value.operator);
to all the case statements here
I'll take a look, looks like some bug
That's a good idea
Ah i see, i'll fix this as well
This should be easy to do. good suggestion |
Prevent the pill tooltip from reappearing when the editor popover closes by disabling focus restore on close, and bump the pill tooltip's hover delay so quick interactions don't trigger it.
NumberField commits via onChange only on blur, leaving Apply disabled while the user is still typing. Expose a live onInputText hook so the filter pill editor can validate the in-progress text and enable Apply on the first keystroke that parses.
Open the values list immediately when the editor mounts with no chosen values, so 'Filter by values' takes one click instead of two.
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.9-dev4 |
Summary
Replaces the column-header value-picker submenu with a unified pill-based filter strip below the table. Users add, edit, and remove filters via a single editor popover anchored to the pills row.
UI
+button on the pills strip opens the editor. The column-header···menu routes through the same editor.scrollWidth > clientWidthcheck on the strip itself with aResizeObserver, re-measured on filter content change.is_in/not_inchip rendering via a shared chip primitive reused by the editor picker.Filter by values editor
Filter pill + Tooltip
Internals
filters.tsinto afilters/folder (types,operators,guards,builders,defaults,serialize,format).selectfilter type. Text and number columns carry their ownin/not_inmembership shape directly.supportsMembershipis promoted to a type guard (isMembershipFilterType) used at every call site.selectwas incorrect conceptually.selectis a filter operator rather than a type.NULLISH,MEMBERSHIP,COMPARISON,TEXT_SCALAR,BOOLEAN_VALUE,EMPTY) consolidated soOPERATORS_WITHOUT_VALUEand per-type lists derive from one source.numbercolumn can have nullish, membership, comparision operators, but not text_scalar, empty or boolean operatorsutils/datesand renameddateToLocalISO*to make local-time semantics explicit (vs.Date.toISOString).Demo
Screen.Recording.2026-05-22.at.4.11.59.PM.mov
Test plan
mo.ui.table(df)with text, number, date, datetime, time, and boolean columns. Column···menu shows "Filter by values" only on text and numeric columns.+button on the pill strip and the column-header···Filter item both open the same editor."X and Y","X to Y","X - Y","X – Y"into a date range input all parse into the two date fields.