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
@@ -1 +1 @@
1
- .SelectPanel-emptyPanel,.SelectPanel-loadingPanel{min-height:min(var(--overlay-height) - 10rem,100vh - 2rem)}
1
+ .SelectPanel-loadingPanel{min-height:min(calc(var(--overlay-height) - 10rem),calc(100vh - 2rem))}.SelectPanel-emptyPanel{align-items:center;display:flex;justify-content:center;min-height:min(calc(var(--overlay-height) - 23rem),calc(100vh - 2rem))}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alpha/select_panel",
3
3
  "selectors": [
4
- ".SelectPanel-emptyPanel",
5
- ".SelectPanel-loadingPanel"
4
+ ".SelectPanel-loadingPanel",
5
+ ".SelectPanel-emptyPanel"
6
6
  ]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["select_panel.pcss"],"names":[],"mappings":"AAIA,kDACE,0DACF","file":"select_panel.css","sourcesContent":[".SelectPanel-loadingPanel {\n min-height: min(var(--overlay-height) - 10rem, 100vh - 2rem);\n}\n\n.SelectPanel-emptyPanel {\n min-height: min(var(--overlay-height) - 10rem, 100vh - 2rem);\n}\n"]}
1
+ {"version":3,"sources":["select_panel.pcss"],"names":[],"mappings":"AAAA,0BACE,sEACF,CAEA,wBAEE,kBAAmB,CACnB,YAAa,CACb,sBAAuB,CAHvB,sEAIF","file":"select_panel.css","sourcesContent":[".SelectPanel-loadingPanel {\n min-height: min(calc(var(--overlay-height) - 10rem), calc(100vh - 2rem));\n}\n\n.SelectPanel-emptyPanel {\n min-height: min(calc(var(--overlay-height) - 23rem), calc(100vh - 2rem));\n align-items: center;\n display: flex;\n justify-content: center;\n}\n"]}
@@ -97,7 +97,7 @@
97
97
  <%= render(@list) %>
98
98
  <% end %>
99
99
  </div>
100
- <div data-target="select-panel.noResults" class="pt-2 color-border-muted text-center d-flex flex-items-start flex-justify-center SelectPanel-emptyPanel" hidden>
100
+ <div data-target="select-panel.noResults" class="SelectPanel-emptyPanel" hidden>
101
101
  <h2 class="v-align-middle m-3 f5"><%= @no_results_label %></h2>
102
102
  </div>
103
103
  <% end %>
@@ -1,7 +1,10 @@
1
1
  .SelectPanel-loadingPanel {
2
- min-height: min(var(--overlay-height) - 10rem, 100vh - 2rem);
2
+ min-height: min(calc(var(--overlay-height) - 10rem), calc(100vh - 2rem));
3
3
  }
4
4
 
5
5
  .SelectPanel-emptyPanel {
6
- min-height: min(var(--overlay-height) - 10rem, 100vh - 2rem);
6
+ min-height: min(calc(var(--overlay-height) - 23rem), calc(100vh - 2rem));
7
+ align-items: center;
8
+ display: flex;
9
+ justify-content: center;
7
10
  }
@@ -1,4 +1,4 @@
1
- <%= render(Primer::BaseComponent.new(tag: :span, hidden: @hidden, data: { target: @target })) do %>
1
+ <%= render(Primer::BaseComponent.new(tag: :span, hidden: @hidden, data: { target: @target }, **@wrapper_arguments)) do %>
2
2
  <%= render Primer::BaseComponent.new(**@system_arguments) do %>
3
3
  <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none" />
4
4
  <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke" />
@@ -43,6 +43,8 @@ module Primer
43
43
 
44
44
  @target = extract_data(:target, @system_arguments)
45
45
  @hidden = @system_arguments.delete(:hidden)
46
+
47
+ @wrapper_arguments = @system_arguments.delete(:wrapper_arguments) || {}
46
48
  end
