primer_view_components 0.0.92 → 0.0.93

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +2 -2
  4. data/app/assets/javascripts/primer_view_components.js +1 -1
  5. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  6. data/app/assets/styles/primer_view_components.css +1 -0
  7. data/app/assets/styles/primer_view_components.css.map +1 -1
  8. data/app/components/primer/alpha/dialog/body.rb +25 -0
  9. data/app/components/primer/alpha/dialog/footer.rb +31 -0
  10. data/app/components/primer/alpha/dialog/header.html.erb +15 -0
  11. data/app/components/primer/alpha/dialog/header.rb +37 -0
  12. data/app/components/primer/alpha/dialog.html.erb +12 -0
  13. data/app/components/primer/alpha/dialog.rb +160 -0
  14. data/app/components/primer/alpha/modal-dialog-element.d.ts +1 -1
  15. data/app/components/primer/alpha/modal-dialog-element.js +2 -3
  16. data/app/components/primer/alpha/modal-dialog-element.ts +148 -0
  17. data/app/components/primer/alpha/toggle-switch-element.js +2 -0
  18. data/app/components/primer/alpha/toggle-switch-element.ts +2 -1
  19. data/app/components/primer/alpha/tool-tip-element.ts +0 -1
  20. data/app/components/primer/beta/button.html.erb +23 -0
  21. data/app/components/primer/beta/button.pcss +332 -0
  22. data/app/components/primer/beta/button.rb +189 -0
  23. data/app/components/primer/beta/icon_button.html.erb +6 -0
  24. data/app/components/primer/beta/icon_button.rb +104 -0
  25. data/app/components/primer/clipboard_copy_component.ts +1 -1
  26. data/app/components/primer/experimental/action-bar-element.d.ts +14 -0
  27. data/app/components/primer/experimental/action-bar-element.js +139 -0
  28. data/app/components/primer/experimental/action-menu-element.d.ts +31 -0
  29. data/app/components/primer/experimental/action-menu-element.js +334 -0
  30. data/app/components/primer/experimental/overflow-menu-element.d.ts +13 -0
  31. data/app/components/primer/experimental/overflow-menu-element.js +113 -0
  32. data/app/components/primer/primer.d.ts +1 -0
  33. data/app/components/primer/primer.js +1 -0
  34. data/app/components/primer/primer.pcss +1 -0
  35. data/app/components/primer/primer.ts +1 -0
  36. data/lib/postcss_mixins/focusBoxShadowInset.pcss +6 -0
  37. data/lib/postcss_mixins/focusOutline.pcss +5 -0
  38. data/lib/postcss_mixins/focusOutlineOnEmphasis.pcss +6 -0
  39. data/lib/postcss_mixins/minTouchTarget.js +20 -0
  40. data/lib/postcss_mixins/targetBoxShadow.pcss +6 -0
  41. data/lib/primer/view_components/linters/argument_mappers/base.rb +1 -1
  42. data/lib/primer/view_components/version.rb +1 -1
  43. data/lib/tasks/docs.rake +3 -8
  44. data/static/arguments.yml +113 -0
  45. data/static/audited_at.json +6 -0
  46. data/static/constants.json +107 -0
  47. data/static/statuses.json +6 -0
  48. metadata +25 -5
  49. data/app/components/primer/alpha/segmented-control-element.d.ts +0 -8
  50. data/app/components/primer/alpha/segmented-control-element.js +0 -28
  51. data/static/classes.yml +0 -230
