openproject-primer_view_components 0.64.0 → 0.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/app/assets/javascripts/components/primer/open_project/collapsible.d.ts +2 -0
  4. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view.d.ts +29 -0
  5. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_icon_pair_element.d.ts +15 -0
  6. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_include_fragment_element.d.ts +9 -0
  7. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_roving_tab_index.d.ts +3 -0
  8. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +38 -0
  9. data/app/assets/javascripts/components/primer/primer.d.ts +4 -0
  10. data/app/assets/javascripts/components/primer/shared_events.d.ts +15 -0
  11. data/app/assets/javascripts/primer_view_components.js +1 -1
  12. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  13. data/app/assets/styles/primer_view_components.css +1 -1
  14. data/app/assets/styles/primer_view_components.css.map +1 -1
  15. data/app/components/primer/alpha/select_panel.css +1 -1
  16. data/app/components/primer/alpha/select_panel.css.json +2 -2
  17. data/app/components/primer/alpha/select_panel.css.map +1 -1
  18. data/app/components/primer/alpha/select_panel.html.erb +1 -1
  19. data/app/components/primer/alpha/select_panel.pcss +5 -2
  20. data/app/components/primer/beta/spinner.html.erb +1 -1
  21. data/app/components/primer/beta/spinner.rb +2 -0
  22. data/app/components/primer/open_project/border_box/collapsible_header.css +1 -1
  23. data/app/components/primer/open_project/border_box/collapsible_header.css.json +2 -1
  24. data/app/components/primer/open_project/border_box/collapsible_header.css.map +1 -1
  25. data/app/components/primer/open_project/border_box/collapsible_header.html.erb +12 -1
  26. data/app/components/primer/open_project/border_box/collapsible_header.pcss +4 -0
  27. data/app/components/primer/open_project/border_box/collapsible_header.rb +16 -12
  28. data/app/components/primer/open_project/collapsible.d.ts +2 -0
  29. data/app/components/primer/open_project/collapsible.js +11 -0
  30. data/app/components/primer/open_project/collapsible.ts +10 -0
  31. data/app/components/primer/open_project/collapsible_section.html.erb +14 -2
  32. data/app/components/primer/open_project/collapsible_section.rb +5 -1
  33. data/app/components/primer/open_project/danger_dialog.html.erb +4 -0
  34. data/app/components/primer/open_project/feedback_dialog.html.erb +5 -0
  35. data/app/components/primer/open_project/file_tree_view/directory_node.html.erb +5 -0
  36. data/app/components/primer/open_project/file_tree_view/directory_node.rb +24 -0
  37. data/app/components/primer/open_project/file_tree_view/file_node.html.erb +2 -0
  38. data/app/components/primer/open_project/file_tree_view/file_node.rb +14 -0
  39. data/app/components/primer/open_project/file_tree_view.rb +15 -0
  40. data/app/components/primer/open_project/skeleton_box.css +1 -0
  41. data/app/components/primer/open_project/skeleton_box.css.json +6 -0
  42. data/app/components/primer/open_project/skeleton_box.css.map +1 -0
  43. data/app/components/primer/open_project/skeleton_box.html.erb +1 -0
  44. data/app/components/primer/open_project/skeleton_box.pcss +30 -0
  45. data/app/components/primer/open_project/skeleton_box.rb +27 -0
  46. data/app/components/primer/open_project/tree_view/icon.html.erb +1 -0
  47. data/app/components/primer/open_project/tree_view/icon.rb +22 -0
  48. data/app/components/primer/open_project/tree_view/icon_pair.html.erb +13 -0
  49. data/app/components/primer/open_project/tree_view/icon_pair.rb +42 -0
  50. data/app/components/primer/open_project/tree_view/leading_action.html.erb +3 -0
  51. data/app/components/primer/open_project/tree_view/leading_action.rb +18 -0
  52. data/app/components/primer/open_project/tree_view/leaf_node.html.erb +18 -0
  53. data/app/components/primer/open_project/tree_view/leaf_node.rb +96 -0
  54. data/app/components/primer/open_project/tree_view/loading_failure_message.html.erb +13 -0
  55. data/app/components/primer/open_project/tree_view/loading_failure_message.rb +31 -0
  56. data/app/components/primer/open_project/tree_view/node.html.erb +32 -0
  57. data/app/components/primer/open_project/tree_view/node.rb +155 -0
  58. data/app/components/primer/open_project/tree_view/skeleton_loader.html.erb +23 -0
  59. data/app/components/primer/open_project/tree_view/skeleton_loader.rb +36 -0
  60. data/app/components/primer/open_project/tree_view/spinner_loader.html.erb +20 -0
  61. data/app/components/primer/open_project/tree_view/spinner_loader.rb +33 -0
  62. data/app/components/primer/open_project/tree_view/sub_tree.html.erb +21 -0
  63. data/app/components/primer/open_project/tree_view/sub_tree.rb +106 -0
  64. data/app/components/primer/open_project/tree_view/sub_tree_container.html.erb +3 -0
  65. data/app/components/primer/open_project/tree_view/sub_tree_container.rb +39 -0
  66. data/app/components/primer/open_project/tree_view/sub_tree_node.html.erb +49 -0
  67. data/app/components/primer/open_project/tree_view/sub_tree_node.rb +172 -0
  68. data/app/components/primer/open_project/tree_view/tree_view.d.ts +29 -0
  69. data/app/components/primer/open_project/tree_view/tree_view.js +238 -0
  70. data/app/components/primer/open_project/tree_view/tree_view.ts +257 -0
  71. data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.d.ts +15 -0
  72. data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.js +62 -0
  73. data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.ts +56 -0
  74. data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.d.ts +9 -0
  75. data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.js +29 -0
  76. data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.ts +29 -0
  77. data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.d.ts +3 -0
  78. data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.js +126 -0
  79. data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.ts +156 -0
  80. data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +38 -0
  81. data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.js +362 -0
  82. data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.ts +402 -0
  83. data/app/components/primer/open_project/tree_view/visual.html.erb +14 -0
  84. data/app/components/primer/open_project/tree_view/visual.rb +27 -0
  85. data/app/components/primer/open_project/tree_view.css +1 -0
  86. data/app/components/primer/open_project/tree_view.css.json +42 -0
  87. data/app/components/primer/open_project/tree_view.css.map +1 -0
  88. data/app/components/primer/open_project/tree_view.html.erb +7 -0
  89. data/app/components/primer/open_project/tree_view.pcss +319 -0
  90. data/app/components/primer/open_project/tree_view.rb +367 -0
  91. data/app/components/primer/primer.d.ts +4 -0
  92. data/app/components/primer/primer.js +4 -0
  93. data/app/components/primer/primer.pcss +2 -0
  94. data/app/components/primer/primer.ts +4 -0
  95. data/app/components/primer/shared_events.d.ts +15 -0
  96. data/app/components/primer/shared_events.ts +19 -0
  97. data/app/lib/primer/forms/acts_as_component.rb +1 -12
  98. data/lib/primer/view_components/version.rb +1 -1
  99. data/previews/primer/open_project/file_tree_view_preview/default.html.erb +16 -0
  100. data/previews/primer/open_project/file_tree_view_preview/playground.html.erb +4 -0
  101. data/previews/primer/open_project/file_tree_view_preview.rb +69 -0
  102. data/previews/primer/open_project/skeleton_box_preview.rb +20 -0
  103. data/previews/primer/open_project/tree_view_preview/default.html.erb +24 -0
  104. data/previews/primer/open_project/tree_view_preview/empty.html.erb +10 -0
  105. data/previews/primer/open_project/tree_view_preview/leaf_node_playground.html.erb +15 -0
  106. data/previews/primer/open_project/tree_view_preview/loading_failure.html.erb +36 -0
  107. data/previews/primer/open_project/tree_view_preview/loading_skeleton.html.erb +12 -0
  108. data/previews/primer/open_project/tree_view_preview/loading_spinner.html.erb +12 -0
  109. data/previews/primer/open_project/tree_view_preview/playground.html.erb +4 -0
  110. data/previews/primer/open_project/tree_view_preview.rb +139 -0
  111. data/static/arguments.json +400 -0
  112. data/static/audited_at.json +17 -0
  113. data/static/classes.json +18 -0
  114. data/static/constants.json +83 -0
  115. data/static/info_arch.json +1379 -0
  116. data/static/previews.json +167 -0
  117. data/static/statuses.json +17 -0
  118. metadata +75 -2
