openproject-primer_view_components 0.64.1 → 0.66.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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view.d.ts +29 -0
  4. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_icon_pair_element.d.ts +15 -0
  5. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_include_fragment_element.d.ts +9 -0
  6. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_roving_tab_index.d.ts +3 -0
  7. data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +38 -0
  8. data/app/assets/javascripts/components/primer/primer.d.ts +4 -0
  9. data/app/assets/javascripts/components/primer/shared_events.d.ts +15 -0
  10. data/app/assets/javascripts/primer_view_components.js +1 -1
  11. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  12. data/app/assets/styles/primer_view_components.css +1 -1
  13. data/app/assets/styles/primer_view_components.css.map +1 -1
  14. data/app/components/primer/alpha/select_panel.css +1 -1
  15. data/app/components/primer/alpha/select_panel.css.json +2 -2
  16. data/app/components/primer/alpha/select_panel.css.map +1 -1
  17. data/app/components/primer/alpha/select_panel.html.erb +1 -1
  18. data/app/components/primer/alpha/select_panel.pcss +5 -2
  19. data/app/components/primer/beta/spinner.html.erb +1 -1
  20. data/app/components/primer/beta/spinner.rb +2 -0
  21. data/app/components/primer/open_project/file_tree_view/directory_node.html.erb +5 -0
  22. data/app/components/primer/open_project/file_tree_view/directory_node.rb +24 -0
  23. data/app/components/primer/open_project/file_tree_view/file_node.html.erb +2 -0
  24. data/app/components/primer/open_project/file_tree_view/file_node.rb +14 -0
  25. data/app/components/primer/open_project/file_tree_view.rb +15 -0
  26. data/app/components/primer/open_project/page_header.rb +1 -1
  27. data/app/components/primer/open_project/skeleton_box.css +1 -0
  28. data/app/components/primer/open_project/skeleton_box.css.json +6 -0
  29. data/app/components/primer/open_project/skeleton_box.css.map +1 -0
  30. data/app/components/primer/open_project/skeleton_box.html.erb +1 -0
  31. data/app/components/primer/open_project/skeleton_box.pcss +30 -0
  32. data/app/components/primer/open_project/skeleton_box.rb +27 -0
  33. data/app/components/primer/open_project/sub_header/button.rb +43 -0
  34. data/app/components/primer/open_project/sub_header/button_group.rb +16 -0
  35. data/app/components/primer/open_project/sub_header/menu.rb +67 -0
  36. data/app/components/primer/open_project/sub_header/segmented_control.rb +16 -0
  37. data/app/components/primer/open_project/sub_header.css +1 -1
  38. data/app/components/primer/open_project/sub_header.css.json +4 -1
  39. data/app/components/primer/open_project/sub_header.css.map +1 -1
  40. data/app/components/primer/open_project/sub_header.html.erb +21 -0
  41. data/app/components/primer/open_project/sub_header.pcss +29 -3
  42. data/app/components/primer/open_project/sub_header.rb +105 -21
  43. data/app/components/primer/open_project/tree_view/icon.html.erb +1 -0
  44. data/app/components/primer/open_project/tree_view/icon.rb +22 -0
  45. data/app/components/primer/open_project/tree_view/icon_pair.html.erb +13 -0
  46. data/app/components/primer/open_project/tree_view/icon_pair.rb +42 -0
  47. data/app/components/primer/open_project/tree_view/leading_action.html.erb +3 -0
  48. data/app/components/primer/open_project/tree_view/leading_action.rb +18 -0
  49. data/app/components/primer/open_project/tree_view/leaf_node.html.erb +18 -0
  50. data/app/components/primer/open_project/tree_view/leaf_node.rb +96 -0
  51. data/app/components/primer/open_project/tree_view/loading_failure_message.html.erb +13 -0
  52. data/app/components/primer/open_project/tree_view/loading_failure_message.rb +31 -0
  53. data/app/components/primer/open_project/tree_view/node.html.erb +32 -0
  54. data/app/components/primer/open_project/tree_view/node.rb +155 -0
  55. data/app/components/primer/open_project/tree_view/skeleton_loader.html.erb +23 -0
  56. data/app/components/primer/open_project/tree_view/skeleton_loader.rb +36 -0
  57. data/app/components/primer/open_project/tree_view/spinner_loader.html.erb +20 -0
  58. data/app/components/primer/open_project/tree_view/spinner_loader.rb +33 -0
  59. data/app/components/primer/open_project/tree_view/sub_tree.html.erb +21 -0
  60. data/app/components/primer/open_project/tree_view/sub_tree.rb +106 -0
  61. data/app/components/primer/open_project/tree_view/sub_tree_container.html.erb +3 -0
  62. data/app/components/primer/open_project/tree_view/sub_tree_container.rb +39 -0
  63. data/app/components/primer/open_project/tree_view/sub_tree_node.html.erb +49 -0
  64. data/app/components/primer/open_project/tree_view/sub_tree_node.rb +172 -0
  65. data/app/components/primer/open_project/tree_view/tree_view.d.ts +29 -0
  66. data/app/components/primer/open_project/tree_view/tree_view.js +238 -0
  67. data/app/components/primer/open_project/tree_view/tree_view.ts +257 -0
  68. data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.d.ts +15 -0
  69. data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.js +62 -0
  70. data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.ts +56 -0
  71. data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.d.ts +9 -0
  72. data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.js +29 -0
  73. data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.ts +29 -0
  74. data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.d.ts +3 -0
  75. data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.js +126 -0
  76. data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.ts +156 -0
  77. data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +38 -0
  78. data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.js +362 -0
  79. data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.ts +402 -0
  80. data/app/components/primer/open_project/tree_view/visual.html.erb +14 -0
  81. data/app/components/primer/open_project/tree_view/visual.rb +27 -0
  82. data/app/components/primer/open_project/tree_view.css +1 -0
  83. data/app/components/primer/open_project/tree_view.css.json +42 -0
  84. data/app/components/primer/open_project/tree_view.css.map +1 -0
  85. data/app/components/primer/open_project/tree_view.html.erb +7 -0
  86. data/app/components/primer/open_project/tree_view.pcss +319 -0
  87. data/app/components/primer/open_project/tree_view.rb +367 -0
  88. data/app/components/primer/primer.d.ts +4 -0
  89. data/app/components/primer/primer.js +4 -0
  90. data/app/components/primer/primer.pcss +2 -0
  91. data/app/components/primer/primer.ts +4 -0
  92. data/app/components/primer/shared_events.d.ts +15 -0
  93. data/app/components/primer/shared_events.ts +19 -0
  94. data/app/lib/primer/forms/acts_as_component.rb +1 -12
  95. data/lib/primer/view_components/version.rb +2 -2
  96. data/previews/primer/open_project/file_tree_view_preview/default.html.erb +16 -0
  97. data/previews/primer/open_project/file_tree_view_preview/playground.html.erb +4 -0
  98. data/previews/primer/open_project/file_tree_view_preview.rb +69 -0
  99. data/previews/primer/open_project/page_header_preview/create_action.html.erb +1 -2
  100. data/previews/primer/open_project/skeleton_box_preview.rb +20 -0
  101. data/previews/primer/open_project/sub_header_preview/action_menu_buttons.html.erb +7 -10
  102. data/previews/primer/open_project/sub_header_preview/button_group.html.erb +13 -7
  103. data/previews/primer/open_project/sub_header_preview/custom_filter_button.html.erb +1 -1
  104. data/previews/primer/open_project/sub_header_preview/dialog_buttons.html.erb +9 -8
  105. data/previews/primer/open_project/sub_header_preview.rb +26 -7
  106. data/previews/primer/open_project/tree_view_preview/default.html.erb +24 -0
  107. data/previews/primer/open_project/tree_view_preview/empty.html.erb +10 -0
  108. data/previews/primer/open_project/tree_view_preview/leaf_node_playground.html.erb +15 -0
  109. data/previews/primer/open_project/tree_view_preview/loading_failure.html.erb +36 -0
  110. data/previews/primer/open_project/tree_view_preview/loading_skeleton.html.erb +12 -0
  111. data/previews/primer/open_project/tree_view_preview/loading_spinner.html.erb +12 -0
  112. data/previews/primer/open_project/tree_view_preview/playground.html.erb +4 -0
  113. data/previews/primer/open_project/tree_view_preview.rb +139 -0
  114. data/static/arguments.json +512 -0
  115. data/static/audited_at.json +21 -0
  116. data/static/classes.json +18 -0
  117. data/static/constants.json +108 -1
  118. data/static/info_arch.json +1612 -0
  119. data/static/previews.json +180 -0
  120. data/static/statuses.json +21 -0
  121. metadata +79 -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,8 +5,8 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 64
