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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +2 -2
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/assets/styles/primer_view_components.css +1 -0
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/dialog/body.rb +25 -0
- data/app/components/primer/alpha/dialog/footer.rb +31 -0
- data/app/components/primer/alpha/dialog/header.html.erb +15 -0
- data/app/components/primer/alpha/dialog/header.rb +37 -0
- data/app/components/primer/alpha/dialog.html.erb +12 -0
- data/app/components/primer/alpha/dialog.rb +160 -0
- data/app/components/primer/alpha/modal-dialog-element.d.ts +1 -1
- data/app/components/primer/alpha/modal-dialog-element.js +2 -3
- data/app/components/primer/alpha/modal-dialog-element.ts +148 -0
- data/app/components/primer/alpha/toggle-switch-element.js +2 -0
- data/app/components/primer/alpha/toggle-switch-element.ts +2 -1
- data/app/components/primer/alpha/tool-tip-element.ts +0 -1
- data/app/components/primer/beta/button.html.erb +23 -0
- data/app/components/primer/beta/button.pcss +332 -0
- data/app/components/primer/beta/button.rb +189 -0
- data/app/components/primer/beta/icon_button.html.erb +6 -0
- data/app/components/primer/beta/icon_button.rb +104 -0
- data/app/components/primer/clipboard_copy_component.ts +1 -1
- data/app/components/primer/experimental/action-bar-element.d.ts +14 -0
- data/app/components/primer/experimental/action-bar-element.js +139 -0
- data/app/components/primer/experimental/action-menu-element.d.ts +31 -0
- data/app/components/primer/experimental/action-menu-element.js +334 -0
- data/app/components/primer/experimental/overflow-menu-element.d.ts +13 -0
- data/app/components/primer/experimental/overflow-menu-element.js +113 -0
- data/app/components/primer/primer.d.ts +1 -0
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.pcss +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/lib/postcss_mixins/focusBoxShadowInset.pcss +6 -0
- data/lib/postcss_mixins/focusOutline.pcss +5 -0
- data/lib/postcss_mixins/focusOutlineOnEmphasis.pcss +6 -0
- data/lib/postcss_mixins/minTouchTarget.js +20 -0
- data/lib/postcss_mixins/targetBoxShadow.pcss +6 -0
- data/lib/primer/view_components/linters/argument_mappers/base.rb +1 -1
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/tasks/docs.rake +3 -8
- data/static/arguments.yml +113 -0
- data/static/audited_at.json +6 -0
- data/static/constants.json +107 -0
- data/static/statuses.json +6 -0
- metadata +25 -5
- data/app/components/primer/alpha/segmented-control-element.d.ts +0 -8
- data/app/components/primer/alpha/segmented-control-element.js +0 -28
- 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
|
+
}
|