@@ -33,3 +33,7 @@ import './open_project/danger_dialog_form_helper';
33
33
  import './open_project/collapsible';
34
34
  import './open_project/border_box/collapsible_header';
35
35
  import './open_project/collapsible_section';
36
+ import './open_project/tree_view/tree_view';
37
+ import './open_project/tree_view/tree_view_icon_pair_element';
38
+ import './open_project/tree_view/tree_view_sub_tree_node_element';
39
+ import './open_project/tree_view/tree_view_include_fragment_element';
@@ -51,3 +51,5 @@
51
51
  @import "./open_project/side_panel/section.pcss";
52
52
  @import "./open_project/border_box/collapsible_header.pcss";
53
53
  @import "./open_project/collapsible_section.pcss";
54
+ @import "./open_project/skeleton_box.pcss";
55
+ @import "./open_project/tree_view.pcss";
@@ -33,3 +33,7 @@ import './open_project/danger_dialog_form_helper'
33
33
  import './open_project/collapsible'
34
34
  import './open_project/border_box/collapsible_header'
35
35
  import './open_project/collapsible_section'
36
+ import './open_project/tree_view/tree_view'
37
+ import './open_project/tree_view/tree_view_icon_pair_element'
38
+ import './open_project/tree_view/tree_view_sub_tree_node_element'
39
+ import './open_project/tree_view/tree_view_include_fragment_element'
@@ -3,9 +3,24 @@ export type ItemActivatedEvent = {
3
3
  checked: boolean;
4
4
  value: string | null;
5
5
  };