9
- PATCH = 1
8
+ MINOR = 66
9
+ PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
12
12
  end
@@ -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
@@ -13,8 +13,7 @@
13
13
  "Filter"
14
14
  end
15
15
 
16
- component.with_action_button(scheme: :primary) do |button|
17
- button.with_leading_visual_icon(icon: :plus)
16
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
18
17
  "User"
19
18
  end
20
19
  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
@@ -1,15 +1,12 @@
1
1
  <%= render(Primer::OpenProject::SubHeader.new) do |component| %>
2
2
  <% component.with_filter_input(name: "filter", label: "Filter") %>
3
3
 
4
- <% component.with_action_component do %>
5
- <%= render Primer::Alpha::ActionMenu.new(menu_id: "menu-1") do |menu|
6
- menu.with_show_button(icon: :"kebab-horizontal", "aria-label": "Menu")
7
- menu.with_item(label: "Subitem 1") do |item|
8
- item.with_leading_visual_icon(icon: :paste)
9
- end
10
- menu.with_item(label: "Subitem 2") do |item|
11
- item.with_leading_visual_icon(icon: :log)
12
- end
13
- end %>
4
+ <% component.with_action_menu(leading_icon: :plus, trailing_icon: :"triangle-down", label: "Create", button_arguments: { scheme: :primary, "aria-label": "Menu"}) do |menu| %>
5
+ <%= menu.with_item(label: "Subitem 1") do |item|
6
+ item.with_leading_visual_icon(icon: :paste)
7
+ end
8
+ menu.with_item(label: "Subitem 2") do |item|
9
+ item.with_leading_visual_icon(icon: :log)
10
+ end %>
14
11
  <% end %>