@@ -0,0 +1,332 @@
1
+ /* CSS for Button */
2
+ /* temporary, pre primitives release */
3
+ :root {
4
+ --primer-duration-fast: 80ms;
5
+ --primer-easing-easeInOut: cubic-bezier(0.65, 0, 0.35, 1);
6
+ }
7
+
8
+ /* base button */
9
+ .Button {
10
+ position: relative;
11
+ font-size: var(--primer-text-body-size-medium, 14px);
12
+ font-weight: var(--base-text-weight-medium, 500);
13
+ cursor: pointer;
14
+ user-select: none;
15
+ background-color: transparent;
16
+ border: var(--primer-borderWidth-thin, 1px) solid;
17
+ border-color: transparent;
18
+ border-radius: var(--primer-borderRadius-medium, 6px);
19
+ color: var(--color-btn-text);
20
+ transition: var(--primer-duration-fast) var(--primer-easing-easeInOut);
21
+ transition-property: color, fill, background-color, border-color;
22
+ text-align: center;
23
+ height: var(--primer-control-medium-size, 32px);
24
+ padding: 0 var(--primer-control-medium-paddingInline-normal, 12px);
25
+ display: flex;
26
+ flex-direction: row;
27
+ justify-content: space-between;
28
+ align-items: center;
29
+ gap: var(--primer-control-medium-gap, 8px);
30
+
31
+ /* mobile friendly sizing */
32
+ @media (pointer: course) {
33
+ &::before {
34
+ @mixin minTouchTarget 48px, 48px;
35
+ }
36
+ }
37
+
38
+ /* base states */
39
+
40
+ &:hover {
41
+ transition-duration: var(--primer-duration-fast);
42
+ }
43
+
44
+ &:active,
45
+ &.Button--active {
46
+ transition: none;
47
+ }
48
+
49
+ &:disabled,
50
+ &.Button--disabled,
51
+ &[aria-disabled='true'] {
52
+ cursor: not-allowed;
53
+ box-shadow: none;
54
+ }
55
+
56
+ /* &:focus {
57
+ @mixin focusOutline;
58
+ } */
59
+ }
60
+
61
+ .Button-withTooltip {
62
+ position: relative;
63
+ display: inline-block;
64
+ }
65
+
66
+ a.Button,
67
+ summary.Button {
68
+ display: inline-flex;
69
+
70
+ &:hover {
71
+ text-decoration: none;
72
+ }
73
+ }
74
+
75
+ /* wrap grid content to allow trailingAction to lock-right */
76
+ .Button-content {
77
+ flex: 1 0 auto;
78
+ display: grid;
79
+ grid-template-areas: 'leadingVisual text trailingVisual';
80
+ grid-template-columns: min-content minmax(0, auto) min-content;
81
+ align-items: center;
82
+ place-content: center;
83
+ /* padding-bottom: 1px; optical alignment for firefox */
84
+
85
+ & > :not(:last-child) {
86
+ margin-right: var(--primer-control-medium-gap, 8px);
87
+ }
88
+ }
89
+
90
+ /* center child elements for fullWidth */
91
+ .Button-content--alignStart {
92
+ justify-content: start;
93
+ }
94
+
95
+ /* button child elements */
96
+
97
+ /* align svg */
98
+ .Button-visual {
99
+ display: flex;
100
+ pointer-events: none; /* allow click handler to work, avoiding visuals */
101
+ }
102
+
103
+ .Button-label {
104
+ grid-area: text;
105
+ white-space: nowrap;
106
+ line-height: var(--primer-text-body-lineHeight-medium, calc(20/14));
107
+ }
108
+
109
+ .Button-leadingVisual {
110
+ grid-area: leadingVisual;
111
+ }
112
+
113
+ .Button-trailingVisual {
114
+ grid-area: trailingVisual;
115
+ }
116
+
117
+ .Button-trailingAction {
118
+ margin-right: calc(var(--base-size-4, 4px) * -1);
119
+ }
120
+
121
+ /* sizes */
122
+
123
+ .Button--small {
124
+ font-size: var(--primer-text-body-size-small, 12px);
125
+ height: var(--primer-control-small-size, 28px);
126
+ padding: 0 var(--primer-control-small-paddingInline-normal, 12px);
127
+ gap: var(--primer-control-small-gap, 4px);
128
+
129
+ .Button-label {
130
+ line-height: var(--primer-text-body-lineHeight-small, calc(20/12));
131
+ }
132
+
133
+ .Button-content {
134
+ & > :not(:last-child) {
135
+ margin-right: var(--primer-control-small-gap, 4px);
136
+ }
137
+ }
138
+ }
139
+
140
+ .Button--large {
141
+ height: var(--primer-control-large-size, 40px);
142
+ padding: 0 var(--primer-control-large-paddingInline-normal, 12px);
143
+ gap: var(--primer-control-large-gap, 8px);
144
+
145
+ .Button-label {
146
+ line-height: var(--primer-text-body-lineHeight-large, calc(48/32));
147
+ }
148
+
149
+ .Button-content {
150
+ & > :not(:last-child) {
151
+ margin-right: var(--primer-control-large-gap, 8px);
152
+ }
153
+ }
154
+ }
155
+
156
+ .Button--fullWidth {
157
+ width: 100%;
158
+ }
159
+
160
+ /* variants */
161
+
162
+ /* primary */
163
+ .Button--primary {
164
+ color: var(--color-btn-primary-text);
165
+ fill: var(--color-btn-primary-icon);
166
+ background-color: var(--color-btn-primary-bg);
167
+ border-color: var(--color-btn-primary-border);
168
+ box-shadow: var(--color-btn-primary-shadow), var(--color-btn-primary-inset-shadow);
169
+
170
+ &:hover {
171
+ background-color: var(--color-btn-primary-hover-bg);
172
+ border-color: var(--color-btn-primary-hover-border);
173
+ }
174
+
175
+ /* fallback :focus state */
176
+ &:focus {
177
+ @mixin focusOutlineOnEmphasis;
178
+
179
+ /* remove fallback :focus if :focus-visible is supported */
180
+ &:not(:focus-visible) {
181
+ outline: solid 1px transparent;
182
+ box-shadow: none;
183
+ }
184
+ }
185
+
186
+ /* default focus state */
187
+ &:focus-visible {
188
+ @mixin focusOutlineOnEmphasis;
189
+ }
190
+
191
+ &:active,
192
+ &[aria-pressed='true'],
193
+ &.Button--pressed {
194
+ background-color: var(--color-btn-primary-selected-bg);
195
+ box-shadow: var(--color-btn-primary-selected-shadow);
196
+ }
197
+
198
+ &:disabled,
199
+ &.Button--disabled,
200
+ &[aria-disabled='true'] {
201
+ color: var(--color-btn-primary-disabled-text);
202
+ background-color: var(--color-btn-primary-disabled-bg);
203
+ border-color: var(--color-btn-primary-disabled-border);
204
+ fill: var(--color-btn-primary-disabled-text);
205
+ }
206
+ }
207
+
208
+ /* default (secondary) */
209
+ .Button--secondary {
210
+ color: var(--color-btn-text);
211
+ fill: var(--color-fg-muted); /* help this */
212
+ background-color: var(--color-btn-bg);
213
+ border-color: var(--color-btn-border);
214
+ box-shadow: var(--color-btn-shadow), var(--color-btn-inset-shadow);
215
+
216
+ &:hover {
217
+ background-color: var(--color-btn-hover-bg);
218
+ border-color: var(--color-btn-hover-border);
219
+ }
220
+
221
+ &:active,
222
+ &.Button--active {
223
+ background-color: var(--color-btn-active-bg);
224
+ border-color: var(--color-btn-active-border);
225
+ }
226
+
227
+ &[aria-pressed='true'],
228
+ &.Button--pressed {
229
+ background-color: var(--color-btn-selected-bg);
230
+ box-shadow: var(--color-primer-shadow-inset);
231
+ }
232
+
233
+ &:disabled,
234
+ &.Button--disabled,
235
+ &[aria-disabled='true'] {
236
+ color: var(--color-primer-fg-disabled);
237
+ background-color: var(--color-btn-bg);
238
+ border-color: var(--color-btn-border);
239
+ fill: var(--color-primer-fg-disabled);
240
+ }
241
+ }
242
+
243
+ /* link color without svg */
244
+ .Button--invisible {
245
+ color: var(--color-fg-default);
246
+ fill: var(--color-fg-default);
247
+ border: none;
248
+
249
+ &:hover {
250
+ background-color: var(--color-action-list-item-default-hover-bg);
251
+ }
252
+
253
+ &[aria-pressed='true'],
254
+ &:active,
255
+ &.Button--active,
256
+ &.Button--pressed {
257
+ background-color: var(--color-action-list-item-default-active-bg);
258
+ /* box-shadow: var(--color-primer-shadow-inset); */
259
+ }
260
+
261
+ &:disabled,
262
+ &.Button--disabled,
263
+ &[aria-disabled='true'] {
264
+ color: var(--color-primer-fg-disabled);
265
+ background-color: var(--color-btn-bg);
266
+ border-color: var(--color-btn-border);
267
+ fill: var(--color-primer-fg-disabled);
268
+ }
269
+
270
+ /* if visual is present, muted label color */
271
+ .Button-label:not(:only-child) {
272
+ color: var(--color-btn-text);
273
+ }
274
+
275
+ /* if trailingAction is present, muted label color */
276
+ .Button-content:not(:only-child) {
277
+ .Button-label {
278
+ color: var(--color-btn-text);
279
+ }
280
+ }
281
+ }
282
+
283
+ /* danger */
284
+ .Button--danger {
285
+ color: var(--color-btn-danger-text);
286
+ fill: var(--color-btn-danger-icon);
287
+ background-color: var(--color-btn-bg);
288
+ border-color: var(--color-btn-border);
289
+ box-shadow: var(--color-btn-shadow), var(--color-btn-inset-shadow);
290
+
291
+ &:hover {
292
+ color: var(--color-btn-danger-hover-text);
293
+ fill: var(--color-btn-danger-hover-text);
294
+ background-color: var(--color-btn-danger-hover-bg);
295
+ border-color: var(--color-btn-danger-hover-border);
296
+ box-shadow: var(--color-btn-danger-hover-shadow), var(--color-btn-danger-hover-inset-shadow);
297
+ }
298
+
299
+ &:active,
300
+ &[aria-pressed='true'],
301
+ &.Button--pressed {
302
+ color: var(--color-btn-danger-selected-text);
303
+ fill: var(--color-btn-danger-selected-text);
304
+ background-color: var(--color-btn-danger-selected-bg);
305
+ border-color: var(--color-btn-danger-selected-border);
306
+ box-shadow: var(--color-btn-danger-selected-shadow);
307
+ }
308
+
309
+ &:disabled,
310
+ &.disabled,
311
+ &[aria-disabled='true'] {
312
+ color: var(--color-btn-danger-disabled-text);
313
+ fill: var(--color-btn-danger-disabled-text);
314
+ background-color: var(--color-btn-danger-disabled-bg);
315
+ border-color: var(--color-btn-border);
316
+ }
317
+ }
318
+
319
+ .Button--iconOnly {
320
+ display: grid;
321
+ place-content: center;
322
+ padding: unset;
323
+ width: var(--primer-control-medium-size, 32px);
324
+
325
+ &.Button--small {
326
+ width: var(--primer-control-small-size, 28px);
327
+ }
328
+
329
+ &.Button--large {
330
+ width: var(--primer-control-large-size, 40px);
331
+ }
332
+ }
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Beta
5
+ # Use `Button` for actions (e.g. in forms). Use links for destinations, or moving from one page to another.
6
+ class Button < Primer::Component
7
+ status :beta
8
+
9
+ DEFAULT_SCHEME = :default
10
+ LINK_SCHEME = :link
11
+ SCHEME_MAPPINGS = {
12
+ DEFAULT_SCHEME => "",
13
+ :primary => "Button--primary",
14
+ :secondary => "Button--secondary",
15
+ :default => "Button--secondary",
16
+ :danger => "Button--danger",
17
+ :outline => "btn-outline",
18
+ :invisible => "Button--invisible",
19
+ LINK_SCHEME => "btn-link"
20
+ }.freeze
21
+ SCHEME_OPTIONS = SCHEME_MAPPINGS.keys
22
+
23
+ DEFAULT_SIZE = :medium
24
+ SIZE_MAPPINGS = {
25
+ :small => "Button--small",
26
+ :medium => "Button--medium",
27
+ :large => "Button--large",
28
+ DEFAULT_SIZE => "Button--medium"
29
+ }.freeze
30
+ SIZE_OPTIONS = SIZE_MAPPINGS.keys
31
+
32
+ DEFAULT_ALIGN_CONTENT = :center
33
+ ALIGN_CONTENT_MAPPINGS = {
34
+ :start => "Button-content--alignStart",
35
+ :center => "",
36
+ DEFAULT_ALIGN_CONTENT => ""
37
+ }.freeze
38
+ ALIGN_CONTENT_OPTIONS = ALIGN_CONTENT_MAPPINGS.keys
39
+
40
+ # Leading visuals appear to the left of the button text.
41
+ #
42
+ # Use:
43
+ #
44
+ # - `leading_visual_icon` for a <%= link_to_component(Primer::OcticonComponent) %>.
45
+ #
46
+ # @param system_arguments [Hash] Same arguments as <%= link_to_component(Primer::OcticonComponent) %>.
47
+ renders_one :leading_visual, types: {
48
+ icon: lambda { |**system_arguments|
49
+ Primer::OcticonComponent.new(**system_arguments)
50
+ }
51
+ }
52
+
53
+ # Trailing visuals appear to the right of the button text.
54
+ #
55
+ # Use:
56
+ #
57
+ # - `trailing_visual_counter` for a <%= link_to_component(Primer::Beta::Counter) %>.
58
+ #
59
+ # @param system_arguments [Hash] Same arguments as <%= link_to_component(Primer::Beta::Counter) %>.
60
+ renders_one :trailing_visual, types: {
61
+ icon: Primer::OcticonComponent,
62
+ label: Primer::LabelComponent,
63
+ counter: Primer::CounterComponent
64
+ }
65
+
66
+ # Trailing action appears to the right of the trailing visual.
67
+ #
68
+ # Use:
69
+ #
70
+ # - `trailing_action_icon` for a <%= link_to_component(Primer::OcticonComponent) %>.
71
+ #
72
+ # @param system_arguments [Hash] Same arguments as <%= link_to_component(Primer::OcticonComponent) %>.
73
+ renders_one :trailing_action, types: {
74
+ icon: Primer::OcticonComponent
75
+ }
76
+
77
+ # `Tooltip` that appears on mouse hover or keyboard focus over the button. Use tooltips sparingly and as a last resort.
78
+ # **Important:** This tooltip defaults to `type: :description`. In a few scenarios, `type: :label` may be more appropriate.
79
+ # Consult the <%= link_to_component(Primer::Alpha::Tooltip) %> documentation for more information.
80
+ #
81
+ # @param type [Symbol] (:description) <%= one_of(Primer::Alpha::Tooltip::TYPE_OPTIONS) %>
82
+ # @param system_arguments [Hash] Same arguments as <%= link_to_component(Primer::Alpha::Tooltip) %>.
83
+ renders_one :tooltip, lambda { |**system_arguments|
84
+ raise ArgumentError, "Buttons with a tooltip must have a unique `id` set on the `Button`." if @id.blank? && !Rails.env.production?
85
+
86
+ system_arguments[:for_id] = @id
87
+ system_arguments[:type] ||= :description
88
+
89
+ Primer::Alpha::Tooltip.new(**system_arguments)
90
+ }
91
+
92
+ # @example Schemes
93
+ # <%= render(Primer::Beta::Button.new) { "Default" } %>
94
+ # <%= render(Primer::Beta::Button.new(scheme: :primary)) { "Primary" } %>
95
+ # <%= render(Primer::Beta::Button.new(scheme: :danger)) { "Danger" } %>
96
+ # <%= render(Primer::Beta::Button.new(scheme: :outline)) { "Outline" } %>
97
+ # <%= render(Primer::Beta::Button.new(scheme: :invisible)) { "Invisible" } %>
98
+ # <%= render(Primer::Beta::Button.new(scheme: :link)) { "Link" } %>
99
+ #
100
+ # @example Sizes
101
+ # <%= render(Primer::Beta::Button.new(size: :small)) { "Small" } %>
102
+ # <%= render(Primer::Beta::Button.new(size: :medium)) { "Medium" } %>
103
+ #
104
+ # @example Block
105
+ # <%= render(Primer::Beta::Button.new(block: :true)) { "Block" } %>
106
+ # <%= render(Primer::Beta::Button.new(block: :true, scheme: :primary)) { "Primary block" } %>
107
+ #
108
+ # @example With leading visual
109
+ # <%= render(Primer::Beta::Button.new) do |c| %>
110
+ # <% c.with_leading_visual_icon(icon: :star) %>
111
+ # Button
112
+ # <% end %>
113
+ #
114
+ # @example With trailing visual
115
+ # <%= render(Primer::Beta::Button.new) do |c| %>
116
+ # <% c.with_trailing_visual_counter(count: 15) %>
117
+ # Button
118
+ # <% end %>
119
+ #
120
+ # @example With leading and trailing visuals
121
+ # <%= render(Primer::Beta::Button.new) do |c| %>
122
+ # <% c.with_leading_visual_icon(icon: :star) %>
123
+ # <% c.with_trailing_visual_counter(count: 15) %>
124
+ # Button
125
+ # <% end %>
126
+ #
127
+ # @example With tooltip
128
+ # @description
129
+ # Use tooltips sparingly and as a last resort. Consult the <%= link_to_component(Primer::Alpha::Tooltip) %> documentation for more information.
130
+ # @code
131
+ # <%= render(Primer::Beta::Button.new(id: "button-with-tooltip")) do |c| %>
132
+ # <% c.with_tooltip(text: "Tooltip text") %>
133
+ # Button
134
+ # <% end %>
135
+ #
136
+ # @param scheme [Symbol] <%= one_of(Primer::Beta::Button::SCHEME_OPTIONS) %>
137
+ # @param size [Symbol] <%= one_of(Primer::Beta::Button::SIZE_OPTIONS) %>
138
+ # @param full_width [Boolean] Whether button is full-width with `display: block`.
139
+ # @param align_content [Symbol] <%= one_of(Primer::Beta::Button::ALIGN_CONTENT_OPTIONS) %>
140
+ # @param tag [Symbol] (Primer::Beta::BaseButton::DEFAULT_TAG) <%= one_of(Primer::Beta::BaseButton::TAG_OPTIONS) %>
141
+ # @param type [Symbol] (Primer::Beta::BaseButton::DEFAULT_TYPE) <%= one_of(Primer::Beta::BaseButton::TYPE_OPTIONS) %>
142
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
143
+ def initialize(
144
+ scheme: DEFAULT_SCHEME,
145
+ size: DEFAULT_SIZE,
146
+ full_width: false,
147
+ align_content: DEFAULT_ALIGN_CONTENT,
148
+ **system_arguments
149
+ )
150
+ @scheme = scheme
151
+
152
+ @system_arguments = system_arguments
153
+
154
+ @id = @system_arguments[:id]
155
+
156
+ @align_content_classes = class_names(
157
+ "Button-content",
158
+ system_arguments[:classes],
159
+ ALIGN_CONTENT_MAPPINGS[fetch_or_fallback(ALIGN_CONTENT_OPTIONS, align_content, DEFAULT_ALIGN_CONTENT)]
160
+ )
161
+
162
+ @system_arguments[:classes] = class_names(
163
+ system_arguments[:classes],
164
+ SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)],
165
+ SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)],
166
+ "Button" => !link?,
167
+ "Button--fullWidth" => full_width
168
+ )
169
+ end
170
+
171
+ private
172
+
173
+ def link?
174
+ @scheme == LINK_SCHEME
175
+ end
176
+
177
+ def trimmed_content
178
+ return if content.blank?
179
+
180
+ trimmed_content = content.strip
181
+
182
+ return trimmed_content unless content.html_safe?
183
+
184
+ # strip unsets `html_safe`, so we have to set it back again to guarantee that HTML blocks won't break
185
+ trimmed_content.html_safe # rubocop:disable Rails/OutputSafety
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,6 @@
1
+ <%= render Primer::ConditionalWrapper.new(condition: render_tooltip?, tag: :div, classes: "Button-withTooltip") do %>
2
+ <%= render Primer::Beta::BaseButton.new(**@system_arguments) do -%>
3
+ <%= render Primer::OcticonComponent.new(icon: @icon, classes: "Button-visual") %>
4
+ <% end -%>
5
+ <%= render Primer::Alpha::Tooltip.new(**@tooltip_arguments) if render_tooltip? %>
6
+ <% end %>
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Beta
5
+ # Use `IconButton` to render Icon-only buttons without the default button styles.
6
+ #
7
+ # `IconButton` will always render with a tooltip unless the tag is `:summary`.
8
+ # `IconButton` will always render with a tooltip unless the tag is `:summary`.
9
+ # @accessibility
10
+ # `IconButton` requires an `aria-label`, which will provide assistive technologies with an accessible label.
11
+ # The `aria-label` should describe the action to be invoked rather than the icon itself. For instance,
12
+ # if your `IconButton` renders a magnifying glass icon and invokes a search action, the `aria-label` should be
13
+ # `"Search"` instead of `"Magnifying glass"`.
14
+ # Either `aria-label` or `aria-description` will be used for the `Tooltip` text, depending on which one is present.
15
+ # Either `aria-label` or `aria-description` will be used for the `Tooltip` text, depending on which one is present.
16
+ # [Learn more about best functional image practices (WAI Images)](https://www.w3.org/WAI/tutorials/images/functional)
17
+ class IconButton < Primer::Component
18
+ status :beta
19
+
20
+ DEFAULT_SCHEME = :default
21
+ SCHEME_MAPPINGS = {
22
+ DEFAULT_SCHEME => "Button--secondary",
23
+ :danger => "Button--danger",
24
+ :invisible => "Button--invisible"
25
+ }.freeze
26
+ SCHEME_OPTIONS = SCHEME_MAPPINGS.keys
27
+
28
+ # @example Default
29
+ #
30
+ # <%= render(Primer::Beta::IconButton.new(icon: :search, "aria-label": "Search", id: "search-button", id: "search-button")) %>
31
+ #
32
+ # @example Schemes
33
+ #
34
+ # <%= render(Primer::Beta::IconButton.new(icon: :search, "aria-label": "Search")) %>
35
+ # <%= render(Primer::Beta::IconButton.new(icon: :trash, "aria-label": "Delete", scheme: :danger)) %>
36
+ #
37
+ # @example With an `aria-description`
38
+ # @description
39
+ # If you need to have a longer description for the icon button, use both the `aria-label` and `aria-description`
40
+ # attributes. A label should be short and concise, while the description can be longer as it is intended to provide
41
+ # more context and information. See the accessibility section for more information.
42
+ # @code
43
+ # <%= render(Primer::Beta::IconButton.new(icon: :bold, "aria-label": "Bold", "aria-description": "Add bold text, Cmd+b")) %>
44
+ #
45
+ # @example Custom tooltip direction
46
+ #
47
+ # <%= render(Primer::Beta::IconButton.new(icon: :search, "aria-label": "Search", tooltip_direction: :e)) %>
48
+ #
49
+ # @param icon [String] Name of <%= link_to_octicons %> to use.
50
+ # @param scheme [Symbol] <%= one_of(Primer::Beta::IconButton::SCHEME_OPTIONS) %>
51
+ # @param size [Symbol] <%= one_of(Primer::Beta::Button::SIZE_OPTIONS) %>
52
+ # @param tag [Symbol] <%= one_of(Primer::BaseButton::TAG_OPTIONS) %>
53
+ # @param type [Symbol] <%= one_of(Primer::BaseButton::TYPE_OPTIONS) %>
54
+ # @param aria-label [String] String that can be read by assistive technology. A label should be short and concise. See the accessibility section for more information.
55
+ # @param aria-description [String] String that can be read by assistive technology. A description can be longer as it is intended to provide more context and information. See the accessibility section for more information.
56
+ # @param tooltip_direction [Symbol] (Primer::Alpha::Tooltip::DIRECTION_DEFAULT) <%= one_of(Primer::Alpha::Tooltip::DIRECTION_OPTIONS) %>
57
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
58
+ def initialize(icon:, scheme: DEFAULT_SCHEME, tooltip_direction: Primer::Alpha::Tooltip::DIRECTION_DEFAULT, size: Primer::Beta::Button::DEFAULT_SIZE, **system_arguments)
59
+ @icon = icon
60
+
61
+ @system_arguments = system_arguments
62
+ @system_arguments[:id] ||= "icon-button-#{SecureRandom.hex(4)}"
63
+
64
+ @system_arguments[:classes] = class_names(
65
+ "Button",
66
+ "Button--iconOnly",
67
+ SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)],
68
+ Primer::Beta::Button::SIZE_MAPPINGS[fetch_or_fallback(Primer::Beta::Button::SIZE_OPTIONS, size, Primer::Beta::Button::DEFAULT_SIZE)],
69
+ system_arguments[:classes]
70
+ )
71
+
72
+ validate_aria_label
73
+
74
+ @aria_label = aria("label", @system_arguments)
75
+ @aria_description = aria("description", @system_arguments)
76
+
77
+ @tooltip_arguments = {
78
+ for_id: @system_arguments[:id],
79
+ direction: tooltip_direction
80
+ }
81
+
82
+ # If we have both an `aria-label` and a `aria-description`, we create a `Tooltip` with the description type and keep the `aria-label` in the button.
83
+ # Otherwise, the `aria-label` is used as the tooltip text, which is the `aria-labelled-by` of the button, so we don't set it in the button.
84
+ if @aria_label.present? && @aria_description.present?
85
+ @system_arguments.delete(:"aria-description")
86
+ @system_arguments[:aria].delete(:description) if @system_arguments.include?(:aria)
87
+ @tooltip_arguments[:text] = @aria_description
88
+ @tooltip_arguments[:type] = :description
89
+ else
90
+ @system_arguments.delete(:"aria-label")
91
+ @system_arguments[:aria].delete(:label) if @system_arguments.include?(:aria)
92
+ @tooltip_arguments[:text] = @aria_label
93
+ @tooltip_arguments[:type] = :label
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def render_tooltip?
100
+ @system_arguments[:tag] != :summary
101
+ end
102
+ end
103
+ end
104
+ end
@@ -32,7 +32,7 @@ function showCheck(button: HTMLElement) {
32
32
 
33
33
  const clipboardCopyElementTimers = new WeakMap<HTMLElement, number>()
34
34
 
35
- document.addEventListener('clipboard-copy', function({target}) {
35
+ document.addEventListener('clipboard-copy', function ({target}) {
36
36
  if (!(target instanceof HTMLElement)) return
37
37
  if (!target.hasAttribute('data-view-component')) return
38
38
 
@@ -0,0 +1,14 @@
1
+ import type { ActionMenuElement } from './action-menu-element';
2
+ export declare class ActionBarElement extends HTMLElement {
3
+ #private;
4
+ items: HTMLElement[];
5
+ menuItems: HTMLElement[];
6
+ moreMenu: ActionMenuElement;
7
+ connectedCallback(): void;
8
+ disconnectedCallback(): void;
9
+ }
10
+ declare global {
11
+ interface Window {
12
+ ActionBarElement: typeof ActionBarElement;
13
+ }
14
+ }