primer_view_components 0.0.123 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/app/assets/javascripts/primer_view_components.js +1 -1
  4. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  5. data/app/assets/styles/primer_view_components.css +2 -2
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/action_list/divider.rb +2 -2
  8. data/app/components/primer/alpha/action_list/heading.html.erb +1 -1
  9. data/app/components/primer/alpha/action_list/heading.rb +10 -4
  10. data/app/components/primer/alpha/action_list/item.rb +7 -3
  11. data/app/components/primer/alpha/action_list.css.json +41 -0
  12. data/app/components/primer/alpha/action_list.html.erb +6 -8
  13. data/app/components/primer/alpha/action_list.rb +5 -10
  14. data/app/components/primer/alpha/auto_complete.css.json +11 -0
  15. data/app/components/primer/alpha/banner.css.json +14 -0
  16. data/app/components/primer/alpha/button_marketing.css.json +10 -0
  17. data/app/components/primer/alpha/dialog.css.json +63 -0
  18. data/app/components/primer/alpha/dropdown.css.json +21 -0
  19. data/app/components/primer/alpha/layout.css.json +27 -0
  20. data/app/components/primer/alpha/menu.css.json +11 -0
  21. data/app/components/primer/alpha/nav_list/{section.rb → group.rb} +9 -9
  22. data/app/components/primer/alpha/nav_list/item.html.erb +1 -1
  23. data/app/components/primer/alpha/nav_list/item.rb +18 -2
  24. data/app/components/primer/alpha/nav_list.d.ts +1 -0
  25. data/app/components/primer/alpha/nav_list.html.erb +8 -8
  26. data/app/components/primer/alpha/nav_list.js +24 -0
  27. data/app/components/primer/alpha/nav_list.rb +28 -32
  28. data/app/components/primer/alpha/nav_list.ts +27 -0
  29. data/app/components/primer/alpha/navigation/tab.rb +168 -0
  30. data/app/components/primer/alpha/overlay/body.rb +26 -0
  31. data/app/components/primer/alpha/overlay/footer.rb +41 -0
  32. data/app/components/primer/alpha/overlay/header.html.erb +15 -0
  33. data/app/components/primer/alpha/overlay/header.rb +47 -0
  34. data/app/components/primer/alpha/overlay.css +1 -0
  35. data/app/components/primer/alpha/overlay.css.json +11 -0
  36. data/app/components/primer/alpha/overlay.css.map +1 -0
  37. data/app/components/primer/alpha/overlay.html.erb +11 -0
  38. data/app/components/primer/alpha/overlay.pcss +14 -0
  39. data/app/components/primer/alpha/overlay.rb +207 -0
  40. data/app/components/primer/alpha/segmented_control.css.json +15 -0
  41. data/app/components/primer/alpha/tab_nav.css.json +10 -0
  42. data/app/components/primer/alpha/tab_nav.rb +10 -3
  43. data/app/components/primer/alpha/tab_panels.rb +2 -2
  44. data/app/components/primer/alpha/text_field.css.json +38 -0
  45. data/app/components/primer/alpha/toggle_switch.css.json +16 -0
  46. data/app/components/primer/alpha/underline_nav.css +1 -1
  47. data/app/components/primer/alpha/underline_nav.css.json +13 -0
  48. data/app/components/primer/alpha/underline_nav.css.map +1 -1
  49. data/app/components/primer/alpha/underline_nav.pcss +1 -0
  50. data/app/components/primer/alpha/underline_nav.rb +2 -2
  51. data/app/components/primer/alpha/underline_panels.rb +2 -2
  52. data/app/components/primer/anchored_position.d.ts +27 -0
  53. data/app/components/primer/anchored_position.js +149 -0
  54. data/app/components/primer/anchored_position.ts +167 -0
  55. data/app/components/primer/beta/avatar.css.json +14 -0
  56. data/app/components/primer/beta/avatar_stack.css.json +9 -0
  57. data/app/components/primer/beta/blankslate.css.json +12 -0
  58. data/app/components/primer/beta/border_box.css.json +32 -0
  59. data/app/components/primer/beta/breadcrumbs.css.json +4 -0
  60. data/app/components/primer/beta/button.css.json +22 -0
  61. data/app/components/primer/beta/button.html.erb +1 -1
  62. data/app/components/primer/beta/button.rb +2 -1
  63. data/app/components/primer/beta/counter.css.json +6 -0
  64. data/app/components/primer/beta/flash.css.json +15 -0
  65. data/app/components/primer/beta/flash.html.erb +1 -2
  66. data/app/components/primer/beta/label.css.json +20 -0
  67. data/app/components/primer/beta/link.css.json +8 -0
  68. data/app/components/primer/beta/popover.css.json +18 -0
  69. data/app/components/primer/beta/progress_bar.css.json +6 -0
  70. data/app/components/primer/beta/state.css.json +10 -0
  71. data/app/components/primer/beta/subhead.css.json +8 -0
  72. data/app/components/primer/beta/timeline_item.css.json +9 -0
  73. data/app/components/primer/beta/truncate.css.json +6 -0
  74. data/app/components/primer/component.rb +34 -0
  75. data/app/components/primer/navigation/tab_component.rb +3 -157
  76. data/app/components/primer/primer.d.ts +2 -0
  77. data/app/components/primer/primer.js +2 -0
  78. data/app/components/primer/primer.pcss +3 -0
  79. data/app/components/primer/primer.ts +2 -0
  80. data/app/components/primer/truncate.css.json +7 -0
  81. data/app/lib/primer/css/layout.css.json +263 -0
  82. data/app/lib/primer/css/utilities.css.json +1636 -0
  83. data/lib/primer/deprecations.yml +4 -0
  84. data/lib/primer/view_components/linters/base_linter.rb +1 -1
  85. data/lib/primer/view_components/linters/disallow_component_css_counter.rb +30 -0
  86. data/lib/primer/view_components/version.rb +2 -2
  87. data/lib/primer/yard/component_manifest.rb +3 -1
  88. data/lib/tasks/docs.rake +1 -1
  89. data/previews/primer/alpha/action_list_preview.rb +6 -14
  90. data/previews/primer/alpha/nav_list_preview/trailing_action.html.erb +19 -0
  91. data/previews/primer/alpha/nav_list_preview.rb +19 -30
  92. data/previews/primer/alpha/overlay_preview/middle_of_page.html.erb +17 -0
  93. data/previews/primer/alpha/overlay_preview.rb +112 -0
  94. data/previews/primer/alpha/tab_nav_preview/with_extra.html.erb +8 -0
  95. data/previews/primer/alpha/tab_nav_preview.rb +5 -0
  96. data/previews/primer/alpha/tab_panels_preview/with_extra.html.erb +17 -0
  97. data/previews/primer/alpha/tab_panels_preview.rb +5 -0
  98. data/static/arguments.json +167 -7
  99. data/static/audited_at.json +6 -1
  100. data/static/classes.json +311 -0
  101. data/static/constants.json +122 -8
  102. data/static/previews.json +31 -0
  103. data/static/statuses.json +7 -2
  104. metadata +25 -6
  105. data/app/components/primer/alpha/nav_list/section.html.erb +0 -3
  106. data/previews/primer/alpha/action_list_preview/heading.html.erb +0 -4
  107. /data/app/components/primer/{navigation/tab_component.html.erb → alpha/navigation/tab.html.erb} +0 -0
