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.
- 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
|
+
}
|