47
49
 
48
50
  def no_aria_label?
@@ -1 +1 @@
1
- .CollapsibleHeader{align-items:center;cursor:pointer;display:flex}.Box:has(.CollapsibleHeader--collapsed){border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom-width:var(--borderWidth-thicker)}.Box:has(.CollapsibleHeader--collapsed) .Box-body,.Box:has(.CollapsibleHeader--collapsed) .Box-footer,.Box:has(.CollapsibleHeader--collapsed) .Box-row{display:none}
1
+ .CollapsibleHeader{align-items:center;cursor:pointer;display:flex}.Box:has(.CollapsibleHeader--collapsed){border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom-width:var(--borderWidth-thicker)}.Box:has(.CollapsibleHeader--collapsed) .Box-body,.Box:has(.CollapsibleHeader--collapsed) .Box-footer,.Box:has(.CollapsibleHeader--collapsed) .Box-row{display:none}.CollapsibleHeader--triggerArea{width:100%}
@@ -5,6 +5,7 @@
5
5
  ".Box:has(.CollapsibleHeader--collapsed)",
6
6
  ".Box:has(.CollapsibleHeader--collapsed) .Box-body",
7
7
  ".Box:has(.CollapsibleHeader--collapsed) .Box-footer",
8
- ".Box:has(.CollapsibleHeader--collapsed) .Box-row"
8
+ ".Box:has(.CollapsibleHeader--collapsed) .Box-row",
9
+ ".CollapsibleHeader--triggerArea"
9
10
  ]
10
11
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["collapsible_header.pcss"],"names":[],"mappings":"AAEA,mBAGI,kBAAmB,CAFnB,cAAe,CACf,YAEJ,CAEA,wCACI,2BAA4B,CAC5B,4BAA6B,CAC7B,8CAOJ,CALI,uJAGI,YACJ","file":"collapsible_header.css","sourcesContent":["/* CSS for BorderBox::CollapsibleHeader */\n\n.CollapsibleHeader {\n cursor: pointer;\n display: flex;\n align-items: center;\n}\n\n.Box:has(.CollapsibleHeader--collapsed) {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-width: var(--borderWidth-thicker);\n\n & .Box-row,\n & .Box-body,\n & .Box-footer {\n display: none;\n }\n}\n"]}
1
+ {"version":3,"sources":["collapsible_header.pcss"],"names":[],"mappings":"AAEA,mBAGI,kBAAmB,CAFnB,cAAe,CACf,YAEJ,CAEA,wCACI,2BAA4B,CAC5B,4BAA6B,CAC7B,8CAOJ,CALI,uJAGI,YACJ,CAGJ,gCACI,UACJ","file":"collapsible_header.css","sourcesContent":["/* CSS for BorderBox::CollapsibleHeader */\n\n.CollapsibleHeader {\n cursor: pointer;\n display: flex;\n align-items: center;\n}\n\n.Box:has(.CollapsibleHeader--collapsed) {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-width: var(--borderWidth-thicker);\n\n & .Box-row,\n & .Box-body,\n & .Box-footer {\n display: none;\n }\n}\n\n.CollapsibleHeader--triggerArea {\n width: 100%;\n}\n"]}
@@ -1,5 +1,16 @@
1
1
  <%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
2
- <%= render(Primer::OpenProject::FlexLayout.new) do |flex| %>
2
+ <%= render(Primer::OpenProject::FlexLayout.new(
3
+ data: {
4
+ target: "collapsible-header.triggerElement",
5
+ action: "click:collapsible-header#toggle keydown:collapsible-header#toggleViaKeyboard"
6
+ },
7
+ tabindex: 0,
8
+ role: "button",
9
+ aria: {
10
+ expanded: !@collapsed,
11
+ },
12
+ classes: "CollapsibleHeader--triggerArea"
13
+ )) do |flex| %>
3
14
  <%= flex.with_row do %>