@@ -0,0 +1,167 @@
1
+ import type {AnchorAlignment, AnchorSide, PositionSettings} from '@primer/behaviors'
2
+ import {getAnchoredPosition} from '@primer/behaviors'
3
+
4
+ const updateWhenVisible = (() => {
5
+ const anchors = new Set<AnchoredPositionElement>()
6
+ let intersectionObserver: IntersectionObserver | null = null
7
+ let resizeObserver: ResizeObserver | null = null
8
+ function updateVisibleAnchors() {
9
+ for (const anchor of anchors) {
10
+ anchor.update()
11
+ }
12
+ }
13
+ return (el: AnchoredPositionElement) => {
14
+ // eslint-disable-next-line github/prefer-observers
15
+ window.addEventListener('resize', updateVisibleAnchors)
16
+ intersectionObserver ||= new IntersectionObserver(entries => {
17
+ for (const entry of entries) {
18
+ const target = entry.target as AnchoredPositionElement
19
+ if (entry.isIntersecting) {
20
+ target.update()
21
+ anchors.add(target)
22
+ } else {
23
+ anchors.delete(target)
24
+ }
25
+ }
26
+ })
27
+ resizeObserver ||= new ResizeObserver(() => {
28
+ for (const anchor of anchors) {
29
+ anchor.update()
30
+ }
31
+ })
32
+ resizeObserver.observe(el.ownerDocument.documentElement)
33
+ intersectionObserver.observe(el)
34
+ }
35
+ })()
36
+
37
+ export default class AnchoredPositionElement extends HTMLElement implements PositionSettings {
38
+ get align(): AnchorAlignment {
39
+ const value = this.getAttribute('align')
40
+ if (value === 'center' || value === 'end') return value
41
+ return 'start'
42
+ }
43
+
44
+ set align(value: AnchorAlignment) {
45
+ this.setAttribute('align', `${value}`)
46
+ }
47
+
48
+ get side(): AnchorSide {
49
+ const value = this.getAttribute('side')
50
+ if (
51
+ value === 'inside-top' ||
52
+ value === 'inside-bottom' ||
53
+ value === 'inside-left' ||
54
+ value === 'inside-right' ||
55
+ value === 'inside-center' ||
56
+ value === 'outside-top' ||
57
+ value === 'outside-left' ||
58
+ value === 'outside-right'
59
+ ) {
60
+ return value
61
+ }
62
+ return 'outside-bottom'
63
+ }
64
+
65
+ set side(value: AnchorSide) {
66
+ this.setAttribute('side', `${value}`)
67
+ }
68
+
69
+ get anchorOffset(): number {
70
+ const alias = this.getAttribute('anchor-offset')
71
+ if (alias === 'spacious' || alias === '8') return 8
72
+ return 4
73
+ }
74
+
75
+ set anchorOffset(value: number | 'normal' | 'spacious') {
76
+ this.setAttribute('anchor-offset', `${value}`)
77
+ }
78
+
79
+ get anchor() {
80
+ return this.getAttribute('anchor') || ''
81
+ }
82
+
83
+ set anchor(value: string) {
84
+ this.setAttribute('anchor', `${value}`)
85
+ }
86
+
87
+ #anchorElement: HTMLElement | null = null
88
+ get anchorElement(): HTMLElement | null {
89
+ if (this.#anchorElement) return this.#anchorElement
90
+ const idRef = this.anchor
91
+ if (!idRef) return null
92
+ return this.ownerDocument.getElementById(idRef)
93
+ }
94
+
95
+ set anchorElement(value: HTMLElement | null) {
96
+ this.#anchorElement = value
97
+ if (!this.#anchorElement) {
98
+ this.removeAttribute('anchor')
99
+ }
100
+ }
101
+
102
+ get alignmentOffset(): number {
103
+ return Number(this.getAttribute('alignment-offset'))
104
+ }
105
+
106
+ set alignmentOffset(value: number) {
107
+ this.setAttribute('alignment-offset', `${value}`)
108
+ }
109
+
110
+ get allowOutOfBounds() {
111
+ return this.hasAttribute('allow-out-of-bounds')
112
+ }
113
+
114
+ set allowOutOfBounds(value: boolean) {
115
+ this.toggleAttribute('allow-out-of-bounds', value)
116
+ }
117
+
118
+ connectedCallback() {
119
+ this.update()
120
+ this.addEventListener('beforetoggle', () => this.update())
121
+ updateWhenVisible(this)
122
+ }
123
+
124
+ static observedAttributes = ['align', 'side', 'anchor', 'alignment-offset', 'allow-out-of-bounds']
125
+ attributeChangedCallback() {
126
+ this.update()
127
+ }
128
+
129
+ #animationFrame: ReturnType<typeof requestAnimationFrame>
130
+ update() {
131
+ if (!this.isConnected) return
132
+ cancelAnimationFrame(this.#animationFrame)
133
+
134
+ this.#animationFrame = requestAnimationFrame(() => {
135
+ const anchor = this.anchorElement
136
+ if (!anchor) return
137
+ const {left, top, anchorSide, anchorAlign} = getAnchoredPosition(this, anchor, this)
138
+ this.style.top = `${top}px`
139
+ this.style.left = `${left}px`
140
+ this.classList.remove(
141
+ 'Overlay--anchorAlign-start',
142
+ 'Overlay--anchorAlign-center',
143
+ 'Overlay--anchorAlign-end',
144
+ 'Overlay--anchorSide-insideTop',
145
+ 'Overlay--anchorSide-insideBottom',
146
+ 'Overlay--anchorSide-insideLeft',
147
+ 'Overlay--anchorSide-insideRight',
148
+ 'Overlay--anchorSide-insideCenter',
149
+ 'Overlay--anchorSide-outsideTop',
150
+ 'Overlay--anchorSide-outsideLeft',
151
+ 'Overlay--anchorSide-outsideRight'
152
+ )
153
+ this.classList.add(`Overlay--anchorAlign-${anchorAlign}`, `Overlay--anchorSide-${anchorSide}`)
154
+ })
155
+ }
156
+ }
157
+
158
+ if (!customElements.get('anchored-position')) {
159
+ window.AnchoredPositionElement = AnchoredPositionElement
160
+ customElements.define('anchored-position', AnchoredPositionElement)
161
+ }
162
+
163
+ declare global {
164
+ interface Window {
165
+ AnchoredPositionElement: typeof AnchoredPositionElement
166
+ }
167
+ }
@@ -13,5 +13,19 @@
13
13
  ".avatar-6",
