Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/spotty-sheep-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openproject/primer-view-components': minor
---

Support single select variant in FilterableTreeView
28 changes: 26 additions & 2 deletions app/components/primer/open_project/filterable_tree_view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,43 @@ def with_filter_mode(name:, **system_arguments)
end

def with_sub_tree(**system_arguments, &block)
system_arguments[:select_variant] ||= :multiple

if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single
raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
end

if system_arguments[:select_variant] == :single
# In single selection, the include sub-items checkbox and the SegmentedControl make no sense
@include_sub_items_check_box_arguments[:hidden] = true
@include_sub_items_check_box_arguments[:checked] = false
@filter_mode_control_arguments[:hidden] = true
end

@tree_view.with_sub_tree(
sub_tree_component_klass: SubTree,
**system_arguments,
select_variant: :multiple,
select_strategy: :self,
&block
)
end

def with_leaf(**system_arguments, &block)
system_arguments[:select_variant] ||= :multiple

if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single
raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
end

if system_arguments[:select_variant] == :single
# In single selection, the include sub-items checkbox and the SegmentedControl make no sense
@include_sub_items_check_box_arguments[:hidden] = true
@include_sub_items_check_box_arguments[:checked] = false
@filter_mode_control_arguments[:hidden] = true
end

@tree_view.with_leaf(
**system_arguments,
select_variant: :multiple,
&block
)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,39 @@ class FilterableTreeView
# should not be used directly.
class SubTree < Primer::Alpha::TreeView::SubTree
def with_sub_tree(**system_arguments, &block)
system_arguments[:select_variant] ||= :multiple

if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single
raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
end

super(
sub_tree_component_klass: self.class,
**system_arguments,
select_variant: :multiple,
select_strategy: :self,
&block
)
end

def with_leaf(**system_arguments, &block)
system_arguments[:select_variant] ||= :multiple

if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single
raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
end

super(
**system_arguments,
select_variant: :multiple,
&block
)
end

def with_loading_spinner(**system_arguments)
raise ArgumentError, "FilteredTreeView does not support asynchronous loading"
raise ArgumentError, "FilterableTreeView does not support asynchronous loading"
end

def with_loading_skeleton(**system_arguments)
raise ArgumentError, "FilteredTreeView does not support asynchronous loading"
raise ArgumentError, "FilterableTreeView does not support asynchronous loading"
end
end
end
Expand Down
8 changes: 6 additions & 2 deletions previews/primer/open_project/filterable_tree_view_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ class FilterableTreeViewPreview < ViewComponent::Preview
# @label Playground
#
# @param expanded [Boolean] toggle
# @param select_variant [Symbol] select [multiple, single]
# @param show_checkbox [Boolean] toggle
# @param show_segmented_control [Boolean] toggle
def playground(expanded: true, show_checkbox: true, show_segmented_control: true)
def playground(expanded: true, select_variant: :multiple, show_checkbox: true, show_segmented_control: true)
render_with_template(locals: {
expanded: coerce_bool(expanded),
select_variant: select_variant.to_sym,
show_checkbox: coerce_bool(show_checkbox),
show_segmented_control: coerce_bool(show_segmented_control)
})
Expand All @@ -29,9 +31,11 @@ def default(expanded: true)

# @label Form input
#
# @param select_variant [Symbol] select [multiple, single]
# @param expanded [Boolean] toggle
def form_input(expanded: true)
def form_input(select_variant: :multiple, expanded: true)
render_with_template(locals: {
select_variant: select_variant.to_sym,
expanded: coerce_bool(expanded)
})
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
<%= form_with(url: primer_view_components.generic_form_submission_path(format: :json)) do |f| %>
<%= render(Primer::Alpha::Stack.new) do %>
<%= render(Primer::OpenProject::FilterableTreeView.new(form_arguments: { builder: f, name: "characters" })) do |tree| %>
<% tree.with_sub_tree(label: "Students", expanded: expanded) do |hogwarts| %>
<% hogwarts.with_sub_tree(label: "Ravenclaw", expanded: expanded) do |ravenclaw| %>
<% ravenclaw.with_leaf(label: "Luna Lovegood") %>
<% tree.with_sub_tree(label: "Students", select_variant: select_variant, expanded: expanded) do |hogwarts| %>
<% hogwarts.with_sub_tree(label: "Ravenclaw", select_variant: select_variant, expanded: expanded) do |ravenclaw| %>
<% ravenclaw.with_leaf(label: "Luna Lovegood", select_variant: select_variant) %>
<% end %>

