Skip to content

Commit b6ede44

Browse files
committed
Support single select variant in FilterableTreeView
1 parent 52fd468 commit b6ede44

9 files changed

Lines changed: 131 additions & 42 deletions

File tree

.changeset/spotty-sheep-dream.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 select variant in FilterableTreeView

app/components/primer/alpha/tree_view.pcss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@
201201
/* singleselect checkmark */
202202
& .TreeViewItem-singleSelectCheckmark {
203203
visibility: hidden;
204-
transition: visibility 0s linear 200ms;
205204
}
206205
}
207206

app/components/primer/open_project/filterable_tree_view.rb

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,19 +230,43 @@ def with_filter_mode(name:, **system_arguments)
230230
end
231231

232232
def with_sub_tree(**system_arguments, &block)
233+
system_arguments[:select_variant] ||= :multiple
234+
235+
if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single
236+
raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
237+
end
238+
239+
if system_arguments[:select_variant] == :single
240+
# In single selection, the include sub-items checkbox and the SegmentedControl make no sense
241+
@include_sub_items_check_box_arguments[:hidden] = true
242+
@include_sub_items_check_box_arguments[:checked] = false
243+
@filter_mode_control_arguments[:hidden] = true
244+
end
245+
233246
@tree_view.with_sub_tree(
234247
sub_tree_component_klass: SubTree,
235248
**system_arguments,
236-
select_variant: :multiple,
237249
select_strategy: :self,
238250
&block
239251
)
240252
end
241253

242254
def with_leaf(**system_arguments, &block)
255+
system_arguments[:select_variant] ||= :multiple
256+
257+
if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single
258+
raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
259+
end
260+
261+
if system_arguments[:select_variant] == :single
262+
# In single selection, the include sub-items checkbox and the SegmentedControl make no sense
263+
@include_sub_items_check_box_arguments[:hidden] = true
264+
@include_sub_items_check_box_arguments[:checked] = false
265+
@filter_mode_control_arguments[:hidden] = true
266+
end
267+
243268
@tree_view.with_leaf(
244269
**system_arguments,
245-
select_variant: :multiple,
246270
&block
247271
)
248272
end

app/components/primer/open_project/filterable_tree_view/sub_tree.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,39 @@ class FilterableTreeView
99
# should not be used directly.
1010
class SubTree < Primer::Alpha::TreeView::SubTree
1111
def with_sub_tree(**system_arguments, &block)
12+
system_arguments[:select_variant] ||= :multiple
13+
14+
if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single
15+
raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
16+
end
17+
1218
super(
1319
sub_tree_component_klass: self.class,
1420
**system_arguments,
15-
select_variant: :multiple,
1621
select_strategy: :self,
1722
&block
1823
)
1924
end
2025

2126
def with_leaf(**system_arguments, &block)
27+
system_arguments[:select_variant] ||= :multiple
28+
29+
if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single
30+
raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
31+
end
32+
2233
super(
2334
**system_arguments,
24-
select_variant: :multiple,
2535
&block
2636
)
2737
end
2838

2939
def with_loading_spinner(**system_arguments)
30-
raise ArgumentError, "FilteredTreeView does not support asynchronous loading"
40+
raise ArgumentError, "FilterableTreeView does not support asynchronous loading"
3141
end
3242

3343
def with_loading_skeleton(**system_arguments)
34-
raise ArgumentError, "FilteredTreeView does not support asynchronous loading"
44+
raise ArgumentError, "FilterableTreeView does not support asynchronous loading"
3545
end
3646
end
3747
end

