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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view.d.ts +29 -0
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_icon_pair_element.d.ts +15 -0
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_include_fragment_element.d.ts +9 -0
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_roving_tab_index.d.ts +3 -0
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +38 -0
- data/app/assets/javascripts/components/primer/primer.d.ts +4 -0
- data/app/assets/javascripts/components/primer/shared_events.d.ts +15 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/assets/styles/primer_view_components.css +1 -1
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/select_panel.css +1 -1
- data/app/components/primer/alpha/select_panel.css.json +2 -2
- data/app/components/primer/alpha/select_panel.css.map +1 -1
- data/app/components/primer/alpha/select_panel.html.erb +1 -1
- data/app/components/primer/alpha/select_panel.pcss +5 -2
- data/app/components/primer/beta/spinner.html.erb +1 -1
- data/app/components/primer/beta/spinner.rb +2 -0
- data/app/components/primer/open_project/file_tree_view/directory_node.html.erb +5 -0
- data/app/components/primer/open_project/file_tree_view/directory_node.rb +24 -0
- data/app/components/primer/open_project/file_tree_view/file_node.html.erb +2 -0
- data/app/components/primer/open_project/file_tree_view/file_node.rb +14 -0
- data/app/components/primer/open_project/file_tree_view.rb +15 -0
- data/app/components/primer/open_project/page_header.rb +1 -1
- data/app/components/primer/open_project/skeleton_box.css +1 -0
- data/app/components/primer/open_project/skeleton_box.css.json +6 -0
- data/app/components/primer/open_project/skeleton_box.css.map +1 -0
- data/app/components/primer/open_project/skeleton_box.html.erb +1 -0
- data/app/components/primer/open_project/skeleton_box.pcss +30 -0
- data/app/components/primer/open_project/skeleton_box.rb +27 -0
- data/app/components/primer/open_project/sub_header/button.rb +43 -0
- data/app/components/primer/open_project/sub_header/button_group.rb +16 -0
- data/app/components/primer/open_project/sub_header/menu.rb +67 -0
- data/app/components/primer/open_project/sub_header/segmented_control.rb +16 -0
- data/app/components/primer/open_project/sub_header.css +1 -1
- data/app/components/primer/open_project/sub_header.css.json +4 -1
- data/app/components/primer/open_project/sub_header.css.map +1 -1
- data/app/components/primer/open_project/sub_header.html.erb +21 -0
- data/app/components/primer/open_project/sub_header.pcss +29 -3
- data/app/components/primer/open_project/sub_header.rb +105 -21
- data/app/components/primer/open_project/tree_view/icon.html.erb +1 -0
- data/app/components/primer/open_project/tree_view/icon.rb +22 -0
- data/app/components/primer/open_project/tree_view/icon_pair.html.erb +13 -0
- data/app/components/primer/open_project/tree_view/icon_pair.rb +42 -0
- data/app/components/primer/open_project/tree_view/leading_action.html.erb +3 -0
- data/app/components/primer/open_project/tree_view/leading_action.rb +18 -0
- data/app/components/primer/open_project/tree_view/leaf_node.html.erb +18 -0
- data/app/components/primer/open_project/tree_view/leaf_node.rb +96 -0
- data/app/components/primer/open_project/tree_view/loading_failure_message.html.erb +13 -0
- data/app/components/primer/open_project/tree_view/loading_failure_message.rb +31 -0
- data/app/components/primer/open_project/tree_view/node.html.erb +32 -0
- data/app/components/primer/open_project/tree_view/node.rb +155 -0
- data/app/components/primer/open_project/tree_view/skeleton_loader.html.erb +23 -0
- data/app/components/primer/open_project/tree_view/skeleton_loader.rb +36 -0
- data/app/components/primer/open_project/tree_view/spinner_loader.html.erb +20 -0
- data/app/components/primer/open_project/tree_view/spinner_loader.rb +33 -0
- data/app/components/primer/open_project/tree_view/sub_tree.html.erb +21 -0
- data/app/components/primer/open_project/tree_view/sub_tree.rb +106 -0
- data/app/components/primer/open_project/tree_view/sub_tree_container.html.erb +3 -0
- data/app/components/primer/open_project/tree_view/sub_tree_container.rb +39 -0
- data/app/components/primer/open_project/tree_view/sub_tree_node.html.erb +49 -0
- data/app/components/primer/open_project/tree_view/sub_tree_node.rb +172 -0
- data/app/components/primer/open_project/tree_view/tree_view.d.ts +29 -0
- data/app/components/primer/open_project/tree_view/tree_view.js +238 -0
- data/app/components/primer/open_project/tree_view/tree_view.ts +257 -0
- data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.d.ts +15 -0
- data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.js +62 -0
- data/app/components/primer/open_project/tree_view/tree_view_icon_pair_element.ts +56 -0
- data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.d.ts +9 -0
- data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.js +29 -0
- data/app/components/primer/open_project/tree_view/tree_view_include_fragment_element.ts +29 -0
- data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.d.ts +3 -0
- data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.js +126 -0
- data/app/components/primer/open_project/tree_view/tree_view_roving_tab_index.ts +156 -0
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +38 -0
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.js +362 -0
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.ts +402 -0
- data/app/components/primer/open_project/tree_view/visual.html.erb +14 -0
- data/app/components/primer/open_project/tree_view/visual.rb +27 -0
- data/app/components/primer/open_project/tree_view.css +1 -0
- data/app/components/primer/open_project/tree_view.css.json +42 -0
- data/app/components/primer/open_project/tree_view.css.map +1 -0
- data/app/components/primer/open_project/tree_view.html.erb +7 -0
- data/app/components/primer/open_project/tree_view.pcss +319 -0
- data/app/components/primer/open_project/tree_view.rb +367 -0
- data/app/components/primer/primer.d.ts +4 -0
- data/app/components/primer/primer.js +4 -0
- data/app/components/primer/primer.pcss +2 -0
- data/app/components/primer/primer.ts +4 -0
- data/app/components/primer/shared_events.d.ts +15 -0
- data/app/components/primer/shared_events.ts +19 -0
- data/app/lib/primer/forms/acts_as_component.rb +1 -12
- data/lib/primer/view_components/version.rb +2 -2
- data/previews/primer/open_project/file_tree_view_preview/default.html.erb +16 -0
- data/previews/primer/open_project/file_tree_view_preview/playground.html.erb +4 -0
- data/previews/primer/open_project/file_tree_view_preview.rb +69 -0
- data/previews/primer/open_project/page_header_preview/create_action.html.erb +1 -2
- data/previews/primer/open_project/skeleton_box_preview.rb +20 -0
- data/previews/primer/open_project/sub_header_preview/action_menu_buttons.html.erb +7 -10
- data/previews/primer/open_project/sub_header_preview/button_group.html.erb +13 -7
- data/previews/primer/open_project/sub_header_preview/custom_filter_button.html.erb +1 -1
- data/previews/primer/open_project/sub_header_preview/dialog_buttons.html.erb +9 -8
- data/previews/primer/open_project/sub_header_preview.rb +26 -7
- data/previews/primer/open_project/tree_view_preview/default.html.erb +24 -0
- data/previews/primer/open_project/tree_view_preview/empty.html.erb +10 -0
- data/previews/primer/open_project/tree_view_preview/leaf_node_playground.html.erb +15 -0
- data/previews/primer/open_project/tree_view_preview/loading_failure.html.erb +36 -0
- data/previews/primer/open_project/tree_view_preview/loading_skeleton.html.erb +12 -0
- data/previews/primer/open_project/tree_view_preview/loading_spinner.html.erb +12 -0
- data/previews/primer/open_project/tree_view_preview/playground.html.erb +4 -0
- data/previews/primer/open_project/tree_view_preview.rb +139 -0
- data/static/arguments.json +512 -0
- data/static/audited_at.json +21 -0
- data/static/classes.json +18 -0
- data/static/constants.json +108 -1
- data/static/info_arch.json +1612 -0
- data/static/previews.json +180 -0
- data/static/statuses.json +21 -0
- metadata +79 -2
@@ -10,6 +10,9 @@ module Primer
|
|
10
10
|
HIDDEN_FILTER_TARGET_SELECTOR = "sub-header.hiddenItemsOnExpandedFilter"
|
11
11
|
SHOWN_FILTER_TARGET_SELECTOR = "sub-header.shownItemsOnExpandedFilter"
|
12
12
|
|
13
|
+
MOBILE_ACTIONS_DISPLAY = [:flex, :none].freeze
|
14
|
+
DESKTOP_ACTIONS_DISPLAY = [:none, :flex].freeze
|
15
|
+
|
13
16
|
# A button or custom content that will render on the right-hand side of the component.
|
14
17
|
#
|
15
18
|
# To render a button, call the `with_button` method, which accepts the arguments accepted by <%= link_to_component(Primer::Beta::Button) %>.
|
@@ -17,25 +20,63 @@ module Primer
|
|
17
20
|
# To render custom content, call the `with_button_component` method and pass a block that returns HTML.
|
18
21
|
renders_many :actions, types: {
|
19
22
|
button: {
|
20
|
-
renders: lambda { |
|
21
|
-
if
|
22
|
-
|
23
|
+
renders: lambda { |icon_only: false, leading_icon:, label:, **kwargs, &block|
|
24
|
+
if label.nil? || label.empty?
|
25
|
+
raise ArgumentError, "You need to provide a valid label."
|
26
|
+
end
|
27
|
+
|
28
|
+
kwargs[:icon] = leading_icon
|
29
|
+
|
30
|
+
kwargs[:aria] ||= merge_aria(
|
31
|
+
kwargs,
|
32
|
+
{ aria: { label: label } }
|
33
|
+
)
|
34
|
+
|
35
|
+
icon_args = kwargs.deep_dup
|
36
|
+
|
37
|
+
if icon_only
|
38
|
+
Primer::Beta::IconButton.new(**icon_args)
|
23
39
|
else
|
24
|
-
|
40
|
+
@mobile_actions ||= []
|
41
|
+
mobile_component = Primer::Beta::IconButton.new(display: MOBILE_ACTIONS_DISPLAY,
|
42
|
+
**icon_args)
|
43
|
+
@mobile_actions.push({ component: mobile_component, block: block})
|
44
|
+
|
45
|
+
Primer::OpenProject::SubHeader::Button.new(display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
|
25
46
|
end
|
26
47
|
},
|
27
48
|
},
|
28
|
-
|
29
|
-
# A generic slot to render whatever component you like on the right side
|
49
|
+
button_group: {
|
30
50
|
renders: lambda { |**kwargs|
|
31
|
-
|
32
|
-
|
33
|
-
|
51
|
+
Primer::OpenProject::SubHeader::ButtonGroup.new(**kwargs)
|
52
|
+
},
|
53
|
+
},
|
54
|
+
menu: {
|
55
|
+
renders: lambda { |icon_only: false, leading_icon:, label:, button_arguments: {}, **kwargs, &block|
|
56
|
+
if label.nil? || label.empty?
|
57
|
+
raise ArgumentError, "You need to provide a valid label."
|
58
|
+
end
|
59
|
+
|
60
|
+
kwargs[:leading_icon] = leading_icon
|
61
|
+
kwargs[:label] = label
|
62
|
+
kwargs[:button_arguments] = button_arguments
|
63
|
+
|
64
|
+
@mobile_actions ||= []
|
65
|
+
mobile_component = Primer::OpenProject::SubHeader::Menu.new(icon_only: true,
|
66
|
+
display: MOBILE_ACTIONS_DISPLAY,
|
67
|
+
**kwargs)
|
68
|
+
@mobile_actions.push({ component: mobile_component, block: block})
|
69
|
+
|
70
|
+
Primer::OpenProject::SubHeader::Menu.new(icon_only: icon_only,display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
|
34
71
|
},
|
35
72
|
}
|
36
73
|
}
|
37
74
|
|
38
75
|
renders_one :filter_input, lambda { |name:, label:, **system_arguments|
|
76
|
+
if label.nil? || label.empty?
|
77
|
+
raise ArgumentError, "You need to provide a valid label."
|
78
|
+
end
|
79
|
+
|
39
80
|
system_arguments[:classes] = class_names(
|
40
81
|
system_arguments[:classes],
|
41
82
|
"SubHeader-filterInput",
|
@@ -62,6 +103,7 @@ module Primer
|
|
62
103
|
@mobile_filter_trigger = Primer::Beta::IconButton.new(icon: system_arguments[:leading_visual][:icon],
|
63
104
|
display: [:inline_flex, :none],
|
64
105
|
aria: { label: label },
|
106
|
+
mr: 2,
|
65
107
|
"data-action": "click:sub-header#expandFilterInput",
|
66
108
|
"data-targets": HIDDEN_FILTER_TARGET_SELECTOR)
|
67
109
|
|
@@ -83,18 +125,25 @@ module Primer
|
|
83
125
|
# To render custom content, call the `with_filter_component` method and pass a block that returns HTML.
|
84
126
|
renders_one :filter_button, types: {
|
85
127
|
button: {
|
86
|
-
renders: lambda { |
|
87
|
-
kwargs[:
|
88
|
-
|
89
|
-
|
128
|
+
renders: lambda { |icon_only: false, leading_icon: :filter, mobile_label: I18n.t("button_filter"), **kwargs|
|
129
|
+
kwargs[:mr] ||= 2
|
130
|
+
kwargs[:icon] = leading_icon
|
131
|
+
|
132
|
+
kwargs[:aria] ||= merge_aria(
|
133
|
+
kwargs,
|
134
|
+
{ aria: { label: mobile_label } }
|
90
135
|
)
|
91
|
-
kwargs[:data] ||= {}
|
92
|
-
kwargs[:data][:targets] ||= HIDDEN_FILTER_TARGET_SELECTOR
|
93
136
|
|
94
|
-
|
95
|
-
|
137
|
+
icon_args = kwargs.deep_dup
|
138
|
+
icon_args = set_as_hidden_filter_target(icon_args)
|
139
|
+
|
140
|
+
if icon_only
|
141
|
+
Primer::Beta::IconButton.new(**icon_args)
|
96
142
|
else
|
97
|
-
Primer::Beta::
|
143
|
+
@mobile_filter_button = Primer::Beta::IconButton.new(display: MOBILE_ACTIONS_DISPLAY,
|
144
|
+
**icon_args)
|
145
|
+
|
146
|
+
Primer::OpenProject::SubHeader::Button.new(display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
|
98
147
|
end
|
99
148
|
},
|
100
149
|
|
@@ -105,8 +154,7 @@ module Primer
|
|
105
154
|
renders: lambda { |**kwargs|
|
106
155
|
deny_tag_argument(**kwargs)
|
107
156
|
kwargs[:tag] = :div
|
108
|
-
kwargs
|
109
|
-
kwargs[:data][:targets] ||= HIDDEN_FILTER_TARGET_SELECTOR
|
157
|
+
kwargs = set_as_hidden_filter_target(kwargs)
|
110
158
|
|
111
159
|
Primer::BaseComponent.new(**kwargs)
|
112
160
|
},
|
@@ -115,6 +163,21 @@ module Primer
|
|
115
163
|
}
|
116
164
|
}
|
117
165
|
|
166
|
+
renders_one :segmented_control, lambda { |**system_arguments, &block|
|
167
|
+
deny_tag_argument(**system_arguments)
|
168
|
+
system_arguments[:mr] ||= 2
|
169
|
+
system_arguments = set_as_hidden_filter_target(system_arguments)
|
170
|
+
|
171
|
+
@segmented_control_block = block
|
172
|
+
@mobile_segmented_control = Primer::OpenProject::SubHeader::SegmentedControl.new(
|
173
|
+
hide_labels: true,
|
174
|
+
display: MOBILE_ACTIONS_DISPLAY,
|
175
|
+
**system_arguments
|
176
|
+
)
|
177
|
+
|
178
|
+
Primer::OpenProject::SubHeader::SegmentedControl.new(display: DESKTOP_ACTIONS_DISPLAY, **system_arguments)
|
179
|
+
}
|
180
|
+
|
118
181
|
renders_one :text, lambda { |**system_arguments|
|
119
182
|
system_arguments[:font_weight] ||= :bold
|
120
183
|
|
@@ -138,7 +201,9 @@ module Primer
|
|
138
201
|
|
139
202
|
@filter_container = Primer::BaseComponent.new(tag: :div,
|
140
203
|
classes: "SubHeader-filterContainer",
|
141
|
-
display:
|
204
|
+
display: DESKTOP_ACTIONS_DISPLAY,
|
205
|
+
|
206
|
+
mr: 2,
|
142
207
|
data: { targets: SHOWN_FILTER_TARGET_SELECTOR })
|
143
208
|
|
144
209
|
@system_arguments[:classes] = class_names(
|
@@ -146,6 +211,25 @@ module Primer
|
|
146
211
|
system_arguments[:classes]
|
147
212
|
)
|
148
213
|
end
|
214
|
+
|
215
|
+
def before_render
|
216
|
+
@system_arguments[:classes] = class_names(
|
217
|
+
@system_arguments[:classes],
|
218
|
+
"SubHeader--emptyLeftPane" => !segmented_control? && !filter_button && !filter_input
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
def set_as_hidden_filter_target(system_arguments)
|
223
|
+
system_arguments[:data] ||= {}
|
224
|
+
system_arguments[:data] = merge_data(
|
225
|
+
system_arguments, {
|
226
|
+
data: {
|
227
|
+
targets: HIDDEN_FILTER_TARGET_SELECTOR,
|
228
|
+
}
|
229
|
+
}
|
230
|
+
)
|
231
|
+
system_arguments
|
232
|
+
end
|
149
233
|
end
|
150
234
|
end
|
151
235
|
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,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 %>
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module OpenProject
|
5
|
+
class TreeView
|
6
|
+
# A `TreeView` leaf 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 LeafNode < Primer::Component
|
11
|
+
# @!parse
|
12
|
+
# # Adds a leading visual icon rendered to the left of the node's label.
|
13
|
+
# #
|
14
|
+
# # @param label [String] A label describing the visual, displayed only to screen readers.
|
15
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::OpenProject::TreeView::Icon) %>.
|
16
|
+
# def with_leading_visual_icon(label: nil, **system_arguments, &block)
|
17
|
+
# end
|
18
|
+
|
19
|
+
renders_one :leading_visual, types: {
|
20
|
+
icon: lambda { |label: nil, **system_arguments|
|
21
|
+
merge_system_arguments!(
|
22
|
+
aria: { describedby: leading_visual_label_id }
|
23
|
+
)
|
24
|
+
|
25
|
+
Visual.new(
|
26
|
+
id: leading_visual_label_id,
|
27
|
+
visual: Icon.new(**system_arguments),
|
28
|
+
label: label
|
29
|
+
)
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
# @!parse
|
34
|
+
# # Adds a leading action rendered to the left of the node's label and any leading visuals or checkboxes.
|
35
|
+
# #
|
36
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::IconButton) %>.
|
37
|
+
# def with_leading_action_button(**system_arguments, &block)
|
38
|
+
# end
|
39
|
+
|
40
|
+
renders_one :leading_action, types: {
|
41
|
+
button: lambda { |**system_arguments|
|
42
|
+
LeadingAction.new(
|
43
|
+
action: Primer::Beta::IconButton.new(
|
44
|
+
scheme: :invisible,
|
45
|
+
**system_arguments
|
46
|
+
)
|
47
|
+
)
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
# @!parse
|
52
|
+
# # Adds a trailing visual icon rendered to the right of the node's label.
|
53
|
+
# #
|
54
|
+
# # @param label [String] A label describing the visual, displayed only to screen readers.
|
55
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::OpenProject::TreeView::Icon) %>.
|
56
|
+
# def with_trailing_visual_icon(label: nil, **system_arguments, &block)
|
57
|
+
# end
|
58
|
+
|
59
|
+
renders_one :trailing_visual, types: {
|
60
|
+
icon: lambda { |label: nil, **system_arguments|
|
61
|
+
Visual.new(
|
62
|
+
id: nil,
|
63
|
+
visual: Icon.new(**system_arguments),
|
64
|
+
label: label
|
65
|
+
)
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
delegate :current?, :merge_system_arguments!, to: :@node
|
70
|
+
|
71
|
+
# @param label [String] The node's label, i.e. it's textual content.
|
72
|
+
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::OpenProject::TreeView::Node) %>.
|
73
|
+
def initialize(label:, **system_arguments)
|
74
|
+
@label = label
|
75
|
+
@system_arguments = system_arguments
|
76
|
+
@system_arguments[:data] = merge_data(
|
77
|
+
@system_arguments,
|
78
|
+
data: { "node-type": "leaf" }
|
79
|
+
)
|
80
|
+
|
81
|
+
@node = Primer::OpenProject::TreeView::Node.new(**@system_arguments)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def base_id
|
87
|
+
@base_id ||= self.class.generate_id
|
88
|
+
end
|
89
|
+
|
90
|
+
def leading_visual_label_id
|
91
|
+
@leading_visual_id ||= "#{base_id}-leading-visual-label"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
|
2
|
+
<%= render(Primer::Beta::Octicon.new(icon: :"alert-fill", color: :danger)) %>
|
3
|
+
<span><%= @text %></span>
|
4
|
+
<div></div> <!-- spacer -->
|
5
|
+
<%= render(Primer::Beta::Button.new(
|
6
|
+
size: :small,
|
7
|
+
scheme: :default,
|
8
|
+
style: "justify-self: start",
|
9
|
+
**@retry_button_arguments
|
10
|
+
)) do %>
|
11
|
+
<%= @retry_button_label %>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module OpenProject
|
5
|
+
class TreeView
|
6
|
+
# A `TreeView` loading failure message.
|
7
|
+
#
|
8
|
+
# This component is part of the <%= link_to_component(Primer::OpenProject::TreeView) %> component and should
|
9
|
+
# not be used directly.
|
10
|
+
class LoadingFailureMessage < Primer::Component
|
11
|
+
DEFAULT_TEXT = "Something went wrong"
|
12
|
+
DEFAULT_RETRY_BUTTON_LABEL = "Retry"
|
13
|
+
|
14
|
+
# @param text [String] The failure message to display.
|
15
|
+
# @param retry_button_label [String] The text shown on the retry button.
|
16
|
+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
17
|
+
def initialize(text: DEFAULT_TEXT, retry_button_label: DEFAULT_RETRY_BUTTON_LABEL, **system_arguments)
|
18
|
+
@text = text
|
19
|
+
@retry_button_label = retry_button_label
|
20
|
+
@retry_button_arguments = system_arguments.delete(:retry_button_arguments)
|
21
|
+
@system_arguments = deny_tag_argument(**system_arguments)
|
22
|
+
@system_arguments[:tag] = :div
|
23
|
+
@system_arguments[:classes] = class_names(
|
24
|
+
@system_arguments.delete(:classes),
|
25
|
+
"TreeViewFailureMessage"
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
|
2
|
+
<div class="TreeViewItemContainer" style="--level: <%= level %>;">
|
3
|
+
<div style="grid-area: spacer; display: flex;">
|
4
|
+
<div style="width: 100%; display: flex;">
|
5
|
+
<% (0...(level - 1)).each do %>
|
6
|
+
<div class="TreeViewItemLevelLine"></div>
|
7
|
+
<% end %>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
<% if toggle? %>
|
11
|
+
<%= toggle %>
|
12
|
+
<% end %>
|
13
|
+
<% if leading_action? %>
|
14
|
+
<%= leading_action %>
|
15
|
+
<% end %>
|
16
|
+
<div id="<%= content_id %>" class="TreeViewItemContent">
|
17
|
+
<% if @select_variant == :multiple %>
|
18
|
+
<div aria-hidden="true" class="TreeViewItemCheckbox" tabindex="-1">
|
19
|
+
<div class="FormControl-checkbox"></div>
|
20
|
+
</div>
|
21
|
+
<% end %>
|
22
|
+
<% if leading_visual? %>
|
23
|
+
<%= leading_visual %>
|
24
|
+
<% end %>
|
25
|
+
<span class="TreeViewItemContentText"><%= text_content %></span>
|
26
|
+
<% if trailing_visual? %>
|
27
|
+
<%= trailing_visual %>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
<%= content %>
|
32
|
+
<% end %>
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module OpenProject
|
5
|
+
class TreeView
|
6
|
+
# A generic `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 Node < Primer::Component
|
11
|
+
# Generic leading action slot
|
12
|
+
renders_one :leading_action
|
13
|
+
|
14
|
+
# Generic leading visual slot
|
15
|
+
renders_one :leading_visual
|
16
|
+
|
17
|
+
# Generic trailing visual slot
|
18
|
+
renders_one :trailing_visual
|
19
|
+
|
20
|
+
# Generic toggle button slot
|
21
|
+
renders_one :toggle
|
22
|
+
|
23
|
+
# Generic text content slot (for node's label)
|
24
|
+
renders_one :text_content
|
25
|
+
|
26
|
+
# Wether or not this node is the current node.
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
attr_reader :current
|
30
|
+
alias current? current
|
31
|
+
|
32
|
+
# This node's checked state.
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
attr_reader :checked
|
36
|
+
|
37
|
+
# This node's select variant (i.e. check box variant).
|
38
|
+
#
|
39
|
+
# @return [Symbol]
|
40
|
+
attr_reader :select_variant
|
41
|
+
|
42
|
+
DEFAULT_SELECT_VARIANT = :none
|
43
|
+
SELECT_VARIANT_OPTIONS = [
|
44
|
+
:multiple,
|
45
|
+
DEFAULT_SELECT_VARIANT
|
46
|
+
].freeze
|
47
|
+
|
48
|
+
DEFAULT_CHECKED_STATE = false
|
49
|
+
CHECKED_STATES = [
|
50
|
+
DEFAULT_CHECKED_STATE,
|
51
|
+
true,
|
52
|
+
"mixed"
|
53
|
+
]
|
54
|
+
|
55
|
+
# @param path [Array<String>] The node's "path," i.e. this node's label and the labels of all its ancestors. This node should be reachable by traversing the tree following this path.
|
56
|
+
# @param current [Boolean] Whether or not this node is the current node. The current node is styled differently than regular nodes and is the first element that receives focus when tabbing to the `TreeView` component.
|
57
|
+
# @param select_variant [Symbol] Controls the type of checkbox that appears. <%= one_of(Primer::OpenProject::TreeView::Node::SELECT_VARIANT_OPTIONS) %>
|
58
|
+
# @param checked [Boolean | String] The checked state of the node's checkbox. <%= one_of(Primer::OpenProject::TreeView::Node::CHECKED_STATES) %>
|
59
|
+
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>.
|
60
|
+
def initialize(
|
61
|
+
path:,
|
62
|
+
current: false,
|
63
|
+
select_variant: DEFAULT_SELECT_VARIANT,
|
64
|
+
checked: DEFAULT_CHECKED_STATE,
|
65
|
+
**system_arguments
|
66
|
+
)
|
67
|
+
@system_arguments = deny_tag_argument(**system_arguments)
|
68
|
+
|
69
|
+
@path = path
|
70
|
+
@current = current
|
71
|
+
@select_variant = fetch_or_fallback(SELECT_VARIANT_OPTIONS, select_variant, DEFAULT_SELECT_VARIANT)
|
72
|
+
@checked = fetch_or_fallback(CHECKED_STATES, checked, DEFAULT_CHECKED_STATE)
|
73
|
+
|
74
|
+
@system_arguments[:tag] = :li
|
75
|
+
@system_arguments[:role] = :treeitem
|
76
|
+
@system_arguments[:tabindex] = current? ? 0 : -1
|
77
|
+
@system_arguments[:classes] = class_names(
|
78
|
+
@system_arguments.delete(:classes),
|
79
|
+
"TreeViewItem"
|
80
|
+
)
|
81
|
+
|
82
|
+
@system_arguments[:aria] = merge_aria(
|
83
|
+
@system_arguments, {
|
84
|
+
aria: {
|
85
|
+
level: level,
|
86
|
+
selected: false,
|
87
|
+
checked: checked,
|
88
|
+
labelledby: content_id
|
89
|
+
}
|
90
|
+
}
|
91
|
+
)
|
92
|
+
|
93
|
+
@system_arguments[:data] = merge_data(
|
94
|
+
@system_arguments,
|
95
|
+
{ data: { path: @path.to_json } }
|
96
|
+
)
|
97
|
+
|
98
|
+
return unless current?
|
99
|
+
|
100
|
+
@system_arguments[:aria] = merge_aria(
|
101
|
+
@system_arguments,
|
102
|
+
{ aria: { current: true } }
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
# The numeric depth of this node.
|
107
|
+
#
|
108
|
+
# @return [Integer] This node's depth.
|
109
|
+
def level
|
110
|
+
@level ||= @path.size
|
111
|
+
end
|
112
|
+
|
113
|
+
# Merges the given arguments into the current hash of system arguments provided when the component was
|
114
|
+
# initially constructed. This method can be used to add additional arguments just before rendering.
|
115
|
+
#
|
116
|
+
# @param other_arguments [Hash] The other hash of system arguments to merge into the current one.
|
117
|
+
def merge_system_arguments!(**other_arguments)
|
118
|
+
@system_arguments[:aria] = merge_aria(
|
119
|
+
@system_arguments,
|
120
|
+
other_arguments
|
121
|
+
)
|
122
|
+
|
123
|
+
@system_arguments[:data] = merge_data(
|
124
|
+
@system_arguments,
|
125
|
+
other_arguments
|
126
|
+
)
|
127
|
+
|
128
|
+
@system_arguments.merge!(**other_arguments)
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def before_render
|
134
|
+
if leading_visual?
|
135
|
+
end
|
136
|
+
|
137
|
+
if leading_action?
|
138
|
+
@system_arguments[:data] = merge_data(
|
139
|
+
@system_arguments,
|
140
|
+
{ data: { "has-leading-action": true } }
|
141
|
+
)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def content_id
|
146
|
+
@content_id ||= "#{base_id}-content"
|
147
|
+
end
|
148
|
+
|
149
|
+
def base_id
|
150
|
+
@base_id ||= self.class.generate_id
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<% unless loading_failure_message? %>
|
2
|
+
<% with_loading_failure_message # set the default %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<%= render(Primer::BaseComponent.new(tag: :"tree-view-include-fragment", src: @src, loading: :lazy, data: { target: "tree-view-sub-tree-node.subTree tree-view-sub-tree-node.includeFragment", path: @container.path.to_json }, hidden: @container.expanded?, accept: "text/fragment+html")) do %>
|
6
|
+
<%= render(@container) do %>
|
7
|
+
<%= render(Primer::OpenProject::TreeView::Node.new(path: [*@container.path, :loader])) do |node| %>
|
8
|
+
<% node.with_text_content do %>
|
9
|
+
<div data-target="tree-view-sub-tree-node.loadingIndicator">
|
10
|
+
<% @count.times do %>
|
11
|
+
<span class="TreeViewSkeletonItemContainerStyle TreeViewItemSkeleton">
|
12
|
+
<%= render(Primer::OpenProject::SkeletonBox.new(width: "16px")) %>
|
13
|
+
<%= render(Primer::OpenProject::SkeletonBox.new(width: "100%", classes: "TreeItemSkeletonTextStyles")) %>
|
14
|
+
</span>
|
15
|
+
<% end %>
|
16
|
+
</div>
|
17
|
+
<div data-target="tree-view-sub-tree-node.loadingFailureMessage" hidden>
|
18
|
+
<%= loading_failure_message %>
|
19
|
+
</div>
|
20
|
+
<% end %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
<% end %>
|