4
15
  <%= title %>
5
16
  <% if count? %>
@@ -17,3 +17,7 @@
17
17
  display: none;
18
18
  }
19
19
  }
20
+
21
+ .CollapsibleHeader--triggerArea {
22
+ width: 100%;
23
+ }
@@ -32,39 +32,43 @@ module Primer
32
32
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
33
33
  renders_one :description, lambda { |**system_arguments, &block|
34
34
  system_arguments[:color] ||= :subtle
35
+ system_arguments[:hidden] = @collapsed
36
+
35
37
  system_arguments[:data] = merge_data(
36
38
  system_arguments, {
37
- data: {
39
+ data: {
38
40
  targets: "collapsible-header.collapsibleElements"
39
41
  }
40
42
  }
41
43
  )
42
- system_arguments[:hidden] = @collapsed
43
44
 
44
45
  Primer::Beta::Text.new(**system_arguments, &block)
45
46
  }
46
47
 
47
-
48
+ # @param id [String] The unique ID of the collapsible header.
48
49
  # @param collapsed [Boolean] Whether the header is collapsed on initial render.
49
50
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
50
- def initialize(collapsed: false, box:, **system_arguments)
51
+ def initialize(id: self.class.generate_id, collapsed: false, box:, **system_arguments)
51
52
  @collapsed = collapsed
52
53
  @box = box
53
54
  @system_arguments = system_arguments
54
-
55
+ @system_arguments[:id] = id
55
56
  @system_arguments[:tag] = "collapsible-header"
56
57
  @system_arguments[:classes] = class_names(
57
58
  system_arguments[:classes],
58
59
  "CollapsibleHeader",
59
60
  "CollapsibleHeader--collapsed" => @collapsed
60
61
  )
61
- @system_arguments[:data] = merge_data(
62
- @system_arguments, {
63
- data: {
64
- action: "click:collapsible-header#toggle"
65
- } }
66
- )
67
- @system_arguments[:data][:collapsed] = true if @collapsed
62
+ if @collapsed
63
+ @system_arguments[:data] = merge_data(
64
+ @system_arguments, {
65
+ data: {
66
+ collapsed: @collapsed
67
+ }
68
+ }
69
+ )
70
+ end
71
+
68
72
  end
69
73
 
70
74
  private
@@ -1,8 +1,10 @@
1
1
  export declare abstract class CollapsibleElement extends HTMLElement {
2
2
  arrowDown: Element;
3
3
  arrowUp: Element;
4
+ triggerElement: HTMLElement;
4
5
  collapsibleElements: HTMLElement[];
5
6
  collapsed: boolean;
7
+ toggleViaKeyboard(event: KeyboardEvent): void;
6
8
  toggle(): void;
7
9
  attributeChangedCallback(name: string): void;
8
10
  hideAll(): void;
@@ -11,6 +11,12 @@ export class CollapsibleElement extends HTMLElement {
11
11
  super(...arguments);
12
12
  this.collapsed = false;
13
13
  }
14
+ toggleViaKeyboard(event) {
15
+ if (event.code === 'Enter' || event.code === 'Space') {
16
+ event.preventDefault();
17
+ this.toggle();
18
+ }
19
+ }
14
20
  toggle() {
15
21
  this.collapsed = !this.collapsed;
16
22
  }
@@ -28,6 +34,7 @@ export class CollapsibleElement extends HTMLElement {
28
34
  // For whatever reason, setting `hidden` directly does not work on the SVGs
29
35
  this.arrowDown?.removeAttribute('hidden');
30
36
  this.arrowUp?.setAttribute('hidden', '');
37
+ this.triggerElement.setAttribute('aria-expanded', 'false');
31
38
  for (const el of this.collapsibleElements) {
32
39
  el.hidden = true;
33
40
  }
@@ -36,6 +43,7 @@ export class CollapsibleElement extends HTMLElement {
36
43
  expandAll() {
37
44
  this.arrowUp?.removeAttribute('hidden');
38
45
  this.arrowDown?.setAttribute('hidden', '');
46
+ this.triggerElement.setAttribute('aria-expanded', 'true');
39
47
  for (const el of this.collapsibleElements) {
40
48
  el.hidden = false;
41
49
  }
@@ -48,6 +56,9 @@ __decorate([
48
56
  __decorate([
49
57
  target
50
58
  ], CollapsibleElement.prototype, "arrowUp", void 0);
59
+ __decorate([
60
+ target
61
+ ], CollapsibleElement.prototype, "triggerElement", void 0);
51
62
  __decorate([
52
63
  targets
53
64
  ], CollapsibleElement.prototype, "collapsibleElements", void 0);
@@ -4,10 +4,18 @@ import {attr, target, targets} from '@github/catalyst'
4
4
  export abstract class CollapsibleElement extends HTMLElement {
5
5
  @target arrowDown: Element
6
6
  @target arrowUp: Element
7
+ @target triggerElement: HTMLElement
7
8
  @targets collapsibleElements: HTMLElement[]
8
9
 
9
10
  @attr collapsed = false
10
11
 
12
+ toggleViaKeyboard(event: KeyboardEvent) {
13
+ if (event.code === 'Enter' || event.code === 'Space') {
14
+ event.preventDefault()
15
+ this.toggle()
16
+ }
17
+ }
18
+
11
19
  toggle() {
12
20
  this.collapsed = !this.collapsed
13
21
  }
@@ -26,6 +34,7 @@ export abstract class CollapsibleElement extends HTMLElement {
26
34
  // For whatever reason, setting `hidden` directly does not work on the SVGs
27
35
  this.arrowDown?.removeAttribute('hidden')
28
36
  this.arrowUp?.setAttribute('hidden', '')
37
+ this.triggerElement.setAttribute('aria-expanded', 'false')
29
38
 
30
39
  for (const el of this.collapsibleElements) {
31
40
  el.hidden = true
@@ -37,6 +46,7 @@ export abstract class CollapsibleElement extends HTMLElement {
37
46
  expandAll() {
38
47
  this.arrowUp?.removeAttribute('hidden')
39
48
  this.arrowDown?.setAttribute('hidden', '')
49
+ this.triggerElement.setAttribute('aria-expanded', 'true')
40
50
 
41
51
  for (const el of this.collapsibleElements) {
42
52
  el.hidden = false
@@ -1,7 +1,14 @@
1
1
  <%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
2
2
  <%= render(Primer::OpenProject::FlexLayout.new) do |flex| %>
3
3
  <%= flex.with_row(classes: "CollapsibleSection--triggerArea",
4
- data: { action: "click:collapsible-section#toggle" }) do %>
4
+ id: @title_id,
5
+ tabindex: 0,
6
+ role: "button",
7
+ data: {
8
+ action: "click:collapsible-section#toggle keydown:collapsible-section#toggleViaKeyboard",
9
+ target: "collapsible-section.triggerElement"
10
+ },
11
+ aria: { expanded: !@collapsed, controls: @content_id }) do %>
5
12
  <%= render(Primer::OpenProject::FlexLayout.new(display: :flex, align_items: :center)) do |header| %>
6
13
  <%= header.with_column do %>
7
14
  <%= title %>
@@ -18,7 +25,12 @@
18
25
  <% end %>
19
26
  <% end %>
20
27
  <% end %>
21
- <%= flex.with_row(hidden: @collapsed, mt: 3, data: { targets: "collapsible-section.collapsibleElements" }) do %>
28
+ <%= flex.with_row(hidden: @collapsed,
29
+ mt: 3,
30
+ role: "region",
31
+ id: @content_id,
32
+ data: { targets: "collapsible-section.collapsibleElements" },
33
+ aria: { labelledby: @title_id }) do %>
22
34
  <%= collapsible_content %>
23
35
  <% end %>
24
36
  <% end %>
@@ -47,13 +47,17 @@ module Primer
47
47
  Primer::BaseComponent.new(tag: :div, **system_arguments)
48
48
  }
49
49
 
50
+ # @param id [String] The unique ID of the collapsible section.
50
51
  # @param collapsed [Boolean] Whether the section is collapsed on initial render.
51
52
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
52
- def initialize(collapsed: false, **system_arguments)
53
+ def initialize(id: self.class.generate_id, collapsed: false, **system_arguments)
54
+ @title_id = "#{id}-title"
55
+ @content_id = "#{id}-content"
53
56
  @collapsed = collapsed
54
57
 
55
58
  @system_arguments = deny_tag_argument(**system_arguments)
56
59
  @system_arguments[:tag] = "collapsible-section"
60
+ @system_arguments[:id] = id
57
61
  @system_arguments[:classes] = class_names(
58
62
  @system_arguments[:classes],
59
63
  "CollapsibleSection",
@@ -1,4 +1,8 @@
1
1
  <%= render @dialog do |dialog| %>
2
+ <% unless header.present? %>
3
+ <% dialog.with_header(close_label: I18n.t("button_close")) %>
4
+ <% end %>
5
+
2
6
  <danger-dialog-form-helper>
3
7
  <%= render(@form_wrapper) do %>
4
8
  <scrollable-region data-labelled-by="<%= labelledby %>">
@@ -1,10 +1,15 @@
1
1
  <%= render @dialog do |dialog| %>
2
+ <% unless header.present? %>
3
+ <% dialog.with_header(close_label: I18n.t("button_close")) %>
4
+ <% end %>
5
+
2
6
  <% dialog.with_body do %>
3
7
  <%= feedback_message %>
4
8
  <% if additional_details.present? %>
5
9
  <%= additional_details %>
6
10
  <% end %>
7
11
  <% end %>
12
+
8
13
  <% dialog.with_footer do %>
9
14
  <% if footer.present? %>
10
15
  <%= footer %>
@@ -0,0 +1,5 @@
1
+ <% with_leading_visual_icons do |icons| %>
2
+ <% icons.with_expanded_icon(icon: :"file-directory-open-fill", **@expanded_icon_arguments) %>
3
+ <% icons.with_collapsed_icon(icon: :"file-directory-fill", **@collapsed_icon_arguments) %>
4
+ <% end %>
5
+ <%= render_parent %>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ class FileTreeView
6
+ class DirectoryNode < TreeView::SubTreeNode
7
+ def initialize(icon_arguments: nil, expanded_icon_arguments: nil, collapsed_icon_arguments: nil, **system_arguments)
8
+ @expanded_icon_arguments = expanded_icon_arguments || icon_arguments || {}
9
+ @collapsed_icon_arguments = collapsed_icon_arguments || icon_arguments || {}
10
+
11
+ super(**system_arguments)
12
+ end
13
+
14
+ def with_file(**system_arguments, &block)
15
+ with_leaf(**system_arguments, component_klass: FileNode, &block)
16
+ end
17
+
18
+ def with_directory(**system_arguments, &block)
19
+ with_sub_tree(**system_arguments, component_klass: DirectoryNode, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,2 @@
1
+ <% with_leading_visual_icon(icon: :file, **@icon_arguments) %>
2
+ <%= render_parent %>
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ class FileTreeView
6
+ class FileNode < TreeView::LeafNode
7
+ def initialize(icon_arguments: {}, **system_arguments)
8
+ @icon_arguments = icon_arguments
9
+ super(**system_arguments)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ class FileTreeView < TreeView
6
+ def with_file(**system_arguments, &block)
7
+ with_leaf(**system_arguments, component_klass: FileNode, &block)
8
+ end
9
+
10
+ def with_directory(**system_arguments, &block)
11
+ with_sub_tree(**system_arguments, component_klass: DirectoryNode, &block)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1 @@
1
+ @keyframes shimmer{0%{mask-position:200%}to{mask-position:0}}.SkeletonBox{animation:shimmer;background-color:var(--bgColor-muted);border-radius:var(--borderRadius-small);display:block;height:1rem}@media (prefers-reduced-motion:no-preference){.SkeletonBox{animation:shimmer;animation-duration:1s;animation-iteration-count:infinite;mask-image:linear-gradient(75deg,#000 30%,#000000a6 80%);mask-size:200%}}@media (forced-colors:active){.SkeletonBox{outline:1px solid #0000;outline-offset:-1px}}
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "open_project/skeleton_box",
3
+ "selectors": [
4
+ ".SkeletonBox"
5
+ ]
6
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["skeleton_box.pcss"],"names":[],"mappings":"AAAA,mBACE,GACE,kBACF,CAEA,GACE,eACF,CACF,CAEA,aAKE,iBAAkB,CAFlB,qCAAsC,CACtC,uCAAwC,CAHxC,aAAc,CACd,WAiBF,CAZE,8CAPF,aAUI,iBAAkB,CAClB,qBAAsB,CACtB,kCAAmC,CAJnC,wDAAqE,CACrE,cAUJ,CANE,CAEA,8BAfF,aAgBI,uBAA8B,CAC9B,mBAEJ,CADE","file":"skeleton_box.css","sourcesContent":["@keyframes shimmer {\n from {\n mask-position: 200%;\n }\n\n to {\n mask-position: 0%;\n }\n}\n\n.SkeletonBox {\n display: block;\n height: 1rem;\n background-color: var(--bgColor-muted);\n border-radius: var(--borderRadius-small);\n animation: shimmer;\n\n @media (prefers-reduced-motion: no-preference) {\n mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);\n mask-size: 200%;\n animation: shimmer;\n animation-duration: 1s;\n animation-iteration-count: infinite;\n }\n\n @media (forced-colors: active) {\n outline: 1px solid transparent;\n outline-offset: -1px;\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ <%= render(Primer::BaseComponent.new(**@system_arguments)) %>
@@ -0,0 +1,30 @@
1
+ @keyframes shimmer {
2
+ from {
3
+ mask-position: 200%;
4
+ }
5
+
6
+ to {
7
+ mask-position: 0%;
8
+ }
9
+ }
10
+
11
+ .SkeletonBox {
12
+ display: block;
13
+ height: 1rem;
14
+ background-color: var(--bgColor-muted);
15
+ border-radius: var(--borderRadius-small);
16
+ animation: shimmer;
17
+
18
+ @media (prefers-reduced-motion: no-preference) {
19
+ mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);
20
+ mask-size: 200%;
21
+ animation: shimmer;
22
+ animation-duration: 1s;
23
+ animation-iteration-count: infinite;
24
+ }
25
+
26
+ @media (forced-colors: active) {
27
+ outline: 1px solid transparent;
28
+ outline-offset: -1px;
29
+ }
30
+ }
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # A SkeletonBox provides a placeholder for non-text, non-Avatar elements (e.g., hero images)
6
+ # that are still loading. You can adjust width and height to match the content's dimensions.
7
+ class SkeletonBox < Primer::Component
8
+ # @param height [String] Any valid CSS height.
9
+ # @param width [String] Any valid CSS width.
10
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
11
+ def initialize(height: nil, width: nil, **system_arguments)
12
+ @system_arguments = deny_tag_argument(**system_arguments)
13
+
14
+ styles = []
15
+ styles << "width: #{width}" if width
16
+ styles << "height: #{height}" if height
17
+
18
+ @system_arguments[:tag] = :div
19
+ @system_arguments[:style] = styles.join("; ") if styles.present?
20
+ @system_arguments[:classes] = class_names(
21
+ @system_arguments.delete(:classes),
22
+ "SkeletonBox"
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1 @@
1
+ <%= render(Primer::Beta::Octicon.new(**@system_arguments)) %>
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ class TreeView
6
+ # An icon for a `TreeView` node.
7
+ #
8
+ # This component is part of the <%= link_to_component(Primer::OpenProject::TreeView) %> component and should
9
+ # not be used directly.
10
+ class Icon < Primer::Component
11
+ # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Octicon) %>.
12
+ def initialize(**system_arguments)
13
+ @system_arguments = system_arguments
14
+ @system_arguments[:focusable] = "false"
15
+ @system_arguments[:display] = :inline_block
16
+ @system_arguments[:overflow] = :visible
17
+ @system_arguments[:style] = "vertical-align: text-bottom;"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ <%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
2
+ <% if expanded_icon? %>
3
+ <div data-target="tree-view-icon-pair.expandedIcon" <%= expanded? ? "" : "hidden" %>>
4
+ <%= expanded_icon %>
5
+ </div>
6
+ <% end %>
7
+
8
+ <% if collapsed_icon? %>
9
+ <div data-target="tree-view-icon-pair.collapsedIcon" <%= expanded? ? "hidden" : "" %>>
10
+ <%= collapsed_icon %>
11
+ </div>
12
+ <% end %>
13
+ <% end %>
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ class TreeView
6
+ # A pair of icons for a `TreeView` sub-tree that displays distinct icons when the sub-tree is
7
+ # expanded and collapsed.
8
+ #
9
+ # This component is part of the <%= link_to_component(Primer::OpenProject::TreeView) %> component and should
10
+ # not be used directly.
11
+ class IconPair < Primer::Component
12
+ # The expanded icon.
13
+ #
14
+ # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::OpenProject::TreeView::Icon) %>.
15
+ renders_one :expanded_icon, lambda { |**system_arguments|
16
+ Icon.new(**system_arguments)
17
+ }
18
+
19
+ # The collapsed icon.
20
+ #
21
+ # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::OpenProject::TreeView::Icon) %>.
22
+ renders_one :collapsed_icon, lambda { |**system_arguments|
23
+ Icon.new(**system_arguments)
24
+ }
25
+
26
+ # Whether or not this icon is expanded.
27
+ #
28
+ # @return [Boolean]
29
+ attr_reader :expanded
30
+ alias expanded? expanded
31
+
32
+ # @param expanded [Boolean] If true, the expanded icon is shown and the collapsed icon is hidden, etc.
33
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
34
+ def initialize(expanded: false, **system_arguments)
35
+ @expanded = expanded
36
+ @system_arguments = deny_tag_argument(**system_arguments)
37
+ @system_arguments[:tag] = :"tree-view-icon-pair"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ <div class="TreeViewItemLeadingAction" aria-hidden="true">
2
+ <%= render(@action) %>
3
+ </div>
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ class TreeView
6
+ # The leading action for `TreeView` nodes.
7
+ #
8
+ # This component is part of the <%= link_to_component(Primer::OpenProject::TreeView) %> component and should
9
+ # not be used directly.
10
+ class LeadingAction < Primer::Component
11
+ # @param action [ViewComponent::Base] A component or other renderable to use as the action button etc.
12
+ def initialize(action:)
13
+ @action = action
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ <%= render(@node) do |node| %>
2
+ <% if leading_action? %>
3
+ <% node.with_leading_action do %>
4
+ <%= leading_action %>
5
+ <% end %>
6
+ <% end %>
7
+ <% if leading_visual? %>
8
+ <% node.with_leading_visual do %>
9
+ <%= leading_visual %>
10
+ <% end %>
11
+ <% end %>
12
+ <% if trailing_visual? %>
13
+ <% node.with_trailing_visual do %>
14
+ <%= trailing_visual %>
15
+ <% end %>
16
+ <% end %>
17
+ <% node.with_text_content { @label } %>
18
+ <% end %>