[DREAM-738] Add dynamic selection counter to SelectPanel - #482
Conversation
🦋 Changeset detectedLatest commit: c396b9d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
1c03c8c to
f3efd5a
Compare
There was a problem hiding this comment.
Pull request overview
Adds an opt-in, self-updating selection counter to Primer::Alpha::SelectPanel show buttons, implemented with built-in Catalyst wiring so downstream consumers don’t need custom JavaScript.
Changes:
- Extend
with_show_buttonwithcounter:andcounter_arguments:and render a trailingCounterwired viadata-targets. - Update
SelectPanelElementto keep the counter text/visibility in sync on connect, item activation, and remote fetch. - Add Lookbook preview plus component/system tests and a changeset entry.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| app/components/primer/alpha/select_panel.rb | Adds counter:/counter_arguments: support and wires the trailing counter via data-targets. |
| app/components/primer/alpha/select_panel_element.ts | Implements live counter updates driven by the tracked selected-items map. |
| previews/primer/alpha/select_panel_preview.rb | Registers the new “With counter” Lookbook preview. |
| previews/primer/alpha/select_panel_preview/with_counter.html.erb | Adds a new preview template demonstrating the counter behavior. |
| test/components/alpha/select_panel_test.rb | Adds unit tests asserting counter rendering, defaults, and argument merging. |
| test/system/alpha/select_panel_test.rb | Adds a system test validating counter visibility and updates on selection changes. |
| .changeset/select-panel-dynamic-counter.md | Documents the user-facing change and bumps package version. |
7d69076 to
c2a2be3
Compare
|
d6c0b6b to
54a5efe
Compare
Adds an opt-in, self-updating selection counter to the SelectPanel show button, so consumers no longer need a bespoke Stimulus controller to keep a Counter in sync with the selection. `with_show_button` gains `counter:` and `counter_arguments:`. When `counter: true`, the button renders a trailing Counter wired to a Catalyst target, defaulting to `hide_if_zero: true`. The target is forced via `merge_data` so a caller's `data:` cannot unwire it. Passing `counter:` to an icon button raises ArgumentError. SelectPanelElement writes the live count into the counter's text and title on connect, on item activation, and after fetch. The count comes from the tracked selectedItems Map rather than a DOM query, so it stays correct in remote panels where the selected items have been filtered out of the DOM. Button renders a trailing counter twice, a visible aria-hidden copy and an sr-only copy, so the wiring uses plural data-targets and updates both. The client writes the raw count, so Counter's formatting arguments only apply to the initial server render.
54a5efe to
c396b9d
Compare
| # | ||
| # @param icon [String] Name of <%= link_to_octicons %> to use instead of text. If an [icon](https://primer.style/octicons/usage-guidelines/) is provided, a <%= link_to_component(Primer::Beta::IconButton) %> will be rendered. Otherwise a <%= link_to_component(Primer::Beta::Button) %> will be rendered. | ||
| # @param counter [Boolean] When true, renders a dynamic selection counter on the show button that updates as items are selected. | ||
| # @param counter_arguments [Hash] System arguments forwarded to the trailing Counter (e.g. `scheme:`). The `data-target` wiring is applied automatically and cannot be overridden. Note that the client writes the raw selected count, so `Counter`'s formatting arguments (`limit:`, `round:`) only affect the initial server render. |
There was a problem hiding this comment.
Small wording question: since data-targets is merged with the caller’s targets, would “cannot be removed” be a little clearer than “cannot be overridden”?
| # @param counter_arguments [Hash] System arguments forwarded to the trailing Counter (e.g. `scheme:`). The `data-target` wiring is applied automatically and cannot be overridden. Note that the client writes the raw selected count, so `Counter`'s formatting arguments (`limit:`, `round:`) only affect the initial server render. | ||
| # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %>. | ||
| renders_one :show_button, lambda { |icon: nil, **system_arguments| | ||
| renders_one :show_button, lambda { |icon: nil, counter: false, counter_arguments: {}, **system_arguments| |
There was a problem hiding this comment.
is supporting counter: true for single-select panels intentional? It looks like the counter would only show 0 or 1. I wasn’t sure whether the feature was intended only for multi-select panels.
| else | ||
| Primer::Beta::Button.new(**system_arguments) | ||
| button = Primer::Beta::Button.new(**system_arguments) | ||
| button.with_trailing_visual_counter(**dynamic_counter_arguments(counter_arguments)) if counter |
There was a problem hiding this comment.
Could combining counter: true with dynamic_label: true make the accessible button name a little repetitive? For example, it might contain both “Item 2, Item 3” and “2”.
Work package: DREAM-738
What are you trying to accomplish?
Teaches
Primer::Alpha::SelectPanelto show a self-updating count of selected items in its show button, with no custom JavaScript required downstream.Consumers (e.g. the OpenProject backlogs filter) previously had to hand-write a Stimulus controller just to keep a
Counterin the show button in sync with the selection. That boilerplate now lives in the component.with_show_buttongainscounter:(Boolean) +counter_arguments:(Hash). Whencounter: true, the button auto-renders a trailingCounterwired to a Catalyst target, defaulting tohide_if_zero: true. The target is forced viamerge_dataso a caller'sdata:cannot unwire it.counter:on an icon button raisesArgumentError(non-production).SelectPanelElementwrites the live count (selectedItems.length, the tracked Map) into the counter on connect, on every item activation, and after fetch. Hides at zero.dynamic_labeltext path is untouched and may coexist with the counter.Integration
No production code changes required to adopt. Existing
SelectPanelusages are unaffected; the counter is opt-in viacounter: true. Downstream consumers can delete their bespoke counter-sync Stimulus controllers.static/*.jsondocs regenerate via CI on push.List the issues that this change affects.
Closes DREAM-738
Risk Assessment
Additive, opt-in API in the
alphanamespace. Legacydynamic_labelpath untouched (regression-tested). Easily reverted.What approach did you choose and why?
The counter is rendered server-side via Primer
Button's nativetrailing_visual_counterslot and only augmented on the client —SelectPanelElementwrites the count into a Catalyst target. This keeps with the library convention (server renders, client augments) rather than introducing client-side templating.Count is read from the tracked
selectedItemsMap rather than a DOM query, so it stays correct in remote/filtered panels where selected items aren't currently in the DOM.Alternatives discarded: a
<template>+{{count}}placeholder syntax (collides with lit-html/Angular interpolation downstream) and web-component<slot>s (require shadow DOM; these elements are light-DOM).Anything you want to highlight for special attention from reviewers?
Primer
Buttonrenders a trailing counter twice — a visiblearia-hiddencopy and ansr-onlycopy (app/components/primer/beta/button.html.erb). The wiring therefore uses pluraldata-targets/@targets dynamicLabelCountsand updates both copies, which also keeps the screen-reader text in sync.The count is keyed on each item's
data-value; items need values to be counted (consistent with existing selection tracking).Accessibility
sr-onlycounter copy is kept in sync, so the selected count is announced to screen readers.Merge checklist