6
+ export type TreeViewNodeType = 'leaf' | 'sub-tree';
7
+ export type TreeViewCheckedValue = 'true' | 'false' | 'mixed';
8
+ export type TreeViewNodeInfo = {
9
+ node: Element;
10
+ type: TreeViewNodeType;
11
+ path: string[];
12
+ checkedValue: TreeViewCheckedValue;
13
+ previousCheckedValue: TreeViewCheckedValue;
14
+ };
6
15
  declare global {
7
16
  interface HTMLElementEventMap {
8
17
  itemActivated: CustomEvent<ItemActivatedEvent>;
9
18
  beforeItemActivated: CustomEvent<ItemActivatedEvent>;
19
+ treeViewNodeActivated: CustomEvent<TreeViewNodeInfo>;
20
+ treeViewBeforeNodeActivated: CustomEvent<TreeViewNodeInfo>;
21
+ treeViewNodeExpanded: CustomEvent<TreeViewNodeInfo>;
22
+ treeViewNodeCollapsed: CustomEvent<TreeViewNodeInfo>;
23
+ treeViewNodeChecked: CustomEvent<TreeViewNodeInfo[]>;
24
+ treeViewBeforeNodeChecked: CustomEvent<TreeViewNodeInfo[]>;
10
25
  }
11
26
  }
@@ -4,9 +4,28 @@ export type ItemActivatedEvent = {
4
4
  value: string | null
5
5
  }
6
6
 
7
+ export type TreeViewNodeType = 'leaf' | 'sub-tree'
8
+ export type TreeViewCheckedValue = 'true' | 'false' | 'mixed'
9
+
10
+ export type TreeViewNodeInfo = {
11
+ node: Element
12
+ type: TreeViewNodeType
13
+ path: string[]
14
+ checkedValue: TreeViewCheckedValue
15
+ previousCheckedValue: TreeViewCheckedValue
16
+ }
17
+
7
18
  declare global {
8
19
  interface HTMLElementEventMap {
9
20
  itemActivated: CustomEvent<ItemActivatedEvent>
10
21
  beforeItemActivated: CustomEvent<ItemActivatedEvent>
22
+
23
+ treeViewNodeActivated: CustomEvent<TreeViewNodeInfo>
24
+ treeViewBeforeNodeActivated: CustomEvent<TreeViewNodeInfo>
25
+ treeViewNodeExpanded: CustomEvent<TreeViewNodeInfo>
26
+ treeViewNodeCollapsed: CustomEvent<TreeViewNodeInfo>
27
+
28
+ treeViewNodeChecked: CustomEvent<TreeViewNodeInfo[]>
29
+ treeViewBeforeNodeChecked: CustomEvent<TreeViewNodeInfo[]>
11
30
  }
12
31
  }