14
14
  ".avatar-7",
15
15
  ".avatar-8"
16
+ ],
17
+ "classes": [
18
+ "avatar",
19
+ "avatar-link",
20
+ "avatar-group-item",
21
+ "avatar-1",
22
+ "avatar-2",
23
+ "avatar-small",
24
+ "avatar-3",
25
+ "avatar-4",
26
+ "avatar-5",
27
+ "avatar-6",
28
+ "avatar-7",
29
+ "avatar-8"
16
30
  ]
17
31
  }
@@ -24,5 +24,14 @@
24
24
  ".AvatarStack--right .avatar.avatar-more:before",
25
25
  ".AvatarStack--right .avatar.avatar-more:after",
26
26
  ".AvatarStack--right .avatar"
27
+ ],
28
+ "classes": [
29
+ "AvatarStack",
30
+ "AvatarStack-body",
31
+ "AvatarStack--two",
32
+ "AvatarStack--three-plus",
33
+ "avatar",
34
+ "avatar-more",
35
+ "AvatarStack--right"
27
36
  ]
28
37
  }
@@ -18,5 +18,17 @@
18
18
  ".blankslate-large h3",
19
19
  ".blankslate-large p",
20
20
  ".blankslate-clean-background"
21
+ ],
22
+ "classes": [
23
+ "blankslate",
24
+ "blankslate-icon",
25
+ "blankslate-image",
26
+ "blankslate-heading",
27
+ "blankslate-action",
28
+ "blankslate-capped",
29
+ "blankslate-spacious",
30
+ "blankslate-narrow",
31
+ "blankslate-large",
32
+ "blankslate-clean-background"
21
33
  ]
