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
@@ -0,0 +1,402 @@
1
+ import {controller, target} from '@github/catalyst'
2
+ import {TreeViewIconPairElement} from './tree_view_icon_pair_element'
3
+ import {observeMutationsUntilConditionMet} from '../../utils'
4
+ import {TreeViewIncludeFragmentElement} from './tree_view_include_fragment_element'
5
+ import {TreeViewElement} from './tree_view'
6
+ import type {TreeViewNodeInfo} from '../../shared_events'
7
+
8
+ type LoadingState = 'loading' | 'error' | 'success'
9
+
10
+ @controller
11
+ export class TreeViewSubTreeNodeElement extends HTMLElement {
12
+ @target node: HTMLElement
13
+ @target subTree: HTMLElement
14
+ @target iconPair: TreeViewIconPairElement
15
+ @target toggleButton: HTMLElement
16
+ @target expandedToggleIcon: HTMLElement
17
+ @target collapsedToggleIcon: HTMLElement
18
+ @target includeFragment: TreeViewIncludeFragmentElement
19
+ @target loadingIndicator: HTMLElement
20
+ @target loadingFailureMessage: HTMLElement
21
+ @target retryButton: HTMLButtonElement
22
+
23
+ expanded: boolean
24
+ loadingState: LoadingState
25
+
26
+ #abortController: AbortController
27
+ #activeElementIsLoader: boolean = false
28
+
29
+ connectedCallback() {
30
+ this.expanded = this.node.getAttribute('aria-expanded') === 'true'
31
+ this.loadingState = 'success'
32
+
33
+ observeMutationsUntilConditionMet(
34
+ this,
35
+ () => Boolean(this.node) && Boolean(this.subTree),
36
+ () => {
37
+ this.#update()
38
+ },
39
+ )
40
+
41
+ const {signal} = (this.#abortController = new AbortController())
42
+ this.addEventListener('click', this, {signal})
43
+ this.addEventListener('keydown', this, {signal})
44
+
45
+ observeMutationsUntilConditionMet(
46
+ this,
47
+ () => Boolean(this.includeFragment),
48
+ () => {
49
+ this.includeFragment.addEventListener('loadstart', this, {signal})
50
+ this.includeFragment.addEventListener('error', this, {signal})
51
+ this.includeFragment.addEventListener('include-fragment-replace', this, {signal})
52
+ this.includeFragment.addEventListener(
53
+ 'include-fragment-replaced',
54
+ (e: Event) => {
55
+ this.#handleIncludeFragmentEvent(e)
56
+ },
57
+ {signal},
58
+ )
59
+ },
60
+ )
61
+
62
+ observeMutationsUntilConditionMet(
63
+ this,
64
+ () => Boolean(this.retryButton),
65
+ () => {
66
+ this.retryButton.addEventListener(
67
+ 'click',
68
+ event => {
69
+ this.#handleRetryButtonEvent(event)
70
+ },
71
+ {signal},
72
+ )
73
+ },
74
+ )
75
+
76
+ const checkedMutationObserver = new MutationObserver(() => {
77
+ if (this.selectStrategy !== 'descendants') return
78
+
79
+ let checkType = 'unknown'
80
+
81
+ for (const node of this.eachDirectDescendantNode()) {
82
+ switch (`${checkType} ${node.getAttribute('aria-checked') || 'false'}`) {
83
+ case 'unknown mixed':
84
+ case 'false mixed':
85
+ case 'true mixed':
86
+ case 'false true':
87
+ case 'true false':
88
+ checkType = 'mixed'
89
+ break
90
+
91
+ case 'unknown false':
92
+ checkType = 'false'
93
+ break
94
+
95
+ case 'unknown true':
96
+ checkType = 'true'
97
+ }
98
+ }
99
+
100
+ if (checkType !== 'unknown' && this.node?.getAttribute('aria-checked') !== checkType) {
101
+ this.node?.setAttribute('aria-checked', checkType)
102
+ }
103
+ })
104
+
105
+ checkedMutationObserver.observe(this, {
106
+ childList: true,
107
+ subtree: true,
108
+ attributeFilter: ['aria-checked'],
109
+ })
110
+ }
111
+
112
+ get selectStrategy(): string {
113
+ return this.node.getAttribute('data-select-strategy') || 'descendants'
114
+ }
115
+
116
+ disconnectedCallback() {
117
+ this.#abortController.abort()
118
+ }
119
+
120
+ handleEvent(event: Event) {
121
+ const checkbox = (event.target as Element).closest('.TreeViewItemCheckbox')
122
+
123
+ if (checkbox && checkbox === this.#checkboxElement) {
124
+ this.#handleCheckboxEvent(event)
125
+ } else if (event.target === this.toggleButton) {
126
+ this.#handleToggleEvent(event)
127
+ } else if (event.target === this.includeFragment) {
128
+ this.#handleIncludeFragmentEvent(event)
129
+ } else if (event instanceof KeyboardEvent) {
130
+ this.#handleKeyboardEvent(event)
131
+ }
132
+ }
133
+
134
+ expand() {
135
+ const alreadyExpanded = this.expanded
136
+
137
+ this.expanded = true
138
+ this.#update()
139
+
140
+ if (!alreadyExpanded && this.treeView) {
141
+ this.treeView.dispatchEvent(
142
+ new CustomEvent('treeViewNodeExpanded', {
143
+ bubbles: true,
144
+ detail: this.treeView?.infoFromNode(this.node),
145
+ }),
146
+ )
147
+ }
148
+ }
149
+
150
+ collapse() {
151
+ const alreadyCollapsed = !this.expanded
152
+
153
+ this.expanded = false
154
+ this.#update()
155
+
156
+ if (!alreadyCollapsed && this.treeView) {
157
+ // Prevent issue where currently focusable node is stuck inside a collapsed
158
+ // sub-tree and no node in the entire tree can be focused
159
+ const previousNode = this.subTree.querySelector("[tabindex='0']")
160
+ previousNode?.setAttribute('tabindex', '-1')
161
+ this.node.setAttribute('tabindex', '0')
162
+
163
+ this.treeView.dispatchEvent(
164
+ new CustomEvent('treeViewNodeCollapsed', {
165
+ bubbles: true,
166
+ detail: this.treeView?.infoFromNode(this.node),
167
+ }),
168
+ )
169
+ }
170
+ }
171
+
172
+ toggle() {
173
+ if (this.expanded) {
174
+ this.collapse()
175
+ } else {
176
+ this.expand()
177
+ }
178
+ }
179
+
180
+ get nodes(): NodeListOf<Element> {
181
+ return this.querySelectorAll(':scope > [role=treeitem]')
182
+ }
183
+
184
+ *eachDirectDescendantNode(): Generator<Element> {
185
+ for (const leaf of this.subTree.querySelectorAll(':scope > [role=treeitem]')) {
186
+ yield leaf
187
+ }
188
+
189
+ for (const subTree of this.subTree.querySelectorAll(':scope > tree-view-sub-tree-node > [role=treeitem]')) {
190
+ yield subTree
191
+ }
192
+ }
193
+
194
+ *eachDescendantNode(): Generator<Element> {
195
+ for (const node of this.subTree.querySelectorAll('[role=treeitem]')) {
196
+ yield node
197
+ }
198
+ }
199
+
200
+ get isEmpty(): boolean {
201
+ return this.nodes.length === 0
202
+ }
203
+
204
+ get treeView(): TreeViewElement | null {
205
+ return this.closest('tree-view')
206
+ }
207
+
208
+ #handleToggleEvent(event: Event) {
209
+ if (event.type === 'click') {
210
+ this.toggle()
211
+ }
212
+ }
213
+
214
+ #handleIncludeFragmentEvent(event: Event) {
215
+ switch (event.type) {
216
+ // the request has started
217
+ case 'loadstart':
218
+ this.loadingState = 'loading'
219
+ this.#update()
220
+ break
221
+
222
+ // the request failed
223
+ case 'error':
224
+ this.loadingState = 'error'
225
+ this.#update()
226
+ break
227
+
228
+ // request succeeded but element has not yet been replaced
229
+ case 'include-fragment-replace':
230
+ this.loadingState = 'success'
231
+ this.#activeElementIsLoader = document.activeElement === this.loadingIndicator.closest('li')
232
+ this.#update()
233
+ break
234
+
235
+ case 'include-fragment-replaced':
236
+ if (this.#activeElementIsLoader) {
237
+ const firstItem = this.querySelector('[role=treeitem] [role=group] > :first-child') as HTMLElement | null
238
+ if (!firstItem) return
239
+
240
+ if (firstItem.tagName.toLowerCase() === 'tree-view-sub-tree-node') {
241
+ const firstChild = firstItem.querySelector('[role=treeitem]') as HTMLElement | null
242
+ firstChild?.focus()
243
+ } else {
244
+ firstItem?.focus()
245
+ }
246
+ }
247
+
248
+ this.#activeElementIsLoader = false
249
+ break
250
+ }
251
+ }
252
+
253
+ #handleRetryButtonEvent(event: Event) {
254
+ if (event.type === 'click') {
255
+ this.loadingState = 'loading'
256
+ this.#update()
257
+
258
+ this.includeFragment.refetch()
259
+ }
260
+ }
261
+
262
+ #handleKeyboardEvent(event: KeyboardEvent) {
263
+ const node = (event.target as HTMLElement).closest('[role=treeitem]')
264
+ if (!node || this.treeView?.getNodeType(node) !== 'sub-tree') {
265
+ return
266
+ }
267
+
268
+ switch (event.key) {
269
+ case 'Enter':
270
+ // eslint-disable-next-line no-restricted-syntax
271
+ event.stopPropagation()
272
+ this.toggle()
273
+ break
274
+
275
+ case 'ArrowRight':
276
+ // eslint-disable-next-line no-restricted-syntax
277
+ event.stopPropagation()
278
+ this.expand()
279
+ break
280
+
281
+ case 'ArrowLeft':
282
+ // eslint-disable-next-line no-restricted-syntax
283
+ event.stopPropagation()
284
+ this.collapse()
285
+ break
286
+
287
+ case ' ':
288
+ // eslint-disable-next-line no-restricted-syntax
289
+ event.stopPropagation()
290
+ event.preventDefault()
291
+ this.toggleChecked()
292
+ break
293
+ }
294
+ }
295
+
296
+ #handleCheckboxEvent(event: Event) {
297
+ if (event.type !== 'click') return
298
+
299
+ this.toggleChecked()
300
+
301
+ // prevent receiving this event twice
302
+ // eslint-disable-next-line no-restricted-syntax
303
+ event.stopPropagation()
304
+ }
305
+
306
+ toggleChecked() {
307
+ const checkValue = this.node.getAttribute('aria-checked') || 'false'
308
+ const newCheckValue = checkValue === 'false' ? 'true' : 'false'
309
+ const nodeInfos: TreeViewNodeInfo[] = []
310
+ const rootInfo = this.treeView?.infoFromNode(this.node, newCheckValue)
311
+ if (rootInfo) nodeInfos.push(rootInfo)
312
+
313
+ if (this.selectStrategy === 'descendants') {
314
+ for (const node of this.eachDescendantNode()) {
315
+ const info = this.treeView?.infoFromNode(node, newCheckValue)
316
+ if (info) nodeInfos.push(info)
317
+ }
318
+ }
319
+
320
+ const checkSuccess = this.dispatchEvent(
321
+ new CustomEvent('treeViewBeforeNodeChecked', {
322
+ bubbles: true,
323
+ cancelable: true,
324
+ detail: nodeInfos,
325
+ }),
326
+ )
327
+
328
+ if (!checkSuccess) return
329
+
330
+ for (const nodeInfo of nodeInfos) {
331
+ nodeInfo.node.setAttribute('aria-checked', newCheckValue)
332
+ }
333
+
334
+ this.dispatchEvent(
335
+ new CustomEvent('treeViewNodeChecked', {
336
+ bubbles: true,
337
+ cancelable: true,
338
+ detail: nodeInfos,
339
+ }),
340
+ )
341
+ }
342
+
343
+ #update() {
344
+ if (this.expanded) {
345
+ if (this.subTree) this.subTree.hidden = false
346
+ this.node.setAttribute('aria-expanded', 'true')
347
+
348
+ if (this.iconPair) {
349
+ this.iconPair.showExpanded()
350
+ }
351
+
352
+ if (this.expandedToggleIcon && this.collapsedToggleIcon) {
353
+ this.expandedToggleIcon.removeAttribute('hidden')
354
+ this.collapsedToggleIcon.setAttribute('hidden', 'hidden')
355
+ }
356
+ } else {
357
+ if (this.subTree) this.subTree.hidden = true
358
+ this.node.setAttribute('aria-expanded', 'false')
359
+
360
+ if (this.iconPair) {
361
+ this.iconPair.showCollapsed()
362
+ }
363
+
364
+ if (this.expandedToggleIcon && this.collapsedToggleIcon) {
365
+ this.expandedToggleIcon.setAttribute('hidden', 'hidden')
366
+ this.collapsedToggleIcon.removeAttribute('hidden')
367
+ }
368
+ }
369
+
370
+ switch (this.loadingState) {
371
+ case 'loading':
372
+ if (this.loadingFailureMessage) this.loadingFailureMessage.hidden = true
373
+ if (this.loadingIndicator) this.loadingIndicator.hidden = false
374
+ break
375
+
376
+ case 'error':
377
+ if (this.loadingIndicator) this.loadingIndicator.hidden = true
378
+ if (this.loadingFailureMessage) this.loadingFailureMessage.hidden = false
379
+ break
380
+
381
+ // success/init case
382
+ default:
383
+ if (this.loadingIndicator) this.loadingIndicator.hidden = true
384
+ if (this.loadingFailureMessage) this.loadingFailureMessage.hidden = true
385
+ }
386
+ }
387
+
388
+ get #checkboxElement(): HTMLElement | null {
389
+ return this.querySelector('.TreeViewItemCheckbox')
390
+ }
391
+ }
392
+
393
+ if (!window.customElements.get('tree-view-sub-tree-node')) {
394
+ window.TreeViewSubTreeNodeElement = TreeViewSubTreeNodeElement
395
+ window.customElements.define('tree-view-sub-tree-node', TreeViewSubTreeNodeElement)
396
+ }
397
+
398
+ declare global {
399
+ interface Window {
400
+ TreeViewSubTreeNodeElement: typeof TreeViewSubTreeNodeElement
401
+ }
402
+ }
@@ -0,0 +1,14 @@
1
+ <%= render(
2
+ Primer::BaseComponent.new(
3
+ tag: :div,
4
+ classes: "sr-only TreeViewVisuallyHidden",
5
+ aria: { hidden: true },
6
+ id: @id,
7
+ test_selector: "tree-view-visual-label"
8
+ )
9
+ ) do %>
10
+ <%= @label %>
11
+ <% end %>
12
+ <div class="TreeViewItemVisual" aria-hidden="true">
13
+ <%= render(@visual) %>
14
+ </div>
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ class TreeView
6
+ # A `TreeView` visual, either leading or trailing.
7
+ #
8
+ # This component is part of the <%= link_to_component(Primer::OpenProject::TreeView) %> component and should
9
+ # not be used directly.
10
+ class Visual < Primer::Component
11
+ # @param id [String] This visual's HTML ID.
12
+ # @param visual [ViewComponent::Base] A renderable component like an instance of <%= link_to_component(Primer::Beta::Octicon) %> to render as the visual.
13
+ # @param label [String] Text describing this visual that will be visible only to screen readers.
14
+ def initialize(id:, visual:, label: nil)
15
+ @id = id
16
+ @visual = visual
17
+ @label = label
18
+ end
19
+
20
+ def render_in(_view_context, &block)
21
+ block&.call(@visual)
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1 @@
1
+ .TreeViewRootUlStyles{list-style:none;margin:0;padding:0}.TreeViewRootUlStyles .TreeViewItem{outline:none}:is(.TreeViewRootUlStyles .TreeViewItem):focus-visible>div{box-shadow:var(--boxShadow-thick) var(--fgColor-accent)}@media (forced-colors:active){:is(.TreeViewRootUlStyles .TreeViewItem):focus-visible>div{outline:2px solid HighlightText;outline-offset:-2}}[data-has-leading-action]:is(.TreeViewRootUlStyles .TreeViewItem){--has-leading-action:1}.TreeViewRootUlStyles .TreeViewItemContainer{--level:1;--toggle-width:1rem;--min-item-height:2rem;border-radius:var(--borderRadius-medium);color:var(--fgColor-default);cursor:pointer;display:grid;font-size:var(--text-body-size-medium);grid-template-areas:"spacer leadingAction toggle content";grid-template-columns:var(--spacer-width) var(--leading-action-width) var(--toggle-width) 1fr;position:relative;width:100%;--leading-action-width:calc(var(--has-leading-action, 0)*1.5rem);--spacer-width:calc((var(--level) - 1)*(var(--toggle-width)/2))}:is(.TreeViewRootUlStyles .TreeViewItemContainer):hover{background-color:var(--control-transparent-bgColor-hover)}@media (forced-colors:active){:is(.TreeViewRootUlStyles .TreeViewItemContainer):hover{outline:2px solid #0000;outline-offset:-2px}}@media (pointer:coarse){.TreeViewRootUlStyles .TreeViewItemContainer{--toggle-width:1.5rem;--min-item-height:2.75rem}}:is(.TreeViewRootUlStyles .TreeViewItemContainer):has(.TreeViewFailureMessage):hover{background-color:initial;cursor:default}@media (forced-colors:active){:is(.TreeViewRootUlStyles .TreeViewItemContainer):has(.TreeViewFailureMessage):hover{outline:none}}.TreeViewRootUlStyles:where([data-omit-spacer=true]) .TreeViewItemContainer{grid-template-columns:0 0 0 1fr}.TreeViewRootUlStyles .TreeViewItem[aria-current=true]>.TreeViewItemContainer{background-color:var(--control-transparent-bgColor-selected)}:is(.TreeViewRootUlStyles .TreeViewItem[aria-current=true]>.TreeViewItemContainer):after{background-color:var(--fgColor-accent);border-radius:var(--borderRadius-medium);content:"";height:1.5rem;left:calc(var(--base-size-8)*-1);position:absolute;top:calc(50% - var(--base-size-12));width:.25rem}@media (forced-colors:active){:is(.TreeViewRootUlStyles .TreeViewItem[aria-current=true]>.TreeViewItemContainer):after{background-color:HighlightText}}[aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItem)>.TreeViewItemContainer .FormControl-checkbox{background:var(--control-checked-bgColor-rest);border-color:var(--control-checked-borderColor-rest);transition:background-color,border-color 80ms cubic-bezier(.32,0,.67,0) 0s}:is([aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItem)>.TreeViewItemContainer .FormControl-checkbox):before{animation:checkmarkIn 80ms cubic-bezier(.65,0,.35,1) 80ms forwards;transition:visibility 0s linear 0s;visibility:visible}[aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItem)>.TreeViewItemContainer .FormControl-checkbox{background:var(--control-checked-bgColor-rest);border-color:var(--control-checked-borderColor-rest);transition:background-color,border-color 80ms cubic-bezier(.32,0,.67,0) 0s}:is([aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItem)>.TreeViewItemContainer .FormControl-checkbox):before{animation:checkmarkIn 80ms cubic-bezier(.65,0,.35,1) 80ms forwards;clip-path:none;mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIyIiBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTAgMiI+PHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCAxYTEgMSAwIDAgMSAxLTFoOGExIDEgMCAxIDEgMCAySDFhMSAxIDAgMCAxLTEtMSIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+");visibility:visible}.TreeViewRootUlStyles .TreeViewItemToggle{align-items:flex-start;color:var(--fgColor-muted);display:flex;grid-area:toggle;height:100%;justify-content:center;padding-top:calc(var(--min-item-height)/2 - var(--base-size-12)/2)}.TreeViewRootUlStyles .TreeViewItemToggleHover:hover{background-color:var(--control-transparent-bgColor-hover)}.TreeViewRootUlStyles .TreeViewItemToggleEnd{border-bottom-left-radius:var(--borderRadius-medium);border-top-left-radius:var(--borderRadius-medium)}.TreeViewRootUlStyles .TreeViewItemContent{display:flex;gap:var(--stack-gap-condensed);grid-area:content;height:100%;line-height:var(--custom-line-height,var(--text-body-lineHeight-medium,1.4285));padding:0 var(--base-size-8);padding-bottom:calc((var(--min-item-height) - var(--custom-line-height, 1.3rem))/2);padding-top:calc((var(--min-item-height) - var(--custom-line-height, 1.3rem))/2)}:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemCheckbox{background-color:initial;border:none;border-radius:var(--borderRadius-medium);color:var(--control-fgColor-rest);position:relative;text-align:left;touch-action:manipulation;transition:background 33.333ms linear;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.TreeViewRootUlStyles .TreeViewItemContentText{flex:1 1 auto;width:0}.TreeViewRootUlStyles:where([data-truncate-text=true]) .TreeViewItemContentText{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.TreeViewRootUlStyles:where([data-truncate-text=false]) .TreeViewItemContentText{word-break:break-word}.TreeViewRootUlStyles .TreeViewItemVisual{align-items:center;color:var(--fgColor-muted);display:flex;height:var(--custom-line-height,1.3rem)}.TreeViewRootUlStyles .TreeViewItemLeadingAction{color:var(--fgColor-muted);display:flex;grid-area:leadingAction}:is(.TreeViewRootUlStyles .TreeViewItemLeadingAction)>button{flex-shrink:1}.TreeViewRootUlStyles .TreeViewItemLevelLine{border-color:var(--borderColor-muted);border-right:var(--borderWidth-thin) solid;height:100%;width:100%}@media (hover:hover){.TreeViewRootUlStyles .TreeViewItemLevelLine{border-color:#0000}.TreeViewRootUlStyles:focus-within .TreeViewItemLevelLine,.TreeViewRootUlStyles:hover .TreeViewItemLevelLine{border-color:var(--borderColor-muted)}}.TreeViewRootUlStyles .TreeViewVisuallyHidden{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.TreeViewSkeletonItemContainerStyle{align-items:center;column-gap:.5rem;display:flex;height:2rem}@media (pointer:coarse){.TreeViewSkeletonItemContainerStyle{height:2.75rem}}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+1){--tree-item-loading-width:67%}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+2){--tree-item-loading-width:47%}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+3){--tree-item-loading-width:73%}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+4){--tree-item-loading-width:64%}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+5){--tree-item-loading-width:50%}.TreeItemSkeletonTextStyles{width:var(--tree-item-loading-width,67%)}.TreeViewFailureMessage{align-items:center;display:grid;gap:.5rem;grid-template-columns:auto 1fr;width:100%}
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "open_project/tree_view",
3
+ "selectors": [
4
+ ".TreeViewRootUlStyles",
5
+ ".TreeViewRootUlStyles .TreeViewItem",
6
+ ":is(.TreeViewRootUlStyles .TreeViewItem):focus-visible>div",
7
+ "[data-has-leading-action]:is(.TreeViewRootUlStyles .TreeViewItem)",
8
+ ".TreeViewRootUlStyles .TreeViewItemContainer",
9
+ ":is(.TreeViewRootUlStyles .TreeViewItemContainer):hover",
10
+ ":is(.TreeViewRootUlStyles .TreeViewItemContainer):has(.TreeViewFailureMessage):hover",
11
+ ".TreeViewRootUlStyles:where([data-omit-spacer=true]) .TreeViewItemContainer",
12
+ ".TreeViewRootUlStyles .TreeViewItem[aria-current=true]>.TreeViewItemContainer",
13
+ ":is(.TreeViewRootUlStyles .TreeViewItem[aria-current=true]>.TreeViewItemContainer):after",
14
+ "[aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItem)>.TreeViewItemContainer .FormControl-checkbox",
15
+ ":is([aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItem)>.TreeViewItemContainer .FormControl-checkbox):before",
16
+ "[aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItem)>.TreeViewItemContainer .FormControl-checkbox",
17
+ ":is([aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItem)>.TreeViewItemContainer .FormControl-checkbox):before",
18
+ ".TreeViewRootUlStyles .TreeViewItemToggle",
19
+ ".TreeViewRootUlStyles .TreeViewItemToggleHover:hover",
20
+ ".TreeViewRootUlStyles .TreeViewItemToggleEnd",
21
+ ".TreeViewRootUlStyles .TreeViewItemContent",
22
+ ":is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemCheckbox",
23
+ ".TreeViewRootUlStyles .TreeViewItemContentText",
24
+ ".TreeViewRootUlStyles:where([data-truncate-text=true]) .TreeViewItemContentText",
25
+ ".TreeViewRootUlStyles:where([data-truncate-text=false]) .TreeViewItemContentText",
26
+ ".TreeViewRootUlStyles .TreeViewItemVisual",
27
+ ".TreeViewRootUlStyles .TreeViewItemLeadingAction",
28
+ ":is(.TreeViewRootUlStyles .TreeViewItemLeadingAction)>button",
29
+ ".TreeViewRootUlStyles .TreeViewItemLevelLine",
30
+ ".TreeViewRootUlStyles:focus-within .TreeViewItemLevelLine",
31
+ ".TreeViewRootUlStyles:hover .TreeViewItemLevelLine",
32
+ ".TreeViewRootUlStyles .TreeViewVisuallyHidden",
33
+ ".TreeViewSkeletonItemContainerStyle",
34
+ ".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+1)",
35
+ ".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+2)",
36
+ ".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+3)",
37
+ ".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+4)",
38
+ ".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+5)",
39
+ ".TreeItemSkeletonTextStyles",
40
+ ".TreeViewFailureMessage"
41
+ ]
42
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["tree_view.pcss"],"names":[],"mappings":"AAEA,sBAGE,eAAgB,CADhB,QAAS,CADT,SAgRF,CA/PE,oCACE,YAeF,CAbE,2DACE,uDAOF,CALE,8BAHF,2DAII,+BAAgC,CAEhC,iBAEJ,CADE,CAGF,kEACE,sBACF,CAGF,6CACE,SAAU,CACV,mBAAoB,CACpB,sBAAuB,CAQvB,wCAAyC,CAFzC,4BAA6B,CAC7B,cAAe,CAJf,YAAa,CAEb,sCAAuC,CAKvC,yDAA0D,CAD1D,6FAA8F,CAP9F,iBAAkB,CAElB,UAAW,CAQX,gEAAmE,CACnE,+DAwBF,CAtBE,wDACE,yDAMF,CAJE,8BAHF,wDAII,uBAA8B,CAC9B,mBAEJ,CADE,CAGF,wBA3BF,6CA4BI,qBAAsB,CACtB,yBAWJ,CAVE,CAEA,qFAEE,wBAA6B,CAD7B,cAMF,CAHE,8BAJF,qFAKI,YAEJ,CADE,CAIJ,4EACE,+BACF,CAEA,8EACE,4DAwBF,CApBE,yFAaE,sCAAuC,CACvC,wCAAyC,CARzC,UAAW,CADX,aAAc,CAFd,gCAAmC,CAFnC,iBAAkB,CAClB,mCAAoC,CAEpC,YAeF,CAHE,8BAhBF,yFAiBI,8BAEJ,CADE,CAQA,yGACE,8CAA+C,CAC/C,oDAAqD,CACrD,0EAQF,CALE,qHAGE,kEAAwE,CADxE,kCAAmC,CADnC,kBAGF,CAMF,0GACE,8CAA+C,CAC/C,oDAAqD,CACrD,0EASF,CANE,sHAGE,kEAAwE,CACxE,cAAe,CAFf,wUAAia,CADja,kBAIF,CAKN,0CAWE,sBAAuB,CAHvB,0BAA2B,CAP3B,YAAa,CAQb,gBAAiB,CAPjB,WAAY,CAQZ,sBAAuB,CAHvB,kEAKF,CAEA,qDACE,yDACF,CAEA,6CAEE,oDAAqD,CADrD,iDAEF,CAEA,2CACE,YAAa,CAWb,8BAA+B,CAD/B,iBAAkB,CATlB,WAAY,CAQZ,+EAAkF,CAPlF,4BAA6B,CAM7B,mFAAsF,CAFtF,gFAmBF,CAZE,sEAKE,wBAA6B,CAC7B,WAAY,CACZ,wCAAyC,CALzC,iCAAkC,CADlC,iBAAkB,CAElB,eAAgB,CAMhB,yBAA0B,CAD1B,qCAAsC,CAJtC,wBAAiB,CAAjB,gBAAiB,CAMjB,uCACF,CAGF,+CACE,aAAc,CACd,OACF,CAEA,gFACE,eAAgB,CAChB,sBAAuB,CACvB,kBACF,CAEA,iFAEE,qBACF,CAEA,0CAOE,kBAAmB,CADnB,0BAA2B,CAL3B,YAAa,CAIb,uCAGF,CAEA,iDAEE,0BAA2B,CAD3B,YAAa,CAEb,uBAKF,CAHE,6DACE,aACF,CAGF,6CAQE,qCAAsC,CACtC,0CAA2C,CAP3C,WAAY,CADZ,UASF,CAQA,qBACE,6CACE,kBACF,CAEA,6GAEE,qCACF,CACF,CAEA,8CAGE,UAAW,CAGX,WAAY,CACZ,eAAgB,CAHhB,SAAU,CAHV,iBAAkB,CAClB,SAAU,CAMV,kBAAsB,CAEtB,cAAe,CADf,kBAEF,CAGF,oCAEE,kBAAmB,CACnB,gBAAkB,CAFlB,YAAa,CAGb,WAyBF,CAvBE,wBANF,oCAOI,cAsBJ,CArBE,CAEA,sDACE,6BACF,CAEA,sDACE,6BACF,CAEA,sDACE,6BACF,CAEA,sDACE,6BACF,CAEA,sDACE,6BACF,CAGF,4BACE,wCACF,CAEA,wBAKE,kBAAmB,CAJnB,YAAa,CAEb,SAAW,CADX,8BAA+B,CAE/B,UAEF","file":"tree_view.css","sourcesContent":["/* stylelint-disable selector-max-type -- Copied from primer/react */\n\n.TreeViewRootUlStyles {\n padding: 0;\n margin: 0;\n list-style: none;\n\n /*\n * WARNING: This is a performance optimization.\n *\n * We define styles for the tree items at the root level of the tree\n * to avoid recomputing the styles for each item when the tree updates.\n * We're sacrificing maintainability for performance because TreeView\n * needs to be performant enough to handle large trees (thousands of items).\n *\n * This is intended to be a temporary solution until we can improve the\n * performance of our styling patterns.\n *\n * Do NOT copy this pattern without understanding the tradeoffs.\n */\n & .TreeViewItem {\n outline: none;\n\n &:focus-visible > div {\n box-shadow: var(--boxShadow-thick) var(--fgColor-accent);\n\n @media (forced-colors: active) {\n outline: 2px solid HighlightText;\n /* stylelint-disable-next-line declaration-property-value-no-unknown -- Copied from primer/react */\n outline-offset: -2;\n }\n }\n\n &[data-has-leading-action] {\n --has-leading-action: 1;\n }\n }\n\n & .TreeViewItemContainer {\n --level: 1;\n --toggle-width: 1rem;\n --min-item-height: 2rem;\n\n position: relative;\n display: grid;\n width: 100%;\n font-size: var(--text-body-size-medium);\n color: var(--fgColor-default);\n cursor: pointer;\n border-radius: var(--borderRadius-medium);\n grid-template-columns: var(--spacer-width) var(--leading-action-width) var(--toggle-width) 1fr;\n grid-template-areas: 'spacer leadingAction toggle content';\n\n --leading-action-width: calc(var(--has-leading-action, 0) * 1.5rem);\n --spacer-width: calc(calc(var(--level) - 1) * (var(--toggle-width) / 2));\n\n &:hover {\n background-color: var(--control-transparent-bgColor-hover);\n\n @media (forced-colors: active) {\n outline: 2px solid transparent;\n outline-offset: -2px;\n }\n }\n\n @media (pointer: coarse) {\n --toggle-width: 1.5rem;\n --min-item-height: 2.75rem;\n }\n\n &:has(.TreeViewFailureMessage):hover {\n cursor: default;\n background-color: transparent;\n\n @media (forced-colors: active) {\n outline: none;\n }\n }\n }\n\n &:where([data-omit-spacer='true']) .TreeViewItemContainer {\n grid-template-columns: 0 0 0 1fr;\n }\n\n & .TreeViewItem[aria-current='true'] > .TreeViewItemContainer {\n background-color: var(--control-transparent-bgColor-selected);\n\n /* Current item indicator */\n /* stylelint-disable-next-line selector-max-specificity -- Copied from primer/react */\n &::after {\n position: absolute;\n top: calc(50% - var(--base-size-12));\n left: calc(-1 * var(--base-size-8));\n width: 0.25rem;\n height: 1.5rem;\n content: '';\n\n /*\n * Use fgColor accent for consistency across all themes. Using the \"correct\" variable,\n * --bgColor-accent-emphasis, causes vrt failures for dark high contrast mode\n */\n /* stylelint-disable-next-line primer/colors */\n background-color: var(--fgColor-accent);\n border-radius: var(--borderRadius-medium);\n\n @media (forced-colors: active) {\n background-color: HighlightText;\n }\n }\n }\n\n /* stylelint-disable-next-line no-duplicate-selectors -- Copied from primer/react */\n & .TreeViewItem {\n &[aria-checked='true'] {\n /* stylelint-disable-next-line selector-max-compound-selectors, selector-max-specificity -- Copied from primer/react */\n & > .TreeViewItemContainer .FormControl-checkbox {\n background: var(--control-checked-bgColor-rest);\n border-color: var(--control-checked-borderColor-rest);\n transition: background-color, border-color 80ms cubic-bezier(0.32, 0, 0.67, 0) 0ms; /* unchecked -> checked */\n\n /* stylelint-disable-next-line max-nesting-depth, selector-max-compound-selectors, selector-max-specificity -- Copied from primer/react */\n &::before {\n visibility: visible;\n transition: visibility 0s linear 0s;\n animation: checkmarkIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;\n }\n }\n }\n\n &[aria-checked='mixed'] {\n /* stylelint-disable-next-line selector-max-compound-selectors, selector-max-specificity -- Copied from primer/react */\n & > .TreeViewItemContainer .FormControl-checkbox {\n background: var(--control-checked-bgColor-rest);\n border-color: var(--control-checked-borderColor-rest);\n transition: background-color, border-color 80ms cubic-bezier(0.32, 0, 0.67, 0) 0ms; /* unchecked -> checked */\n\n /* stylelint-disable-next-line max-nesting-depth, selector-max-compound-selectors, selector-max-specificity -- Copied from primer/react */\n &::before {\n visibility: visible;\n mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMiIgdmlld0JveD0iMCAwIDEwIDIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMCAxQzAgMC40NDc3MTUgMC40NDc3MTUgMCAxIDBIOUM5LjU1MjI5IDAgMTAgMC40NDc3MTUgMTAgMUMxMCAxLjU1MjI4IDkuNTUyMjkgMiA5IDJIMUMwLjQ0NzcxNSAyIDAgMS41NTIyOCAwIDFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n animation: checkmarkIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;\n clip-path: none;\n }\n }\n }\n }\n\n & .TreeViewItemToggle {\n display: flex;\n height: 100%;\n\n /* The toggle should appear vertically centered for single-line items, but remain at the top for items that wrap\n across more lines. */\n /* stylelint-disable-next-line primer/spacing */\n padding-top: calc(var(--min-item-height) / 2 - var(--base-size-12) / 2);\n color: var(--fgColor-muted);\n grid-area: toggle;\n justify-content: center;\n align-items: flex-start;\n }\n\n & .TreeViewItemToggleHover:hover {\n background-color: var(--control-transparent-bgColor-hover);\n }\n\n & .TreeViewItemToggleEnd {\n border-top-left-radius: var(--borderRadius-medium);\n border-bottom-left-radius: var(--borderRadius-medium);\n }\n\n & .TreeViewItemContent {\n display: flex;\n height: 100%;\n padding: 0 var(--base-size-8);\n\n /* The dynamic top and bottom padding to maintain the minimum item height for single line items */\n /* stylelint-disable-next-line primer/spacing */\n padding-top: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2);\n /* stylelint-disable-next-line primer/spacing */\n padding-bottom: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2);\n line-height: var(--custom-line-height, var(--text-body-lineHeight-medium, 1.4285));\n grid-area: content;\n gap: var(--stack-gap-condensed);\n\n & .TreeViewItemCheckbox {\n position: relative;\n color: var(--control-fgColor-rest);\n text-align: left;\n user-select: none;\n background-color: transparent;\n border: none;\n border-radius: var(--borderRadius-medium);\n transition: background 33.333ms linear;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n }\n\n & .TreeViewItemContentText {\n flex: 1 1 auto;\n width: 0;\n }\n\n &:where([data-truncate-text='true']) .TreeViewItemContentText {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &:where([data-truncate-text='false']) .TreeViewItemContentText {\n /* stylelint-disable-next-line declaration-property-value-keyword-no-deprecated -- Copied from primer/react */\n word-break: break-word;\n }\n\n & .TreeViewItemVisual {\n display: flex;\n\n /* The visual icons should appear vertically centered for single-line items, but remain at the top for items that wrap\n across more lines. */\n height: var(--custom-line-height, 1.3rem);\n color: var(--fgColor-muted);\n align-items: center;\n }\n\n & .TreeViewItemLeadingAction {\n display: flex;\n color: var(--fgColor-muted);\n grid-area: leadingAction;\n\n & > button {\n flex-shrink: 1;\n }\n }\n\n & .TreeViewItemLevelLine {\n width: 100%;\n height: 100%;\n\n /*\n * On devices without hover, the nesting indicator lines\n * appear at all times.\n */\n border-color: var(--borderColor-muted);\n border-right: var(--borderWidth-thin) solid;\n }\n\n /*\n * On devices with :hover support, the nesting indicator lines\n * fade in when the user mouses over the entire component,\n * or when there's focus inside the component. This makes\n * sure the component remains simple when not in use.\n */\n @media (hover: hover) {\n .TreeViewItemLevelLine {\n border-color: transparent;\n }\n\n &:hover .TreeViewItemLevelLine,\n &:focus-within .TreeViewItemLevelLine {\n border-color: var(--borderColor-muted);\n }\n }\n\n & .TreeViewVisuallyHidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n /* stylelint-disable-next-line primer/spacing */\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n }\n}\n\n.TreeViewSkeletonItemContainerStyle {\n display: flex;\n align-items: center;\n column-gap: 0.5rem;\n height: 2rem;\n\n @media (pointer: coarse) {\n height: 2.75rem;\n }\n\n &:nth-of-type(5n + 1) {\n --tree-item-loading-width: 67%;\n }\n\n &:nth-of-type(5n + 2) {\n --tree-item-loading-width: 47%;\n }\n\n &:nth-of-type(5n + 3) {\n --tree-item-loading-width: 73%;\n }\n\n &:nth-of-type(5n + 4) {\n --tree-item-loading-width: 64%;\n }\n\n &:nth-of-type(5n + 5) {\n --tree-item-loading-width: 50%;\n }\n}\n\n.TreeItemSkeletonTextStyles {\n width: var(--tree-item-loading-width, 67%);\n}\n\n.TreeViewFailureMessage {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 0.5rem;\n width: 100%;\n align-items: center;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ <tree-view>
2
+ <%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
3
+ <% nodes.each do |node| %>
4
+ <%= node %>
5
+ <% end %>
6
+ <% end %>
7
+ </tree-view>