previews/primer/open_project/filterable_tree_view_preview.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ class FilterableTreeViewPreview < ViewComponent::Preview
77
# @label Playground
88
#
99
# @param expanded [Boolean] toggle
10+
# @param select_variant [Symbol] select [multiple, single]
1011
# @param show_checkbox [Boolean] toggle
1112
# @param show_segmented_control [Boolean] toggle
12-
def playground(expanded: true, show_checkbox: true, show_segmented_control: true)
13+
def playground(expanded: true, select_variant: :multiple, show_checkbox: true, show_segmented_control: true)
1314
render_with_template(locals: {
1415
expanded: coerce_bool(expanded),
16+
select_variant: select_variant.to_sym,
1517
show_checkbox: coerce_bool(show_checkbox),
1618
show_segmented_control: coerce_bool(show_segmented_control)
1719
})
@@ -29,9 +31,11 @@ def default(expanded: true)
2931

3032
# @label Form input
3133
#
34+
# @param select_variant [Symbol] select [multiple, single]
3235
# @param expanded [Boolean] toggle
33-
def form_input(expanded: true)
36+
def form_input(select_variant: :multiple, expanded: true)
3437
render_with_template(locals: {
38+
select_variant: select_variant.to_sym,
3539
expanded: coerce_bool(expanded)
3640
})
3741
end

previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
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::OpenProject::FilterableTreeView.new(form_arguments: { builder: f, name: "characters" })) do |tree| %>
4-
<% tree.with_sub_tree(label: "Students", expanded: expanded) do |hogwarts| %>
5-
<% hogwarts.with_sub_tree(label: "Ravenclaw", expanded: expanded) do |ravenclaw| %>
6-
<% ravenclaw.with_leaf(label: "Luna Lovegood") %>
4+
<% tree.with_sub_tree(label: "Students", select_variant: select_variant, expanded: expanded) do |hogwarts| %>
5+
<% hogwarts.with_sub_tree(label: "Ravenclaw", select_variant: select_variant, expanded: expanded) do |ravenclaw| %>
6+
<% ravenclaw.with_leaf(label: "Luna Lovegood", select_variant: select_variant) %>
77
<% end %>
88