22
34
  }
@@ -50,5 +50,37 @@
50
50
  ".Box-row--blue",
51
51
  ".Box-row--gray",
52
52
  ".Box-btn-octicon.btn-octicon"
53
+ ],
54
+ "classes": [
55
+ "Box",
56
+ "Box--condensed",
57
+ "Box-body",
58
+ "Box-footer",
59
+ "Box-header",
60
+ "Box-btn-octicon",
61
+ "btn-octicon",
62
+ "Box-row",
63
+ "Box--spacious",
64
+ "Box-title",
65
+ "Box-row--unread",
66
+ "unread",
67
+ "navigation-focus",
68
+ "Box-row--drag-button",
69
+ "is-dragging",
70
+ "sortable-chosen",
71
+ "sortable-ghost",
72
+ "Box-row--drag-hide",
73
+ "Box-row--focus-gray",
74
+ "Box-row--focus-blue",
75
+ "Box-row--hover-gray",
76
+ "Box-row--hover-blue",
77
+ "Box-row-link",
78
+ "Box--scrollable",
79
+ "Box--blue",
80
+ "Box--danger",
81
+ "Box-header--blue",
82
+ "Box-row--yellow",
83
+ "Box-row--blue",
84
+ "Box-row--gray"
53
85
  ]
54
86
  }
@@ -7,5 +7,9 @@
7
7
  ".breadcrumb-item-selected:after",
8
8
  ".breadcrumb-item[aria-current]:not([aria-current=false]):after",
9
9
  ".breadcrumb-item-selected a"
10
+ ],
11
+ "classes": [
12
+ "breadcrumb-item",
13
+ "breadcrumb-item-selected"
10
14
  ]
11
15
  }
@@ -67,5 +67,27 @@
67
67
  ".Button--iconOnly",
68
68
  ".Button--iconOnly.Button--small",
69
69
  ".Button--iconOnly.Button--large"
