primer_view_components 0.0.92 → 0.0.93

Sign up to get free protection for your applications and to get access to all the features.
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
+ }