feat(FilterSummaryBar): active-filter summary bar for data grids - #400
Conversation
Ported from waggleline's grid pages: 'X of Y records — N filters active · Clear all', hidden while idle unless showWhenIdle, tinted with the primary accent when narrowing. Teal hardcodes become primary tokens, FontAwesome becomes lucide, and every visible string is overridable for i18n.
There was a problem hiding this comment.
Pull request overview
Adds a new FilterSummaryBar component to the design system to display an always-on “filtered results” summary for grids/lists (filtered/total counts, active filter count, and search state), with an optional idle display mode.
Changes:
- Introduces
FilterSummaryBarReact component with styling, accessibility semantics (role="status"), and configurable labels. - Adds unit tests and Storybook stories (including an interactive demo) for the new component.
- Exposes the component via package exports (
src/index.ts) and build entrypoints (tsup.config.ts).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tsup.config.ts | Adds build entrypoint for FilterSummaryBar. |
| src/index.ts | Re-exports FilterSummaryBar from the public package API. |
| src/components/FilterSummaryBar/index.ts | Barrel export for component + props. |
| src/components/FilterSummaryBar/FilterSummaryBar.tsx | New summary bar component implementation and public props. |
| src/components/FilterSummaryBar/FilterSummaryBar.test.tsx | Unit tests covering idle/active states, pluralization, clear action, and label overrides. |
| src/components/FilterSummaryBar/FilterSummaryBar.stories.tsx | Storybook stories including interactive “add a filter” example. |
Suppressed comments (1)
src/components/FilterSummaryBar/FilterSummaryBar.tsx:125
- The combined state renders a hardcoded "+ search" suffix, which is user-visible and not overridable for i18n (and it also differs from the
searchActiveLabeltext used in the search-only state).
{hasSearchText && activeFilterCount === 0 && (
<> — {searchActiveLabel}</>
)}
{hasSearchText && activeFilterCount > 0 && <> + search</>}
{!isFiltering && showWhenIdle && <> — {allVisibleLabel}</>}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Deploying ui with
|
| Latest commit: |
66cd9e4
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://d2dc74ba.ui-6d0.pages.dev |
| Branch Preview URL: | https://feat-filter-summary-bar.ui-6d0.pages.dev |
…rops for full i18n coverage
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
src/components/FilterSummaryBar/FilterSummaryBar.tsx:84
- The live region is missing explicit
aria-liveconfiguration. Elsewhere in the codebase, status regions typically specifyaria-live(and oftenaria-atomic) to ensure consistent announcements (e.g.DropzoneOverlay.tsx:97-99,ConnectionStatus.tsx:386-389). Adding these here makes the announcement behavior more predictable across screen readers.
<div
ref={ref}
role="status"
className={cn(
src/components/FilterSummaryBar/FilterSummaryBar.tsx:148
- Add a
displayNamefor the forwarded-ref component. Many components in this repo set it for better DevTools output (e.g.Avatar.tsx:122,Alert.tsx:106).
});
src/components/FilterSummaryBar/FilterSummaryBar.tsx:140
- The "Clear all" button doesn’t include the project’s standard
focus-visiblering styles, which can make keyboard focus hard to see. Other inline icon buttons include these classes (e.g.Alert.tsx:91-95,CountBadge.tsx:43-44).
className={cn(
'ms-auto flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-colors',
'text-primary-700 hover:bg-primary-500/15 dark:text-primary-300'
)}
… add safelist coverage to preset twins
… record label, localized filter count, safelist dedupe
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/components/FilterSummaryBar/FilterSummaryBar.tsx:94
{...props}is spread afterrole="status", so a consumer-providedroleprop can override the status role and break the intended assistive-tech announcement behavior. If the role should always bestatus, spread props first (or striprolefrom incoming props) so it can’t be overridden.
src/components/FilterSummaryBar/FilterSummaryBar.test.tsx:67
- This assertion assumes an en-US thousands separator ("8,911"), but
toLocaleString()output depends on the runtime’s default locale and the test setup doesn’t configure one. Using a regex here avoids locale-dependent failures while still verifying the content.
expect(screen.getByRole('status')).toHaveTextContent('1 of 8,911 record —');
…or locale-agnostic tests
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (4)
Previously missed (3) — in code that hasn't changed since the last review.
src/components/FilterSummaryBar/FilterSummaryBar.tsx:94
{...props}is spread afterrole="status", so a consumer-providedrolewill override the live-region behavior (and potentially break the accessibility guarantee implied by the component). Move the spread beforeroleto keep the status role enforced.
src/components/FilterSummaryBar/FilterSummaryBar.tsx:133- PR description says the idle state (when
showWhenIdleis true and no filters/search are active) “reads "all records visible"”, but the current render still includes counts/record labels and appends “— all records visible”. If the intended UX is label-only while idle, the render logic should switch to onlyallVisibleLabel(and tests/stories updated accordingly).
src/components/FilterSummaryBar/FilterSummaryBar.tsx:10 - The prop doc says consumers can pass
0whentotalCountis unknown, but0can also be a valid known total (empty dataset). Suggest documentingundefinedas the “unknown” signal and letting0mean an actual total of zero.
This issue also appears on line 110 of the same file.
src/components/FilterSummaryBar/FilterSummaryBar.tsx:110
totalCountis rendered only when it’s truthy ({totalCount ? ...}), which means a validtotalCount={0}(empty dataset) will incorrectly omit the “of 0” portion. Consider checking for a finite number so0still renders andundefined/NaN do not.
{totalCount ? (
…utage timeouts The visual-test webServer (npx http-server) and the a11y job (pnpm dlx http-server / wait-on) downloaded packages from the npm registry at run time. During registry outages the download hangs, the server never starts, and Playwright's 120s webServer timeout fails the job (run 33820036956). Pin both as devDependencies and use pnpm exec so test startup requires no network.
…e idle state Move role="status" from the container div to the text span so the clear button no longer lives inside the live region (avoids repeated button announcements on count changes). When idle with showWhenIdle, render only allVisibleLabel instead of counts + label, matching the documented contract. Addresses Copilot review comments r3929798497 and r3929798523.
…live-region attrs, displayName Addresses the suppressed Copilot review findings: - totalCount rendered via a finite-number check so a real total of 0 shows 'of 0' (doc now says omit = unknown); test added - clear button gets the standard focus-visible ring classes (safelisted focus-visible:ring-offset-2 in both preset twins) - explicit aria-live=polite / aria-atomic=true on the status span, matching DropzoneOverlay/ConnectionStatus conventions - displayName set per repo convention
Ports the bar that anchors every filtered waggleline grid into the design system: "1,204 of 8,911 records — 3 filters active · Clear all".
What it does
filteredCount/totalCount/activeFilterCountwith correct pluralization, plus search-only and filters-plus-search statesshowWhenIdle(then reads "all records visible" with no clear action)role="status"announces changes to assistive techrecordsLabel,filterLabel/filtersLabel,clearLabel, …)Screenshots
Provenance
Port of
waggleline/app/imports/ui/components/filters/FilterSummaryBar.tsx(used across its AG Grid pages); teal hardcodes → primary tokens, FontAwesome → lucideFilter/X,ml-auto→ logicalms-auto.Testing
typecheck/lint/format/rtl:scanclean; combined batch suite 651/651