@@ -8,7 +8,7 @@ module Primer
8
8
  module ActsAsComponent
9
9
  # :nodoc:
10
10
  module InstanceMethods
11
- delegate :render, :content_tag, :output_buffer, to: :@view_context
11
+ delegate :render, :content_tag, :output_buffer, :capture, to: :@view_context
12
12
 
13
13
  def render_in(view_context, &block)
14
14
  @view_context = view_context
@@ -16,17 +16,6 @@ module Primer
16
16
  perform_render(&block)
17
17
  end
18
18
 
19
- # This is necessary to restore the functionality changed by https://github.com/rails/rails/pull/47194.
20
- # I would love to remove this at some point, perhaps if we ever decide to replace
21
- # ActsAsComponent with view component.
22
- def capture(*args, &block)
23
- old_buffer = @view_context.output_buffer
24
- @view_context.output_buffer = ActionView::OutputBuffer.new
25
- @view_context.capture(*args, &block)
26
- ensure
27
- @view_context.output_buffer = old_buffer
28
- end
29
-
30
19
  # :nocov:
31
20
  def perform_render(&_block)
32
21
  raise NotImplementedError, "subclasses must implement ##{__method__}."
@@ -5,7 +5,7 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 64
8
+ MINOR = 65
9
9
  PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -0,0 +1,16 @@