70
+ ],
71
+ "classes": [
72
+ "Button",
73
+ "Button-withTooltip",
74
+ "Button-content",
75
+ "Button-content--alignStart",
76
+ "Button-visual",
77
+ "Counter",
78
+ "Button-label",
79
+ "Button-leadingVisual",
80
+ "Button-trailingVisual",
81
+ "Button-trailingAction",
82
+ "Button--small",
83
+ "Button--large",
84
+ "Button--fullWidth",
85
+ "Button--primary",
86
+ "Button--secondary",
87
+ "Button--invisible",
88
+ "Button--invisible-noVisuals",
89
+ "Button--link",
90
+ "Button--danger",
91
+ "Button--iconOnly"
70
92
  ]
71
93
  }
@@ -1,4 +1,4 @@
1
- <%= render Primer::ConditionalWrapper.new(condition: tooltip.present?, tag: :div, classes: "Button-withTooltip") do -%>
1
+ <%= render Primer::ConditionalWrapper.new(condition: tooltip.present?, tag: :div, display: (:block if @block), classes: "Button-withTooltip") do -%>
2
2
  <%= render Primer::Beta::BaseButton.new(**@system_arguments) do -%>
3
3
  <span class="<%= @align_content_classes %>">
4
4
  <% if leading_visual %>
@@ -144,6 +144,7 @@ module Primer
144
144
  **system_arguments
145
145
  )
146
146
  @scheme = scheme
147
+ @block = block
147
148
 
148
149
  @system_arguments = system_arguments
149
150
 
@@ -162,7 +163,7 @@ module Primer
162
163
  SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)],
163
164
  SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)],
164
165
  "Button",
165
- "Button--fullWidth" => block
166
+ "Button--fullWidth" => @block
166
167
  )
167
168
  end
168
169
 
@@ -6,5 +6,11 @@
6
6
  ".Counter .octicon",
7
7
  ".Counter--primary",
8
8
  ".Counter--secondary"
9
+ ],
10
+ "classes": [
11
+ "Counter",
12
+ "octicon",
13
+ "Counter--primary",
14
+ "Counter--secondary"
9
15
  ]
10
16
  }
@@ -23,5 +23,20 @@
23
23
  ".flash-banner",
24
24
  ".flash-full",
25
25
  ".warning"
26
+ ],
27
+ "classes": [
28
+ "flash",
29
+ "octicon",
30
+ "flash-messages",
31
+ "flash-close",
32
+ "flash-action",
33
+ "btn",
34
+ "btn-primary",
35
+ "flash-warn",
36
+ "flash-error",
37
+ "flash-success",
38
+ "flash-full",
39
+ "flash-banner",
40
+ "warning"
26
41
  ]
27
42
  }
@@ -1,6 +1,5 @@
1
1
  <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
- <%= primer_octicon @icon if @icon %>
3
- <%= content %>
2
+ <%= primer_octicon @icon if @icon %><%= content %>
4
3
  <% if @dismissible %>
5
4
  <button class="flash-close js-flash-close" type="button" aria-label="Close">
6
5
  <%= primer_octicon "x" %>
@@ -21,5 +21,25 @@
21
21
  ".Label--closed",
22
22
  ".Label--done",
23
23
  ".Label--sponsors"
24
+ ],
25
+ "classes": [
26
+ "labels",
27
+ "Label",
28
+ "label",
29
+ "Label--large",
30
+ "Label--inline",
31
+ "Label--primary",
32
+ "Label--secondary",
33
+ "Label--accent",
34
+ "Label--info",
35
+ "Label--success",
36
+ "Label--attention",
37
+ "Label--warning",
38
+ "Label--severe",
39
+ "Label--danger",
40
+ "Label--open",
41
+ "Label--closed",
42
+ "Label--done",
43
+ "Label--sponsors"
24
44
  ]
25
45
  }
@@ -15,5 +15,13 @@
15
15
  ".Link--muted:hover [class*=color-fg]",
16
16
  ".Link--primary:hover [class*=color-fg]",
17
17
  ".Link--secondary:hover [class*=color-fg]"
18
+ ],
19
+ "classes": [
20
+ "Link",
21
+ "Link--primary",
22
+ "Link--secondary",
23
+ "Link--muted",
24
+ "Link--onHover",
25
+ "color-fg"
18
26
  ]
19
27
  }
@@ -35,5 +35,23 @@
35
35
  ".Popover-message--right:before",
36
36
  ".Popover-message--large",
37
37
  ".Popover-message>.btn-octicon"
