Skip to content

Commit 3cce6a2

Browse files
committed
Add single select option to TreeView
1 parent 5d040dc commit 3cce6a2

10 files changed

Lines changed: 112 additions & 11 deletions

File tree

.changeset/full-states-beg.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openproject/primer-view-components': minor
3+
---
4+
5+
Support single selection variant for TreeView

app/components/primer/alpha/tree_view.pcss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,15 @@
197197
}
198198
}
199199

200+
&[aria-checked='false'],
201+
&[aria-selected='false'] {
202+
/* singleselect checkmark */
203+
& .TreeViewItem-singleSelectCheckmark {
204+
visibility: hidden;
205+
transition: visibility 0s linear 200ms;
206+
}
207+
}
208+
200209
&[aria-checked='mixed'] {
201210
& .FormControl-checkbox {
202211
background: var(--control-checked-bgColor-rest);

app/components/primer/alpha/tree_view/node.html.erb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
<%= leading_visual %>
2424
<% end %>
2525
<span class="TreeViewItemContentText"><%= text_content %></span>
26-
<% if trailing_visual? %>
26+
<% if @select_variant == :single %>
27+
<span class="TreeViewItemVisual" aria-hidden="true">
28+
<%= render(Primer::Beta::Octicon.new(icon: :check, classes: "TreeViewItem-singleSelectCheckmark")) %>
29+
</span>
30+
<% elsif trailing_visual? %>
2731
<%= trailing_visual %>
2832
<% end %>
2933
<% end %>

app/components/primer/alpha/tree_view/node.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class Node < Primer::Component
5656

5757
DEFAULT_SELECT_VARIANT = :none
5858
SELECT_VARIANT_OPTIONS = [
59+
:single,
5960
:multiple,
6061
DEFAULT_SELECT_VARIANT
6162
].freeze
@@ -128,7 +129,8 @@ def initialize(
128129
@content_arguments, {
129130
data: {
130131
value: value,
131-
path: @path.to_json
132+
path: @path.to_json,
133+
select_variant: @select_variant
132134
}
133135
}
134136
)
@@ -169,6 +171,10 @@ def merge_system_arguments!(**other_arguments)
169171
private
170172

171173
def before_render
174+
if trailing_visual? && select_variant == :single
175+
raise ArgumentError, "Trailing visuals can't be used in combination with single select mode as the icon is reserved."
176+
end
177+
172178
if leading_action?
173179
@content_arguments[:data] = merge_data(
174180
@content_arguments,

app/components/primer/alpha/tree_view/sub_tree_node.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def initialize(
155155
node_variant: node_variant
156156
)
157157

158-
return if @node.select_variant == :none
158+
return unless @node.select_variant == :multiple
159159

160160
@node.merge_system_arguments!(
161161
data: {

app/components/primer/alpha/tree_view/tree_view.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {SelectStrategy, TreeViewSubTreeNodeElement} from './tree_view_sub_tree_n
33
import {useRovingTabIndex} from './tree_view_roving_tab_index'
44
import type {TreeViewNodeType, TreeViewCheckedValue, TreeViewNodeInfo} from '../../shared_events'
55

6+
export type SelectVariant = 'none' | 'single' | 'multiple'
7+
68
@controller
79
export class TreeViewElement extends HTMLElement {
810
@target formInputContainer: HTMLElement
@@ -199,6 +201,19 @@ export class TreeViewElement extends HTMLElement {
199201

200202
if (!activationSuccess) return
201203

204+
if (this.selectVariant(node) === 'single') {
205+
const currentlyChecked = !this.getNodeCheckedValue(node)
206+
207+
// disallow unchecking checked item in single-select mode
208+
if (!currentlyChecked) {
209+
for (const el of this.activeNodes) {
210+
this.uncheckAtPath(this.getNodePath(el))
211+
}
212+
213+
this.checkAtPath(path)
214+
}
215+
}
216+
202217
// navigate or trigger button, don't toggle
203218
if (!this.nodeHasNativeAction(node)) {
204219
this.toggleAtPath(path)
@@ -279,6 +294,10 @@ export class TreeViewElement extends HTMLElement {
279294
return this.querySelector('[aria-current=true]')
280295
}
281296

297+
get activeNodes() {
298+
return document.querySelectorAll('[aria-checked="true"]');
299+
}
300+
282301
expandAtPath(path: string[]) {
283302
const node = this.subTreeAtPath(path)
284303
if (!node) return
@@ -420,6 +439,10 @@ export class TreeViewElement extends HTMLElement {
420439
previousCheckedValue: checkedValue,
421440
}
422441
}
442+
443+
selectVariant(node: HTMLElement): SelectVariant {
444+
return (node.getAttribute('data-select-variant') || 'none') as SelectVariant
445+
}
423446
}
424447

425448
if (!window.customElements.get('tree-view')) {

previews/primer/alpha/tree_view_preview.rb

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class TreeViewPreview < ViewComponent::Preview
99
# @snapshot interactive
1010
# @param expanded [Boolean] toggle
1111
# @param disabled [Boolean] toggle
12-
# @param select_variant [Symbol] select [multiple, none]
12+
# @param select_variant [Symbol] select [multiple, single, none]
1313
# @param select_strategy [Symbol] select [self, descendants, mixed_descendants]
1414
def default(
1515
expanded: false,
@@ -28,7 +28,7 @@ def default(
2828
# @label Playground
2929
#
3030
# @param expanded [Boolean] toggle
31-
# @param select_variant [Symbol] select [multiple, none]
31+
# @param select_variant [Symbol] select [multiple, single, none]
3232
# @param select_strategy [Symbol] select [self, descendants, mixed_descendants]
3333
def playground(
3434
expanded: false,
@@ -43,6 +43,38 @@ def playground(
4343
})
4444
end
4545

46+
# @label Single select
47+
#
48+
# @snapshot interactive
49+
# @param expanded [Boolean] toggle
50+
# @param disabled [Boolean] toggle
51+
def single_select(
52+
expanded: false,
53+
disabled: false
54+
)
55+
render_with_template(locals: {
56+
expanded: coerce_bool(expanded),
57+
disabled: coerce_bool(disabled),
58+
select_variant: :single,
59+
})
60+
end
61+
62+
# @label Multi select
63+
#
64+
# @snapshot interactive
65+
# @param expanded [Boolean] toggle
66+
# @param disabled [Boolean] toggle
67+
def multi_select(
68+
expanded: false,
69+
disabled: false
70+
)
71+
render_with_template(locals: {
72+
expanded: coerce_bool(expanded),
73+
disabled: coerce_bool(disabled),
74+
select_variant: :multiple,
75+
})
76+
end
77+
4678
# @label Empty
4779
#
4880
# @snapshot interactive
@@ -94,7 +126,7 @@ def async_alpha(action_menu_expanded: false)
94126
# @param leading_visual_icon [Symbol] octicon
95127
# @param leading_action_icon [Symbol] octicon
96128
# @param trailing_visual_icon [Symbol] octicon
97-
# @param select_variant [Symbol] select [multiple, none]
129+
# @param select_variant [Symbol] select [multiple, single, none]
98130
# @param disabled [Boolean] toggle
99131
def leaf_node_playground(
100132
label: "Leaf node",
@@ -153,9 +185,11 @@ def auto_expansion
153185

154186
# @label Form input
155187
#
188+
# @param select_variant [Symbol] select [multiple, single, none]
156189
# @param expanded [Boolean] toggle
157-
def form_input(expanded: true)
190+
def form_input(select_variant: :multiple, expanded: true)
158191
render_with_template(locals: {
192+
select_variant: select_variant.to_sym,
159193
expanded: coerce_bool(expanded)
160194
})
161195
end

previews/primer/alpha/tree_view_preview/form_input.html.erb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<%= form_with(url: primer_view_components.generic_form_submission_path(format: :json)) do |f| %>
22
<%= render(Primer::Alpha::Stack.new) do %>
33
<%= render(Primer::Alpha::TreeView.new(form_arguments: { builder: f, name: "folder_structure" })) do |tree| %>
4-
<% tree.with_sub_tree(label: "src", expanded: expanded, select_variant: :multiple, value: 0) do |sub_tree| %>
5-
<% sub_tree.with_leaf(label: "button.rb", select_variant: :multiple, value: 1) %>
6-
<% sub_tree.with_leaf(label: "icon_button.rb", current: true, select_variant: :multiple, value: 2) %>
4+
<% tree.with_sub_tree(label: "src", expanded: expanded, select_variant: select_variant, value: 0) do |sub_tree| %>
5+
<% sub_tree.with_leaf(label: "button.rb", select_variant: select_variant, value: 1) %>
6+
<% sub_tree.with_leaf(label: "icon_button.rb", current: true, select_variant: select_variant, value: 2) %>
77
<% end %>
88

9-
<% tree.with_leaf(label: "action_menu.rb", select_variant: :multiple, value: 3) %>
9+
<% tree.with_leaf(label: "action_menu.rb", select_variant: select_variant, value: 3) %>
1010
<% end %>
1111

1212
<%= render(Primer::Alpha::SubmitButton.new(name: :submit, label: "Submit")) %>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div style="max-width: 400px">
2+
<%= render(Primer::Alpha::TreeView.new) do |tree_view| %>
3+
<% tree_view.with_sub_tree(label: "src", expanded: expanded, disabled: disabled, select_variant: select_variant) do |sub_tree| %>
4+
<% sub_tree.with_leaf(label: "button.rb", disabled: disabled, select_variant: select_variant) %>
5+
<% sub_tree.with_leaf(label: "icon_button.rb", current: true, disabled: disabled, select_variant: select_variant) %>
6+
<% end %>
7+
8+
<% tree_view.with_leaf(label: "action_menu.rb", disabled: disabled, select_variant: select_variant) %>
9+
<% end %>
10+
</div>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div style="max-width: 400px">
2+
<%= render(Primer::Alpha::TreeView.new) do |tree_view| %>
3+
<% tree_view.with_sub_tree(label: "src", expanded: expanded, disabled: disabled, select_variant: select_variant) do |sub_tree| %>
4+
<% sub_tree.with_leaf(label: "button.rb", disabled: disabled, select_variant: select_variant) %>
5+
<% sub_tree.with_leaf(label: "icon_button.rb", current: true, disabled: disabled, select_variant: select_variant) %>
6+
<% end %>
7+
8+
<% tree_view.with_leaf(label: "action_menu.rb", disabled: disabled, select_variant: select_variant) %>
9+
<% end %>
10+
</div>

0 commit comments

Comments
 (0)