1
+ <div style="max-width: 400px">
2
+ <%= render(Primer::OpenProject::FileTreeView.new) do |tree_view| %>
3
+ <% tree_view.with_directory(label: "src", expanded: expanded, select_variant: select_variant) do |dir| %>
4
+ <% dir.with_trailing_visual_icon(icon: :"diff-modified") %>
5
+
6
+ <% dir.with_file(label: "button.rb", select_variant: select_variant) %>
7
+ <% dir.with_file(label: "icon_button.rb", current: true, select_variant: select_variant) %>
8
+
9
+ <% dir.with_directory(label: "tree_view", select_variant: select_variant) do |subdir| %>
10
+ <% subdir.with_file(label: "sub_tree.rb", select_variant: select_variant) %>
11
+ <% end %>
12
+ <% end %>
13
+
14
+ <% tree_view.with_file(label: "action_menu.rb", select_variant: select_variant) %>
15
+ <% end %>
16
+ </div>
@@ -0,0 +1,4 @@
1
+ <% data = TreeViewItemsController::TREE %>
2
+ <%= render(Primer::OpenProject::FileTreeView.new) do |tree| %>
3
+ <% populate.call(tree, data, { select_variant: select_variant, expanded: expanded }) %>
4
+ <% end %>
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # @label FileTreeView
6
+ class FileTreeViewPreview < ViewComponent::Preview
7
+ # @label Default
8
+ #
9
+ # @snapshot interactive
10
+ # @param expanded [Boolean] toggle
11
+ # @param select_variant [Symbol] select [multiple, none]
12
+ def default(expanded: false, select_variant: :none)
13
+ render_with_template(locals: {
14
+ expanded: coerce_bool(expanded),
15
+ select_variant: select_variant.to_sym
16
+ })
17
+ end
18
+
19
+ # @label Playground
20
+ #
21
+ # @param expanded [Boolean] toggle
22
+ # @param select_variant [Symbol] select [multiple, none]
23
+ def playground(expanded: false, select_variant: :none)
24
+ render_with_template(locals: {
25
+ expanded: coerce_bool(expanded),
26
+ select_variant: select_variant.to_sym,
27
+ populate: -> (*args) { populate(*args) }
28
+ })
29
+ end
30
+
31
+ private
32
+
33
+ def coerce_bool(value)
34
+ case value
35
+ when true, false
36
+ value
37
+ when "true"
38
+ true
39
+ when "false"
40
+ false
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ def populate(node, data, node_arguments)
47
+ return unless data
48
+
49
+ entries = (
50
+ data.fetch("children", {}).keys.map { |label, idx| [label, :directory] } +
51
+ data.fetch("files", []).map { |label| [label, :file] }
52
+ )
53
+
54
+ entries.sort_by!(&:first)
55
+
56
+ entries.each do |label, kind, idx|
57
+ case kind
58
+ when :directory
59
+ node.with_directory(label: label, **node_arguments) do |sub_tree|
60
+ populate(sub_tree, data["children"][label], node_arguments)
61
+ end
62
+ when :file
63
+ node.with_file(label: label, **node_arguments)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # @label SkeletonBox
6
+ class SkeletonBoxPreview < ViewComponent::Preview
7
+ # @label Default
8
+ def default
9
+ render(Primer::OpenProject::SkeletonBox.new(width: "64px", height: "64px"))
10
+ end
11
+
12
+ # @label Playground
13
+ # @param width text
14
+ # @param height text
15
+ def playground(width: "64px", height: "64px")
16
+ render(Primer::OpenProject::SkeletonBox.new(width: width, height: height))
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ <div style="max-width: 400px">
2
+ <%= render(Primer::OpenProject::TreeView.new) do |tree_view| %>
3
+ <% tree_view.with_sub_tree(label: "src", expanded: expanded, select_variant: select_variant, select_strategy: select_strategy) do |sub_tree| %>
4
+ <% sub_tree.with_leading_visual_icons(label: "Foobar") do |icons| %>
5
+ <% icons.with_expanded_icon(icon: :"file-directory-open-fill", color: :accent) %>
6
+ <% icons.with_collapsed_icon(icon: :"file-directory-fill", color: :accent) %>
7
+ <% end %>
8
+
9
+ <% sub_tree.with_trailing_visual_icon(icon: :"diff-modified") %>
10
+
11
+ <% sub_tree.with_leaf(label: "button.rb", select_variant: select_variant) do |item| %>
12
+ <% item.with_leading_visual_icon(icon: :file) %>
13
+ <% end %>
14
+
15
+ <% sub_tree.with_leaf(label: "icon_button.rb", current: true, select_variant: select_variant) do |item| %>
16
+ <% item.with_leading_visual_icon(icon: :file) %>
17
+ <% end %>
18
+ <% end %>
19
+
20
+ <% tree_view.with_leaf(label: "action_menu.rb", select_variant: select_variant) do |item| %>
21
+ <% item.with_leading_visual_icon(icon: :file) %>
22
+ <% end %>
23
+ <% end %>
24
+ </div>
@@ -0,0 +1,10 @@
1
+ <div style="max-width: 400px">
2
+ <%= render(Primer::OpenProject::TreeView.new) do |tree_view| %>
3
+ <% tree_view.with_sub_tree(label: "src") do |sub_tree| %>
4
+ <% sub_tree.with_leading_visual_icons do |icons| %>
5
+ <% icons.with_expanded_icon(icon: :"file-directory-open-fill", color: :accent) %>
6
+ <% icons.with_collapsed_icon(icon: :"file-directory-fill", color: :accent) %>
7
+ <% end %>
8
+ <% end %>
9
+ <% end %>
10
+ </div>
@@ -0,0 +1,15 @@
1
+ <%= render(Primer::OpenProject::TreeView.new) do |tree| %>
2
+ <% tree.with_leaf(label: label, select_variant: select_variant) do |node| %>
3
+ <% if leading_visual_icon && leading_visual_icon != :none %>
4
+ <% node.with_leading_visual_icon(icon: leading_visual_icon) %>
5
+ <% end %>
6
+
7
+ <% if trailing_visual_icon && trailing_visual_icon != :none %>
8
+ <% node.with_trailing_visual_icon(icon: trailing_visual_icon) %>
9
+ <% end %>
10
+
11
+ <% if leading_action_icon && leading_action_icon != :none %>
12
+ <% node.with_leading_action_button(icon: leading_action_icon, aria: { label: "Leading action icon" }) %>
13
+ <% end %>
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,36 @@
1
+ <% subject_id = SecureRandom.hex %>
2
+
3
+ <div style="max-width: 400px">
4
+ <%= render(Primer::OpenProject::TreeView.new(data: { interaction_subject: subject_id })) do |tree_view| %>
5
+ <% tree_view.with_sub_tree(label: "primer") do |sub_tree| %>
6
+ <% sub_tree.with_leading_visual_icons do |icons| %>
7
+ <% icons.with_expanded_icon(icon: :"file-directory-open-fill", color: :accent) %>
8
+ <% icons.with_collapsed_icon(icon: :"file-directory-fill", color: :accent) %>
9
+ <% end %>
10
+
11
+ <% sub_tree.with_loading_spinner(src: tree_view_items_path(loader: "spinner", fail: true)) %>
12
+ <% end %>
13
+ <% end %>
14
+ </div>
15
+
16
+ <script>
17
+ function ready(fn) {
18
+ if (document.readyState !== 'loading') {
19
+ fn()
20
+ } else {
21
+ document.addEventListener('DOMContentLoaded', fn)
22
+ }
23
+ }
24
+
25
+ ready(() => {
26
+ const subject = document.querySelector("[data-interaction-subject='<%= subject_id %>']")
27
+ if (!subject) return
28
+
29
+ const includeFragment = subject.querySelector('tree-view-include-fragment')
30
+ if (!includeFragment) return
31
+
32
+ includeFragment.addEventListener('loadend', (event) => {
33
+ subject.setAttribute('data-ready', 'true')
34
+ })
35
+ })
36
+ </script>
@@ -0,0 +1,12 @@
1
+ <div style="max-width: 400px">
2
+ <%= render(Primer::OpenProject::TreeView.new) do |tree_view| %>
3
+ <% tree_view.with_sub_tree(label: "primer") do |sub_tree| %>
4
+ <% sub_tree.with_leading_visual_icons do |icons| %>
5
+ <% icons.with_expanded_icon(icon: :"file-directory-open-fill", color: :accent) %>
6
+ <% icons.with_collapsed_icon(icon: :"file-directory-fill", color: :accent) %>
7
+ <% end %>
8
+
9
+ <% sub_tree.with_loading_skeleton(src: tree_view_items_path(loader: "skeleton", fail: simulate_failure, empty: simulate_empty)) %>
10
+ <% end %>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ <div style="max-width: 400px">
2
+ <%= render(Primer::OpenProject::TreeView.new) do |tree_view| %>
3
+ <% tree_view.with_sub_tree(label: "primer") do |sub_tree| %>
4
+ <% sub_tree.with_leading_visual_icons do |icons| %>
5
+ <% icons.with_expanded_icon(icon: :"file-directory-open-fill", color: :accent) %>
6
+ <% icons.with_collapsed_icon(icon: :"file-directory-fill", color: :accent) %>
7
+ <% end %>
8
+
9
+ <% sub_tree.with_loading_spinner(src: tree_view_items_path(loader: "spinner", fail: simulate_failure, empty: simulate_empty)) %>
10
+ <% end %>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,4 @@
1
+ <% data = TreeViewItemsController::TREE %>
2
+ <%= render(Primer::OpenProject::TreeView.new) do |tree| %>
3
+ <% populate.call(tree, data, { select_variant: select_variant, select_strategy: select_strategy, expanded: expanded }) %>
4
+ <% end %>
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # @label TreeView
6
+ class TreeViewPreview < ViewComponent::Preview
7
+ # @label Default
8
+ #
9
+ # @snapshot interactive
10
+ # @param expanded [Boolean] toggle
11
+ # @param select_variant [Symbol] select [multiple, none]
12
+ # @param select_strategy [Symbol] select [self, descendants]
13
+ def default(expanded: false, select_variant: :none, select_strategy: :descendants)
14
+ render_with_template(locals: {
15
+ expanded: coerce_bool(expanded),
16
+ select_variant: select_variant.to_sym,
17
+ select_strategy: select_strategy.to_sym
18
+ })
19
+ end
20
+
21
+ # @label Playground
22
+ #
23
+ # @param expanded [Boolean] toggle
24
+ # @param select_variant [Symbol] select [multiple, none]
25
+ # @param select_strategy [Symbol] select [self, descendants]
26
+ def playground(expanded: false, select_variant: :none, select_strategy: :descendants)
27
+ render_with_template(locals: {
28
+ expanded: coerce_bool(expanded),
29
+ select_variant: select_variant.to_sym,
30
+ select_strategy: select_strategy.to_sym,
31
+ populate: -> (*args) { populate(*args) }
32
+ })
33
+ end
34
+
35
+ # @label Empty
36
+ #
37
+ # @snapshot interactive
38
+ def empty
39
+ end
40
+
41
+ # @label Loading failure
42
+ #
43
+ # @snapshot interactive
44
+ def loading_failure
45
+ end
46
+
47
+ # @label Loading spinner
48
+ #
49
+ # @snapshot interactive
50
+ # @param simulate_failure [Boolean] toggle
51
+ # @param simulate_empty [Boolean] toggle
52
+ def loading_spinner(simulate_failure: false, simulate_empty: false)
53
+ render_with_template(locals: {
54
+ simulate_failure: coerce_bool(simulate_failure),
55
+ simulate_empty: coerce_bool(simulate_empty),
56
+ })
57
+ end
58
+
59
+ # @label Loading skeleton
60
+ #
61
+ # @snapshot interactive
62
+ # @param simulate_failure [Boolean] toggle
63
+ # @param simulate_empty [Boolean] toggle
64
+ def loading_skeleton(simulate_failure: false, simulate_empty: false)
65
+ render_with_template(locals: {
66
+ simulate_failure: coerce_bool(simulate_failure),
67
+ simulate_empty: coerce_bool(simulate_empty)
68
+ })
69
+ end
70
+
71
+ # @label Leaf node playground
72
+ #
73
+ # @param label [String] text
74
+ # @param leading_visual_icon [Symbol] octicon
75
+ # @param leading_action_icon [Symbol] octicon
76
+ # @param trailing_visual_icon [Symbol] octicon
77
+ # @param select_variant [Symbol] select [multiple, none]
78
+ def leaf_node_playground(
79
+ label: "Leaf node",
80
+ leading_visual_icon: nil,
81
+ leading_action_icon: nil,
82
+ trailing_visual_icon: nil,
83
+ select_variant: :none
84
+ )
85
+ render_with_template(locals: {
86
+ label: label,
87
+ leading_visual_icon: leading_visual_icon,
88
+ leading_action_icon: leading_action_icon,
89
+ trailing_visual_icon: trailing_visual_icon,
90
+ select_variant: select_variant.to_sym
91
+ })
92
+ end
93
+
94
+ private
95
+
96
+ def coerce_bool(value)
97
+ case value
98
+ when true, false
99
+ value
100
+ when "true"
101
+ true
102
+ when "false"
103
+ false
104
+ else
105
+ false
106
+ end
107
+ end
108
+
109
+ def populate(node, data, node_arguments)
110
+ return unless data
111
+
112
+ entries = (
113
+ data.fetch("children", {}).keys.map { |label, idx| [label, :directory] } +
114
+ data.fetch("files", []).map { |label| [label, :file] }
115
+ )
116
+
117
+ entries.sort_by!(&:first)
118
+
119
+ entries.each do |label, kind, idx|
120
+ case kind
121
+ when :directory
122
+ node.with_sub_tree(label: label, **node_arguments) do |sub_tree|
123
+ sub_tree.with_leading_visual_icons do |icons|
124
+ icons.with_expanded_icon(icon: :"file-directory-open-fill", color: :accent)
125
+ icons.with_collapsed_icon(icon: :"file-directory-fill", color: :accent)
126
+ end
127
+
128
+ populate(sub_tree, data["children"][label], node_arguments)
129
+ end
130
+ when :file
131
+ node.with_leaf(label: label, **node_arguments) do |leaf|
132
+ leaf.with_leading_visual_icon(icon: :file)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end