38
+ ],
39
+ "classes": [
40
+ "Popover",
41
+ "Popover-message",
42
+ "Popover-message--no-caret",
43
+ "Popover-message--bottom-left",
44
+ "Popover-message--bottom-right",
45
+ "Popover-message--bottom",
46
+ "Popover-message--top-right",
47
+ "Popover-message--top-left",
48
+ "Popover-message--left-bottom",
49
+ "Popover-message--left-top",
50
+ "Popover-message--left",
51
+ "Popover-message--right-bottom",
52
+ "Popover-message--right-top",
53
+ "Popover-message--right",
54
+ "Popover-message--large",
55
+ "btn-octicon"
38
56
  ]
39
57
  }
@@ -6,5 +6,11 @@
6
6
  ".Progress--small",
7
7
  ".Progress-item",
8
8
  ".Progress-item+.Progress-item"
9
+ ],
10
+ "classes": [
11
+ "Progress",
12
+ "Progress--large",
13
+ "Progress--small",
14
+ "Progress-item"
9
15
  ]
10
16
  }
@@ -9,5 +9,15 @@
9
9
  ".State--closed",
10
10
  ".State--small",
11
11
  ".State--small .octicon"
12
+ ],
13
+ "classes": [
14
+ "State",
15
+ "state",
16
+ "State--draft",
17
+ "State--open",
18
+ "State--merged",
19
+ "State--closed",
20
+ "State--small",
21
+ "octicon"
12
22
  ]
13
23
  }
@@ -8,5 +8,13 @@
8
8
  ".Subhead-description",
9
9
  ".Subhead-actions",
10
10
  ".Subhead-actions+.Subhead-description"
11
+ ],
12
+ "classes": [
13
+ "Subhead",
14
+ "Subhead--spacious",
15
+ "Subhead-heading",
16
+ "Subhead-heading--danger",
17
+ "Subhead-description",
18
+ "Subhead-actions"
11
19
  ]
12
20
  }
@@ -12,5 +12,14 @@
12
12
  ".TimelineItem--condensed",
13
13
  ".TimelineItem--condensed:last-child",
14
14
  ".TimelineItem--condensed .TimelineItem-badge"
15
+ ],
16
+ "classes": [
17
+ "TimelineItem",
18
+ "TimelineItem-badge",
19
+ "TimelineItem-badge--success",
20
+ "TimelineItem-body",
21
+ "TimelineItem-avatar",
22
+ "TimelineItem-break",
23
+ "TimelineItem--condensed"
15
24
  ]
16
25
  }
@@ -8,5 +8,11 @@
8
8
  ".Truncate>.Truncate-text.Truncate-text--expandable:active",
9
9
  ".Truncate>.Truncate-text.Truncate-text--expandable:focus",
10
10
  ".Truncate>.Truncate-text.Truncate-text--expandable:hover"
11
+ ],
12
+ "classes": [
13
+ "Truncate",
14
+ "Truncate-text",
15
+ "Truncate-text--primary",
16
+ "Truncate-text--expandable"
11
17
  ]
12
18
  }
@@ -58,6 +58,40 @@ module Primer
58
58
  system_arguments[:"aria-#{val}"] || system_arguments.dig(:aria, val.to_sym)
59
59
  end
60
60
 
61
+ # Merges hashes that contain "aria-*" keys and nested aria: hashes. Removes keys from
62
+ # each hash and returns them in the new hash.
63
+ #
64
+ # Eg. merge_aria({ "aria-disabled": "true" }, { aria: { invalid: "true" } })
65
+ # => { disabled: "true", invalid: "true" }
66
+ #
67
+ # It's designed to be used to normalize and merge aria information from system_arguments
68
+ # hashes. Consider using this pattern in component initializers:
69
+ #
70
+ # @system_arguments[:aria] = merge_aria(
71
+ # @system_arguments,
72
+ # { aria: { labelled_by: id } }
73
+ # )
74
+ def merge_aria(*hashes)
75
+ {}.tap do |result|
76
+ hashes.each do |hash|
77
+ next unless hash
78
+
79
+ result.merge!(hash.delete(:aria) || {})
80
+
81
+ hash.delete_if do |key, val|
82
+ key_s = key.to_s
83
+
84
+ if key.start_with?("aria-")
85
+ result[key_s.sub("aria-", "").to_sym] = val
86
+ true
87
+ else
88
+ false
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
61
95
  def validate_aria_label
62
96
  aria_label = aria("label", @system_arguments)
63
97
  aria_labelledby = aria("labelledby", @system_arguments)