15
12
  <% end %>
@@ -1,11 +1,17 @@
1
1
  <%= render(Primer::OpenProject::SubHeader.new) do |component| %>
2
2
  <% component.with_filter_input(name: "filter", label: "Filter") %>
3
3
 
4
- <% component.with_action_component do %>
5
- <%= render(Primer::Beta::ButtonGroup.new) do |group|
6
- group.with_button { "Button 1" }
7
- group.with_button { "Button 2" }
8
- group.with_button { "Button 3" }
9
- end %>
10
- <% end %>
4
+ <% component.with_action_button_group do |group|
5
+ group.with_button(icon: :note, "aria-label": "Button 1")
6
+ group.with_button(icon: :rows, "aria-label": "Button 2")
7
+ group.with_button(icon: "sort-desc", "aria-label": "Button 3")
8
+ end %>
9
+
10
+ <% component.with_action_button(icon_only: true, leading_icon: :star, label: "Star") do
11
+ "Star"
12
+ end %>
13
+
14
+ <% component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
15
+ "Create"
16
+ end %>
11
17
  <% end %>
@@ -3,6 +3,6 @@
3
3
  <% component.with_filter_component do %>
4
4
  <!-- Render any custom filter component that you want -->
5
5
  <!-- Note, that you can pass an ID for the bottom pane if you need to access that area for further filter actions-->