<% hogwarts.with_sub_tree(label: "Slytherin", expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Draco Malfoy") %>
<% hogwarts.with_sub_tree(label: "Slytherin", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Draco Malfoy", select_variant: select_variant) %>
<% end %>

<% hogwarts.with_sub_tree(label: "Hufflepuff", expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Susan Bones") %>
<% hogwarts.with_sub_tree(label: "Hufflepuff", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Susan Bones", select_variant: select_variant) %>
<% end %>

<% hogwarts.with_sub_tree(label: "Gryffindor", expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Harry Potter") %>
<% hufflepuff.with_leaf(label: "Ronald Weasley") %>
<% hufflepuff.with_leaf(label: "Hermione Granger") %>
<% hogwarts.with_sub_tree(label: "Gryffindor", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Harry Potter", select_variant: select_variant) %>
<% hufflepuff.with_leaf(label: "Ronald Weasley", select_variant: select_variant) %>
<% hufflepuff.with_leaf(label: "Hermione Granger", select_variant: select_variant) %>
<% end %>
<% end %>

<% tree.with_leaf(label: "Albus Dumbledore") %>
<% tree.with_leaf(label: "Minerva McGonagall") %>
<% tree.with_leaf(label: "Severus Snape") %>
<% tree.with_leaf(label: "Rubeus Hagrid") %>
<% tree.with_leaf(label: "Albus Dumbledore", select_variant: select_variant) %>
<% tree.with_leaf(label: "Minerva McGonagall", select_variant: select_variant) %>
<% tree.with_leaf(label: "Severus Snape", select_variant: select_variant) %>
<% tree.with_leaf(label: "Rubeus Hagrid", select_variant: select_variant) %>
<% end %>

<%= render(Primer::Alpha::SubmitButton.new(name: :submit, label: "Submit")) %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@
include_sub_items_check_box_arguments: { hidden: !show_checkbox },
filter_mode_control_arguments: { hidden: !show_segmented_control }
)) do |tree| %>
<% tree.with_sub_tree(label: "Students", expanded: expanded) do |hogwarts| %>
<% hogwarts.with_sub_tree(label: "Ravenclaw", expanded: expanded) do |ravenclaw| %>
<% ravenclaw.with_leaf(label: "Luna Lovegood") %>
<% tree.with_sub_tree(label: "Students", select_variant: select_variant, expanded: expanded) do |hogwarts| %>
<% hogwarts.with_sub_tree(label: "Ravenclaw", select_variant: select_variant, expanded: expanded) do |ravenclaw| %>
<% ravenclaw.with_leaf(label: "Luna Lovegood", select_variant: select_variant) %>
<% end %>

<% hogwarts.with_sub_tree(label: "Slytherin", expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Draco Malfoy") %>
<% hogwarts.with_sub_tree(label: "Slytherin", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Draco Malfoy", select_variant: select_variant) %>
<% end %>

<% hogwarts.with_sub_tree(label: "Hufflepuff", expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Susan Bones") %>
<% hogwarts.with_sub_tree(label: "Hufflepuff", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Susan Bones", select_variant: select_variant) %>
<% end %>

<% hogwarts.with_sub_tree(label: "Gryffindor", expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Harry Potter") %>
<% hufflepuff.with_leaf(label: "Ronald Weasley") %>
<% hufflepuff.with_leaf(label: "Hermione Granger") %>
<% hogwarts.with_sub_tree(label: "Gryffindor", select_variant: select_variant, expanded: expanded) do |hufflepuff| %>
<% hufflepuff.with_leaf(label: "Harry Potter", select_variant: select_variant) %>
<% hufflepuff.with_leaf(label: "Ronald Weasley", select_variant: select_variant) %>
<% hufflepuff.with_leaf(label: "Hermione Granger", select_variant: select_variant) %>
<% end %>
<% end %>

<% tree.with_leaf(label: "Albus Dumbledore") %>
<% tree.with_leaf(label: "Minerva McGonagall") %>
<% tree.with_leaf(label: "Severus Snape") %>
<% tree.with_leaf(label: "Rubeus Hagrid") %>
<% tree.with_leaf(label: "Albus Dumbledore", select_variant: select_variant) %>
<% tree.with_leaf(label: "Minerva McGonagall", select_variant: select_variant) %>
<% tree.with_leaf(label: "Severus Snape", select_variant: select_variant) %>
<% tree.with_leaf(label: "Rubeus Hagrid", select_variant: select_variant) %>
<% end %>
54 changes: 51 additions & 3 deletions test/components/primer/open_project/filterable_tree_view_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_filter_mode_control_label_can_be_overridden
assert_selector("segmented-control [role=list][aria-label='Filtermodus']")
end

def test_segmented_control_can_be_hidden
def test_filter_mode_control_can_be_hidden
render_inline(
Primer::OpenProject::FilterableTreeView.new(
filter_mode_control_arguments: { hidden: true }
Expand Down Expand Up @@ -93,6 +93,14 @@ def test_include_sub_items_can_have_a_different_default
assert_selector("input[name=include_sub_items][checked=checked]", visible: :visible)
end

def test_include_sub_items_and_filter_mode_are_hidden_for_single_select_variant
render_preview(:form_input, params: { select_variant: :single })

assert_selector(".FormControl-checkbox-wrap", visible: :hidden)
assert_selector("input[name=include_sub_items]:not([checked=false])", visible: :hidden)
assert_selector("segmented-control", visible: :hidden)
end

def test_has_filter_input
render_preview(:default)

Expand All @@ -119,6 +127,46 @@ def test_filter_input_arguments_can_be_overridden
assert_selector("label[for='#{id}'].sr-only", text: "Filtern")
end

def test_leaf_cannot_render_with_select_variant_none
error = assert_raises(ArgumentError) do
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
tree.with_leaf(label: "Foo", select_variant: :none)
end
end

assert_equal error.message, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"

error = assert_raises(ArgumentError) do
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
tree.with_sub_tree(label: "Foo") do |sub_tree|
sub_tree.with_leaf(label: "Bar", select_variant: :none)
end
end
end

assert_equal error.message, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
end

def test_sub_trees_cannot_render_with_select_variant_none
error = assert_raises(ArgumentError) do
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
tree.with_sub_tree(label: "Foo", select_variant: :none)
end
end

assert_equal error.message, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"

error = assert_raises(ArgumentError) do
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
tree.with_sub_tree(label: "Foo") do |sub_tree|
sub_tree.with_sub_tree(label: "Bar", select_variant: :none)
end
end
end

assert_equal error.message, "FilterableTreeView only supports `:multiple` or `:single` as select_variant"
end

def test_sub_trees_cannot_load_with_async_spinner
error = assert_raises(ArgumentError) do
render_inline(Primer::OpenProject::FilterableTreeView.new) do |tree|
Expand All @@ -128,7 +176,7 @@ def test_sub_trees_cannot_load_with_async_spinner
end
end

assert_equal error.message, "FilteredTreeView does not support asynchronous loading"
assert_equal error.message, "FilterableTreeView does not support asynchronous loading"
end

def test_sub_trees_cannot_load_with_async_skeleton
Expand All @@ -140,7 +188,7 @@ def test_sub_trees_cannot_load_with_async_skeleton
end
end

assert_equal error.message, "FilteredTreeView does not support asynchronous loading"
assert_equal error.message, "FilterableTreeView does not support asynchronous loading"
end

def test_custom_filter_modes
Expand Down
17 changes: 17 additions & 0 deletions test/system/open_project/filterable_tree_view_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,23 @@ def test_automatically_expands_sub_trees_to_show_matching_items
assert_path("Students", "Gryffindor", "Ronald Weasley")
end

def test_form_submits_checked_nodes_for_single_select_variant
visit_preview(:form_input, select_variant: :single)

activate_at_path("Students", "Gryffindor", "Harry Potter")
activate_at_path("Students", "Ravenclaw", "Luna Lovegood")
click_on "Submit"

response = JSON.parse(find("pre").text)
assert character_list = response.dig("form_params", "characters")

# Since we are in single select mode, only the last checked element is selected and send
assert_equal 1, character_list.size

character = JSON.parse(character_list[0])
assert_equal character["path"], ["Students", "Ravenclaw", "Luna Lovegood"]
end

def test_form_submits_checked_nodes
visit_preview(:form_input)

Expand Down
Loading