Skip to content

Commit 7077475

Browse files
committed
Change dynamic label implementation for selectpanel to use a counter
1 parent 226ae5f commit 7077475

10 files changed

Lines changed: 197 additions & 79 deletions

File tree

app/components/primer/alpha/select_panel.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ def with_avatar_item(**system_arguments)
291291
:none,
292292
].freeze
293293

294+
DEFAULT_DYNAMIC_LABEL_TYPE = :count
295+
DYNAMIC_LABEL_TYPE_OPTIONS = [:label, :count].freeze
296+
294297
DEFAULT_BANNER_SCHEME = :danger
295298
BANNER_SCHEME_OPTIONS = [
296299
DEFAULT_BANNER_SCHEME,
@@ -351,6 +354,7 @@ def with_avatar_item(**system_arguments)
351354
# @param preload [Boolean] Whether to preload search results when the page loads. If this option is false, results are loaded when the panel is opened.
352355
# @param dynamic_label [Boolean] Whether or not to display the text of the currently selected item in the show button.
353356
# @param dynamic_label_prefix [String] If provided, the prefix is prepended to the dynamic label and displayed in the show button.
357+
# @param dynamic_label_type [Symbol] Controls what is shown as the dynamic label. `:label` shows the selected item's text; `:count` shows the number of selected items. Only applies when `select_variant: :multiple`. <%= one_of(Primer::Alpha::SelectPanel::DYNAMIC_LABEL_TYPE_OPTIONS) %>
354358
# @param dynamic_aria_label_prefix [String] If provided, the prefix is prepended to the dynamic label and set as the value of the `aria-label` attribute on the show button.
355359
# @param body_id [String] The unique ID of the panel body. If not provided, the body ID will be set to the panel ID with a "-body" suffix.
356360
# @param list_arguments [Hash] Arguments to pass to the underlying <%= link_to_component(Primer::Alpha::ActionList) %> component. Only has an effect for the local fetch strategy.
@@ -374,6 +378,7 @@ def initialize(
374378
preload: DEFAULT_PRELOAD,
375379
dynamic_label: false,
376380
dynamic_label_prefix: nil,
381+
dynamic_label_type: DEFAULT_DYNAMIC_LABEL_TYPE,
377382
dynamic_aria_label_prefix: nil,
378383
body_id: nil,
379384
list_arguments: {},
@@ -405,6 +410,7 @@ def initialize(
405410
@show_filter = show_filter
406411
@dynamic_label = dynamic_label
407412
@dynamic_label_prefix = dynamic_label_prefix
413+
@dynamic_label_type = fetch_or_fallback(DYNAMIC_LABEL_TYPE_OPTIONS, dynamic_label_type, DEFAULT_DYNAMIC_LABEL_TYPE)
408414
@dynamic_aria_label_prefix = dynamic_aria_label_prefix
409415
@loading_label = loading_label
410416
@loading_description_id = nil
@@ -435,6 +441,7 @@ def initialize(
435441
data: { select_variant: @select_variant, fetch_strategy: @fetch_strategy, open_on_load: open_on_load }.tap do |data|
436442
data[:dynamic_label] = dynamic_label if dynamic_label
437443
data[:dynamic_label_prefix] = dynamic_label_prefix if dynamic_label_prefix.present?
444+
data[:dynamic_label_type] = @dynamic_label_type if dynamic_label
438445
data[:dynamic_aria_label_prefix] = dynamic_aria_label_prefix if dynamic_aria_label_prefix.present?
439446
end
440447
}
@@ -517,6 +524,8 @@ def initialize(
517524

518525
if icon.present?
519526
Primer::Beta::IconButton.new(icon: icon, **system_arguments)
527+
elsif @dynamic_label && @dynamic_label_type == :count && @select_variant == :multiple
528+
ShowButton.new(dynamic_label_prefix: @dynamic_label_prefix, **system_arguments)
520529
else
521530
Primer::Beta::Button.new(**system_arguments)
522531
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
module Primer
4+
module Alpha
5+
class SelectPanel
6+
# This component is part of <%= link_to_component(Primer::Alpha::SelectPanel) %> and should not be
7+
# used as a standalone component.
8+
class ShowButton < Primer::Component
9+
def initialize(dynamic_label_prefix: nil, **button_args)
10+
@dynamic_label_prefix = dynamic_label_prefix
11+
@button_args = button_args
12+
end
13+
14+
def call
15+
leading_visual = @button_args.delete(:leading_visual)
16+
trailing_visual = @button_args.delete(:trailing_visual)
17+
button = Primer::Beta::Button.new(**@button_args)
18+
button.with_leading_visual_icon(**leading_visual) if leading_visual&.key?(:icon)
19+
button.with_trailing_visual_icon(**trailing_visual) if trailing_visual&.key?(:icon)
20+
21+
prefix_span = @dynamic_label_prefix.present? ? content_tag(:span, "#{@dynamic_label_prefix} ", class: "color-fg-muted", data: { target: "select-panel.labelPrefix" }) : nil
22+
counter = render(Primer::Beta::Counter.new(
23+
count: 0,
24+
hidden: true,
25+
data: { target: "select-panel.counterLabel" }
26+
))
27+
render(button) do
28+
safe_join([prefix_span, counter].compact)
29+
end
30+
end
31+
end
32+
end
33+
end
34+
end

app/components/primer/alpha/select_panel_element.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export class SelectPanelElement extends HTMLElement {
7676
@target bannerErrorElement: HTMLElement
7777
@target bodySpinner: HTMLElement
7878
@target liveRegion: LiveRegionElement
79+
@target counterLabel: HTMLElement
80+
@target labelPrefix: HTMLElement
7981

8082
filterFn?: FilterFn
8183

@@ -111,7 +113,7 @@ export class SelectPanelElement extends HTMLElement {
111113
get dynamicLabelPrefix(): string {
112114
const prefix = this.getAttribute('data-dynamic-label-prefix')
113115
if (!prefix) return ''
114-
return `${prefix}:`
116+
return this.dynamicLabelType === 'count' ? prefix : `${prefix}:`
115117
}
116118

117119
get dynamicAriaLabelPrefix(): string {
@@ -132,6 +134,10 @@ export class SelectPanelElement extends HTMLElement {
132134
this.toggleAttribute('data-dynamic-label', value)
133135
}
134136

137+
get dynamicLabelType(): string {
138+
return this.getAttribute('data-dynamic-label-type') || 'count'
139+
}
140+
135141
get invokerElement(): HTMLButtonElement | null {
136142
const id = this.querySelector('dialog')?.id
137143
if (!id) return null
@@ -955,13 +961,20 @@ export class SelectPanelElement extends HTMLElement {
955961
if (!invokerLabel) return
956962
this.#originalLabel ||= invokerLabel.textContent || ''
957963
let itemLabel: string | undefined
964+
958965
if (this.selectVariant === 'single') {
959966
itemLabel = this.querySelector(`[${this.ariaSelectionType}=true] .ActionListItem-label`)?.textContent
960-
} else if (this.selectVariant === 'multiple') {
961-
itemLabel = Array.from(this.querySelectorAll(`[${this.ariaSelectionType}=true] .ActionListItem-label`))
962-
.map(label => label.textContent.trim())
963-
.join(', ')
967+
} else {
968+
if (this.dynamicLabelType === 'count') {
969+
this.#setDynamicCountLabel()
970+
return
971+
} else {
972+
itemLabel = Array.from(this.querySelectorAll(`[${this.ariaSelectionType}=true] .ActionListItem-label`))
973+
.map(label => label.textContent?.trim() ?? '')
974+
.join(', ')
975+
}
964976
}
977+
965978
itemLabel ||= this.#originalLabel
966979
if (itemLabel) {
967980
const prefixSpan = document.createElement('span')
@@ -979,6 +992,28 @@ export class SelectPanelElement extends HTMLElement {
979992
}
980993
}
981994

995+
#setDynamicCountLabel() {
996+
if (!this.counterLabel) return
997+
const count = this.querySelectorAll(`[${this.ariaSelectionType}=true]`).length
998+
999+
if (count === 0) {
1000+
this.counterLabel.hidden = true
1001+
this.invokerElement?.classList.add('color-fg-muted')
1002+
this.labelPrefix?.classList.add('color-fg-muted')
1003+
} else {
1004+
this.counterLabel.textContent = String(count)
1005+
this.counterLabel.setAttribute('title', String(count))
1006+
this.counterLabel.hidden = false
1007+
this.invokerElement?.classList.remove('color-fg-muted')
1008+
this.labelPrefix?.classList.remove('color-fg-muted')
1009+
}
1010+
1011+
if (this.dynamicAriaLabelPrefix) {
1012+
const ariaLabel = count === 0 ? this.dynamicAriaLabelPrefix : `${this.dynamicAriaLabelPrefix} ${count}`
1013+
this.invokerElement?.setAttribute('aria-label', ariaLabel)
1014+
}
1015+
}
1016+
9821017
#updateInput() {
9831018
if (this.selectVariant === 'single') {
9841019
const input =

demo/package-lock.json

Lines changed: 0 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 0 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)