From ee99cc383d1d9a175a18604576d706e188afb4e0 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 28 May 2026 12:13:29 +0100 Subject: [PATCH] feat(ActiveFilterPills): inactive-state pill styling and a11y polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds visual treatment for filters that are present in state but not applied to the current query (e.g. column doesn't exist on the active source). Inactive pills render with a dashed `--color-border-emphasis` border, transparent background, 0.75 opacity, and strikethrough text plus a multi-line tooltip explaining why they aren't applied. Also refreshes the excluded (!=) pill to use the `--color-bg-danger` / `--color-text-danger` design tokens instead of raw Mantine red, adds deterministic `data-testid="active-filter-pill-"` hooks for E2E, an `aria-label` on the remove button, and a `data-invalid` attribute on the pill wrapper. The component exposes two new optional props — `invalidFields` and `invalidFieldReason` — so callers can drive the inactive state without any behavior change for existing call sites. Co-authored-by: Cursor --- .../app/src/components/ActiveFilterPills.tsx | 95 +++++++++++++++---- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/packages/app/src/components/ActiveFilterPills.tsx b/packages/app/src/components/ActiveFilterPills.tsx index 687eea057e..763cafbb19 100644 --- a/packages/app/src/components/ActiveFilterPills.tsx +++ b/packages/app/src/components/ActiveFilterPills.tsx @@ -59,22 +59,41 @@ const pillStyle = { function FilterPill({ pill, + isInvalid, + invalidReason, onRemove, }: { pill: PillItem; + isInvalid?: boolean; + invalidReason?: string; onRemove: () => void; }) { const isExcluded = pill.type === 'excluded'; const operator = isExcluded ? ' != ' : pill.type === 'range' ? ': ' : ' = '; + const tooltipLabel = isInvalid + ? (invalidReason ?? + `Filter not applied: "${pill.field}" isn't a column on the current source. It will reapply if you switch back.`) + : `${pill.field}${operator}${pill.value}`; + + const showDangerAccent = isExcluded && !isInvalid; + return ( - + {pill.field} - + {operator} - + {pill.value} @@ -109,9 +151,23 @@ function FilterPill({ export const ActiveFilterPills = memo(function ActiveFilterPills({ searchFilters, + invalidFields, + invalidFieldReason, ...flexProps }: { searchFilters: FilterStateHook; + /** + * Field names whose filters are present in state but not applied to the + * current query (e.g. column doesn't exist on the active source). These + * render in a muted, strikethrough, dashed-border style and are preserved + * so the user can switch back without losing their selection. + */ + invalidFields?: Set; + /** + * Optional tooltip override for invalid pills. Receives the field name and + * returns the tooltip text. + */ + invalidFieldReason?: (field: string) => string; } & FlexProps) { const { filters, setFilterValue, clearFilter, clearAllFilters } = searchFilters; @@ -161,13 +217,20 @@ export const ActiveFilterPills = memo(function ActiveFilterPills({ return ( - {visiblePills.map((pill, i) => ( - handleRemove(pill)} - /> - ))} + {visiblePills.map((pill, i) => { + const isInvalid = invalidFields?.has(pill.field) ?? false; + return ( + handleRemove(pill)} + /> + ); + })} {!expanded && hiddenCount > 0 && (