9-
<% hogwarts.with_sub_tree(label: "Slytherin", expanded: expanded) do |hufflepuff| %>
10-
<% hufflepuff.with_leaf(label: "Draco Malfoy") %>
9+
<% hogwarts.with_sub_tree(label: "Slytherin", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
10+
<% hufflepuff.with_leaf(label: "Draco Malfoy", select_variant: select_variant) %>
1111
<% end %>
1212

13-
<% hogwarts.with_sub_tree(label: "Hufflepuff", expanded: expanded) do |hufflepuff| %>
14-
<% hufflepuff.with_leaf(label: "Susan Bones") %>
13+
<% hogwarts.with_sub_tree(label: "Hufflepuff", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
14+
<% hufflepuff.with_leaf(label: "Susan Bones", select_variant: select_variant) %>
1515
<% end %>
1616

17-
<% hogwarts.with_sub_tree(label: "Gryffindor", expanded: expanded) do |hufflepuff| %>
18-
<% hufflepuff.with_leaf(label: "Harry Potter") %>
19-
<% hufflepuff.with_leaf(label: "Ronald Weasley") %>
20-
<% hufflepuff.with_leaf(label: "Hermione Granger") %>
17+
<% hogwarts.with_sub_tree(label: "Gryffindor", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
18+
<% hufflepuff.with_leaf(label: "Harry Potter", select_variant: select_variant) %>
19+
<% hufflepuff.with_leaf(label: "Ronald Weasley", select_variant: select_variant) %>
20+
<% hufflepuff.with_leaf(label: "Hermione Granger", select_variant: select_variant) %>
2121
<% end %>
2222
<% end %>
2323

24-
<% tree.with_leaf(label: "Albus Dumbledore") %>
25-
<% tree.with_leaf(label: "Minerva McGonagall") %>
26-
<% tree.with_leaf(label: "Severus Snape") %>
27-
<% tree.with_leaf(label: "Rubeus Hagrid") %>
24+
<% tree.with_leaf(label: "Albus Dumbledore", select_variant: select_variant) %>
25+
<% tree.with_leaf(label: "Minerva McGonagall", select_variant: select_variant) %>
26+
<% tree.with_leaf(label: "Severus Snape", select_variant: select_variant) %>
27+
<% tree.with_leaf(label: "Rubeus Hagrid", select_variant: select_variant) %>
2828
<% end %>
2929

3030
<%= render(Primer::Alpha::SubmitButton.new(name: :submit, label: "Submit")) %>

previews/primer/open_project/filterable_tree_view_preview/playground.html.erb

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,28 @@
22
include_sub_items_check_box_arguments: { hidden: !show_checkbox },
33
filter_mode_control_arguments: { hidden: !show_segmented_control }
44
)) do |tree| %>
5-
<% tree.with_sub_tree(label: "Students", expanded: expanded) do |hogwarts| %>
6-
<% hogwarts.with_sub_tree(label: "Ravenclaw", expanded: expanded) do |ravenclaw| %>
7-
<% ravenclaw.with_leaf(label: "Luna Lovegood") %>
5+
<% tree.with_sub_tree(label: "Students", select_variant: select_variant, expanded: expanded) do |hogwarts| %>
6+
<% hogwarts.with_sub_tree(label: "Ravenclaw", select_variant: select_variant, expanded: expanded) do |ravenclaw| %>
7+
<% ravenclaw.with_leaf(label: "Luna Lovegood", select_variant: select_variant) %>
88
<% end %>
99

10-
<% hogwarts.with_sub_tree(label: "Slytherin", expanded: expanded) do |hufflepuff| %>
11-
<% hufflepuff.with_leaf(label: "Draco Malfoy") %>
10+
<% hogwarts.with_sub_tree(label: "Slytherin", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
11+
<% hufflepuff.with_leaf(label: "Draco Malfoy", select_variant: select_variant) %>
1212
<% end %>
1313

14-
<% hogwarts.with_sub_tree(label: "Hufflepuff", expanded: expanded) do |hufflepuff| %>
15-
<% hufflepuff.with_leaf(label: "Susan Bones") %>
14+
<% hogwarts.with_sub_tree(label: "Hufflepuff", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
15+
<% hufflepuff.with_leaf(label: "Susan Bones", select_variant: select_variant) %>
1616
<% end %>
1717

18-
<% hogwarts.with_sub_tree(label: "Gryffindor", expanded: expanded) do |hufflepuff| %>
19-
<% hufflepuff.with_leaf(label: "Harry Potter") %>
20-
<% hufflepuff.with_leaf(label: "Ronald Weasley") %>
21-
<% hufflepuff.with_leaf(label: "Hermione Granger") %>
18+
<% hogwarts.with_sub_tree(label: "Gryffindor", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
19+
<% hufflepuff.with_leaf(label: "Harry Potter", select_variant: select_variant) %>
20+
<% hufflepuff.with_leaf(label: "Ronald Weasley", select_variant: select_variant) %>
21+
<% hufflepuff.with_leaf(label: "Hermione Granger", select_variant: select_variant) %>
2222
<% end %>
2323
<% end %>
2424

25-
<% tree.with_leaf(label: "Albus Dumbledore") %>
26-
<% tree.with_leaf(label: "Minerva McGonagall") %>
27-
<% tree.with_leaf(label: "Severus Snape") %>
28-
<% tree.with_leaf(label: "Rubeus Hagrid") %>
25+
<% tree.with_leaf(label: "Albus Dumbledore", select_variant: select_variant) %>
26+
<% tree.with_leaf(label: "Minerva McGonagall", select_variant: select_variant) %>
27+
<% tree.with_leaf(label: "Severus Snape", select_variant: select_variant) %>
28+
<% tree.with_leaf(label: "Rubeus Hagrid", select_variant: select_variant) %>
2929
<% end %>

test/components/primer/open_project/filterable_tree_view_test.rb

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_filter_mode_control_label_can_be_overridden
5151
assert_selector("segmented-control [role=list][aria-label='Filtermodus']")
5252
end
5353

54-
def test_segmented_control_can_be_hidden
54+
def test_filter_mode_control_can_be_hidden
5555
render_inline(
5656
Primer::OpenProject::FilterableTreeView.new(
5757
filter_mode_control_arguments: { hidden: true }
@@ -93,6 +93,16 @@ def test_include_sub_items_can_have_a_different_default
9393
assert_selector("input[name=include_sub_items][checked=checked]", visible: :visible)
9494
end
9595

96+
def test_include_sub_items_and_filter_mode_are_hidden_for_single_select_variant
97+
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
98+
tree.with_sub_tree(label: "Foo", select_variant: :single)
99+
end
100+
101+
assert_selector(".FormControl-checkbox-wrap", visible: :hidden)
102+
assert_selector("input[name=include_sub_items]:not([checked=false])", visible: :hidden)
103+
assert_selector("segmented-control", visible: :hidden)
104+
end
105+
96106
def test_has_filter_input
97107
render_preview(:default)
98108

@@ -119,6 +129,26 @@ def test_filter_input_arguments_can_be_overridden
119129
assert_selector("label[for='#{id}'].sr-only", text: "Filtern")
120130
end
121131

132+
def test_leaf_cannot_render_with_select_variant_none
133+
error = assert_raises(ArgumentError) do
134+
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
135+
tree.with_leaf(label: "Foo", select_variant: :none)
136+
end
137+
end
138+
139+
assert_equal error.message, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
140+
end
141+
142+
def test_sub_trees_cannot_render_with_select_variant_none
143+
error = assert_raises(ArgumentError) do
144+
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
145+
tree.with_sub_tree(label: "Foo", select_variant: :none)
146+
end
147+
end
148+
149+
assert_equal error.message, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
150+
end
151+
122152
def test_sub_trees_cannot_load_with_async_spinner
123153
error = assert_raises(ArgumentError) do
124154
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
@@ -128,7 +158,7 @@ def test_sub_trees_cannot_load_with_async_spinner
128158
end
129159
end
130160

131-
assert_equal error.message, "FilteredTreeView does not support asynchronous loading"
161+
assert_equal error.message, "FilterableTreeView does not support asynchronous loading"
132162
end
133163

134164
def test_sub_trees_cannot_load_with_async_skeleton
@@ -140,7 +170,7 @@ def test_sub_trees_cannot_load_with_async_skeleton
140170
end
141171
end
142172

143-
assert_equal error.message, "FilteredTreeView does not support asynchronous loading"
173+
assert_equal error.message, "FilterableTreeView does not support asynchronous loading"
144174
end
145175

146176
def test_custom_filter_modes

test/system/open_project/filterable_tree_view_test.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,23 @@ def test_automatically_expands_sub_trees_to_show_matching_items
217217
assert_path("Students", "Gryffindor", "Ronald Weasley")
218218
end
219219

220+
def test_form_submits_checked_nodes_for_single_select_variant
221+
visit_preview(:form_input, select_variant: :single)
222+
223+
activate_at_path("Students", "Gryffindor", "Harry Potter")
224+
activate_at_path("Students", "Ravenclaw", "Luna Lovegood")
225+
click_on "Submit"
226+
227+
response = JSON.parse(find("pre").text)
228+
assert character_list = response.dig("form_params", "characters")
229+
230+
# Since we are in single select mode, only the last checked element is selected and send
231+
assert_equal 1, character_list.size
232+
233+
character = JSON.parse(character_list[0])
234+
assert_equal character["path"], ["Students", "Ravenclaw", "Luna Lovegood"]
235+
end
236+
220237
def test_form_submits_checked_nodes
221238
visit_preview(:form_input)
222239

0 commit comments

Comments
 (0)