6
- <%= render(Primer::Beta::IconButton.new(icon: "filter", "aria-label": "Filter")) %>
6
+ <%= render(Primer::Beta::IconButton.new(icon: "filter-remove", "aria-label": "Filter")) %>
7
7
  <% end %>
8
8
  <% end %>
@@ -1,12 +1,13 @@
1
1
  <%= render(Primer::OpenProject::SubHeader.new) do |component| %>
2
2
  <% component.with_filter_input(name: "filter", label: "Filter") %>
3
3
 
4
- <% component.with_action_component do %>
5
- <%= render(Primer::Alpha::Dialog.new(id: "dialog-one", title: "Dialog")) do |d| %>
6
- <% d.with_show_button { "Show Dialog" } %>
7
- <% d.with_body do %>
8
- Hello world!
9
- <% end %>
10
- <% end %>
11
- <% end %>
4
+ <% component.with_text { "Only async dialogs are supported in the SubHeader!" } %>
5
+
6
+ <% component.with_action_button(leading_icon: :alert,
7
+ label: "Open Dialog",
8
+ #tag: :a,
9
+ #href: "show_dialog_path",
10
+ data: { controller: "async-dialog" }) do
11
+ "Open Dialog"
12
+ end %>
12
13
  <% end %>
@@ -35,9 +35,8 @@ module Primer
35
35
 
36
36
  component.with_text { text } unless text.nil?
37
37
 
38
- component.with_action_button(scheme: :primary) do |button|
39
- button.with_leading_visual_icon(icon: :plus)
40
- "Create"
38
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
39
+ "Create"
41
40
  end if show_action_button
42
41
  end
43
42
  end
@@ -51,8 +50,7 @@ module Primer
51
50
  "Filter"
52
51
  end
53
52
 
54
- component.with_action_button(scheme: :primary) do |button|
55
- button.with_leading_visual_icon(icon: :plus)
53
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
56
54
  "Create"
57
55
  end
58
56
  end
@@ -64,6 +62,9 @@ module Primer
64
62
  end
65
63
 
66
64
  # @label With Dialog
65
+ # Only async dialogs are supported in the SubHeader.
66
+ # Since we duplicate the buttons for mobile purposes,
67
+ # the dialog would otherwise be duplicated (with the same ID) as well
67
68
  def dialog_buttons
68
69
  render_with_template(locals: {})
69
70
  end
@@ -78,6 +79,25 @@ module Primer
78
79
  render_with_template(locals: {})
79
80
  end
80
81
 
82
+ # @label With SegmentedControl
83
+ def segmented_control
84
+ render(Primer::OpenProject::SubHeader.new) do |component|
85
+ component.with_filter_button do |button|
86
+ button.with_trailing_visual_counter(count: "15")
87
+ "Filter"
88
+ end
89
+
90
+ component.with_segmented_control("aria-label": "Segmented control") do |control|
91
+ control.with_item(tag: :a, href: "#", label: "Preview", icon: :eye, selected: true)
92
+ control.with_item(tag: :a, href: "#", label: "Raw", icon: :"file-code")
93
+ end
94
+
95
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
96
+ "Create"
97
+ end
98
+ end
99
+ end
100
+
81
101
  # @label With a custom area below
82
102
  def bottom_pane
83
103
  render_with_template(locals: {})
@@ -90,8 +110,7 @@ module Primer
90
110
 
91
111
  component.with_text { "Hello world!" }
92
112
 
93
- component.with_action_button(scheme: :primary) do |button|
94
- button.with_leading_visual_icon(icon: :plus)
113
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
95
114
  "Create"
96
115
  end
97
116
  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