Skip to content

Add reusable CheckboxListFilter with overflow-triggered sticky search - #253

Merged
einari merged 6 commits into
mainfrom
feature/reusable-checkbox-filter
Sep 8, 2026
Merged

Add reusable CheckboxListFilter with overflow-triggered sticky search#253
einari merged 6 commits into
mainfrom
feature/reusable-checkbox-filter

Conversation

@einari

@einari einari commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Consolidates two duplicate checkbox-list filter implementations (Chronicle's own FilterPanel option list and Stagehand's bespoke CheckboxPicker) into one reusable renderer, per Cratis/Chronicle#3991.

Added

  • CheckboxListFilter, a reusable checkbox/radio picker with a search box that becomes sticky and only shows once the option list overflows its bounded height (Reuse filter type Chronicle#3991)

Changed

  • FilterPanel's internal option-list renderer now delegates to CheckboxListFilter, gaining a scroll-capped list and overflow-triggered sticky search; the public FilterPanelProps/FilterDefinition API is unchanged and fully backward compatible (Reuse filter type Chronicle#3991)

FilterPanel's checkbox/radio option list was a one-off inline renderer
whose inline search box only ever appeared when a filter explicitly
opted in via `searchable: true` - so a long, unsearched list scrolled
past dozens of rows with no way to narrow it. Extract that renderer
into a standalone, exported CheckboxListFilter (Filter/CheckboxListFilter.tsx)
so applications can reuse the same checkbox/radio picker outside a
FilterPanel, following the pattern proven by Stagehand's bespoke
CheckboxPicker escape hatch.

The list is now bounded to a fixed, scrollable box (.pv-option-list),
and `searchable` becomes a tri-state: `true`/`false` keep forcing the
search box on or off exactly as before, while the new default
(`undefined`) shows it only when the option list actually overflows
its box - measured via useOptionListOverflow against an off-screen
mirror of every option, so a live search filtering the visible rows
never itself shrinks the measured content and un-decides that the box
needed a search box in the first place. The search box is pinned to
the top of the list's own scroll container via CSS position: sticky.

FilterPanel's OptionList is now a thin adapter over CheckboxListFilter,
keeping FilterPanelProps and FilterDefinition unchanged for existing
consumers such as Chronicle Workbench.
Covers the pure overflow decision (optionListOverflows) on plain
numbers, plus CheckboxListFilter itself: no search box for a list
that fits, a sticky search box pinned to the top of the box once the
list overflows, search/false/true forcing that decision explicitly,
label filtering (case-insensitive, no-matches message), and
checkbox/radio selection and toggling.

The DOM-based specs stub ResizeObserver, Element.scrollHeight, and
getComputedStyle(...).maxHeight the same way other suites in this
package work around jsdom's missing layout engine, letting a spec
dictate whether the option list "overflows" without a real browser.
CheckboxListFilter.stories.tsx demonstrates the standalone component:
a short list with no search box, a 30-option list whose search box
grows out of the overflow and filters/selects correctly, and both
explicit searchable overrides. Two new FilterPanel stories show the
same behaviour through the full anchored panel - a 30-repository
filter that grows its own search box next to a 3-option filter that
never does.
@einari einari added the minor label Sep 8, 2026
CheckboxListFilter.stories.tsx plus two new stories on FilterPanel
bring the totals from 67/277/67 to 68/283/68 (modules/stories/docs
pages). While updating the ratchet, also fixed the adapter and
matrix log lines: they printed literal "277"/"67" text instead of
the computed counts, so they were already silently stale before
this change - now they read from the same variables the check
enforces.
The off-screen mirror list used to decide whether a search box is
needed duplicated each option's real label/count as literal DOM
text, so a search that filters the real list down still leaves the
mirror's matching text discoverable by any text-content query (e.g.
Testing Library's getByText/queryByText, which do not consider
aria-hidden). CSS generated content occupies the same layout space
for measurement purposes without ever becoming DOM text, so the
mirror can no longer be mistaken for a real, currently-visible row.
Five options is enough for Department's own option list to overflow
its fixed-height box, so it now legitimately grows a search box of
its own - inheriting this panel's placeholder text by design (see
for_FilterPanel/when_a_filter_group_has_no_own_search_placeholder.tsx).
The story's play function queried by that placeholder text globally
and started matching both boxes. Scope to .pv-search, the panel's
own top-level search container, rather than either per-group one.
@einari
einari merged commit 03b7496 into main Sep 8, 2026
41 checks passed
@cratis-direct
cratis-direct Bot deleted the feature/reusable-checkbox-filter branch September 8, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant