@primer/css 20.2.4-rc.ca62d1cf → 20.2.4

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.
@@ -1,27 +1,23 @@
1
- // stylelint-disable primer/typography, primer/borders, primer/spacing, primer/box-shadow, max-nesting-depth
1
+ // stylelint-disable primer/typography, primer/borders, primer/spacing, selector-max-type, selector-max-specificity, selector-no-qualifying-type, max-nesting-depth
2
2
 
3
- // group label, field, caption and error message
4
- .FormGroup {
3
+ // groups label, field, caption and inline error message
4
+ .FormControl {
5
5
  display: inline-flex;
6
6
  flex-direction: column;
7
7
  gap: var(--base-size-4, 4px);
8
8
  }
9
9
 
10
10
  // fill container
11
- .FormGroup--fullWidth {
11
+ .FormControl--fullWidth {
12
12
  display: flex;
13
-
14
- // stretch field to fill container
15
- .FormControl-fieldWrap {
16
- flex-grow: 1;
17
- }
18
13
  }
19
14
 
20
15
  // <label>
21
16
  .FormControl-label {
22
17
  font-size: var(--primer-text-body-size-medium, 14px);
23
18
  font-weight: var(--base-text-weight-semibold, 600);
24
- line-height: var(--primer-text-body-lineHeight-medium, 20px);
19
+ line-height: var(--primer-text-body-lineHeight-medium, calc(20 / 14));
20
+ color: var(--color-fg-default);
25
21
  user-select: none;
26
22
  }
27
23
 
@@ -30,11 +26,12 @@
30
26
  margin-bottom: 0;
31
27
  font-size: var(--primer-text-caption-size, 12px);
32
28
  font-weight: var(--primer-text-caption-weight, 400);
33
- line-height: var(--primer-text-caption-lineHeight, 16px);
29
+ line-height: var(--primer-text-caption-lineHeight, calc(16 / 12));
34
30
  color: var(--color-fg-muted);
35
31
  }
36
32
 
37
- .FormControl-validation {
33
+ // inline validation message
34
+ .FormControl-inlineValidation {
38
35
  display: flex;
39
36
  font-size: var(--primer-text-caption-size, 12px);
40
37
  font-weight: var(--base-text-weight-semibold, 600);
@@ -44,339 +41,636 @@
44
41
  align-items: center;
45
42
  gap: var(--base-size-4, 4px);
46
43
 
47
- // stylelint-disable-next-line selector-max-type
48
44
  p {
49
45
  margin-bottom: 0;
50
46
  }
51
47
  }
52
48
 
53
49
  // shared among all form control components (input, select, textarea, checkbox, radio)
54
- .FormControl {
50
+ @mixin Field {
55
51
  background-color: var(--color-canvas-default);
56
52
  border: solid var(--primer-borderWidth-thin, 1px) var(--color-border-default);
57
53
 
58
54
  &[disabled] {
59
55
  color: var(--color-primer-fg-disabled);
56
+ cursor: not-allowed;
60
57
  background-color: var(--color-input-disabled-bg);
61
58
  border-color: var(--color-border-default);
62
- -webkit-text-fill-color: var(--color-primer-fg-disabled);
63
59
  opacity: 1;
60
+ -webkit-text-fill-color: var(--color-primer-fg-disabled);
64
61
  }
65
62
 
66
63
  &:not(:focus)[invalid] {
67
64
  border-color: var(--color-danger-emphasis);
68
65
  }
69
66
 
70
- // <select> and <input>
71
- &.FormControl--input,
72
- &.FormControl--select {
73
- height: var(--primer-control-medium-size, 32px);
74
- font-size: var(--primer-text-body-size-medium, 14px);
75
- line-height: var(--primer-text-body-lineHeight-medium, 20px);
76
- border-radius: var(--primer-borderRadius-medium, 6px);
77
- padding-inline: var(--primer-control-medium-paddingInline-condensed, 8px);
78
- transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
79
- transition-property: color, background-color, box-shadow, border-color;
67
+ &:not([type='checkbox']):not([type='radio']):focus {
68
+ @include focusBoxShadowInset;
80
69
 
81
- &[disabled] {
82
- &::placeholder {
83
- color: var(--color-primer-fg-disabled);
84
- }
70
+ // remove fallback :focus if :focus-visible is supported
71
+ &:not(:focus-visible) {
72
+ border-color: transparent;
73
+
74
+ @include focusBoxShadowInset(1px, transparent);
85
75
  }
76
+ }
86
77
 
87
- ::placeholder {
88
- color: var(--color-fg-subtle);
89
- opacity: 1;
78
+ // default focus state
79
+ &:focus-visible {
80
+ @include focusBoxShadowInset;
81
+ }
82
+ }
83
+
84
+ // TextInput structure
85
+ // ===================
86
+ //
87
+ // .FormControl
88
+ // ├─ .FormControl-label
89
+ // │ ├─ .FormControl-input-wrap
90
+ // │ │ ├─ .FormControl-input-leadingVisualWrap
91
+ // │ │ │ ├─ .FormControl-input-leadingVisual
92
+ // │ │ ├─ .FormControl-input
93
+ // │ │ ├─ .FormControl-input-trailingAction
94
+ // ├─ .FormControl-inlineValidation
95
+ // ├─ .FormControl-caption
96
+
97
+ // Select structure
98
+ // ===================
99
+ //
100
+ // .FormControl
101
+ // ├─ .FormControl-label
102
+ // │ ├─ .FormControl-select-wrap
103
+ // │ │ ├─ .FormControl-select
104
+ // ├─ .FormControl-inlineValidation
105
+ // ├─ .FormControl-caption
106
+
107
+ // Textarea structure
108
+ // ===================
109
+ //
110
+ // .FormControl
111
+ // ├─ .FormControl-label
112
+ // ├─ .FormControl-textarea
113
+ // ├─ .FormControl-inlineValidation
114
+ // ├─ .FormControl-caption
115
+
116
+ .FormControl-input,
117
+ .FormControl-select,
118
+ .FormControl-textarea {
119
+ @include Field;
120
+
121
+ width: 100%;
122
+ font-size: var(--primer-text-body-size-medium, 14px);
123
+ line-height: var(--primer-text-body-lineHeight-medium, calc(20 / 14));
124
+ border-radius: var(--primer-borderRadius-medium, 6px);
125
+ transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
126
+ transition-property: color, background-color, box-shadow, border-color;
127
+ padding-inline: var(--primer-control-medium-paddingInline-condensed, 8px);
128
+ padding-block: calc(var(--primer-control-medium-paddingBlock, 6px) - var(--primer-borderWidth-thin, 1px));
129
+
130
+ &[disabled] {
131
+ &::placeholder {
132
+ color: var(--color-primer-fg-disabled);
90
133
  }
91
134
  }
92
135
 
136
+ ::placeholder {
137
+ color: var(--color-fg-subtle);
138
+ opacity: 1;
139
+ }
140
+
93
141
  // sizes
94
142
 
95
- &.FormControl--small {
143
+ &.FormControl-small {
96
144
  height: var(--primer-control-small-size, 28px);
145
+ padding-inline: var(--primer-control-small-paddingInline-normal, 8px);
146
+ padding-block: var(--primer-control-small-paddingBlock, 4px);
97
147
  font-size: var(--primer-text-body-size-small, 12px);
98
148
  }
99
149
 
100
- &.FormControl--medium {
150
+ &.FormControl-medium {
101
151
  height: var(--primer-control-medium-size, 32px);
102
152
  }
103
153
 
104
- &.FormControl--large {
154
+ &.FormControl-large {
105
155
  height: var(--primer-control-large-size, 40px);
106
156
  padding-inline: var(--primer-control-large-paddingInline-normal, 12px);
157
+ padding-block: var(--primer-control-large-paddingBlock, 10px);
107
158
  }
108
- }
109
159
 
110
- // pseudo input styles to allow for visual and action slots
111
- // set input styles on -fieldWrap, remove styles from <input>
112
- .FormControl-fieldWrap {
113
- display: inline-grid;
114
- height: var(--primer-control-medium-size, 32px);
115
- background-color: var(--color-canvas-default);
116
- border-radius: var(--primer-borderRadius-medium, 6px);
117
- box-shadow: var(--primer-borderInset-thin, inset 0 0 0 max(1px, 0.0625rem)) var(--color-border-default);
118
- grid-template-rows: auto;
119
- gap: var(--primer-controlStack-medium-gap-condensed, 8px);
120
- align-items: center;
121
- align-content: center;
122
- padding-inline: var(--primer-control-medium-paddingInline-condensed, 8px);
160
+ // variants
123
161
 
124
- .FormControl {
125
- padding: unset;
126
- background-color: transparent;
127
- border: 0;
162
+ &.FormControl-inset {
163
+ background-color: var(--color-canvas-inset);
164
+ }
128
165
 
129
- &:focus-visible,
130
- &:focus {
131
- outline: none;
132
- box-shadow: none;
133
- }
166
+ &.FormControl-monospace {
167
+ font-family:
168
+ var(
169
+ --primer-fontStack-monospace,
170
+ 'ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace'
171
+ );
134
172
  }
135
173
 
136
- &:focus-within {
137
- @include focusBoxShadowInset(2px, var(--color-accent-fg));
174
+ // validation states
175
+
176
+ &.FormControl-error {
177
+ border-color: var(--color-danger-emphasis);
138
178
  }
139
179
 
140
- &.FormControl-fieldWrap--disabled {
141
- color: var(--color-primer-fg-disabled);
142
- background-color: var(--color-input-disabled-bg);
180
+ &.FormControl-success {
181
+ border-color: var(--color-success-emphasis);
143
182
  }
144
183
 
145
- &.FormControl-fieldWrap--invalid:not(:focus-within) {
146
- box-shadow: var(--primer-borderInset-thin, inset 0 0 0 max(1px, 0.0625rem)) var(--color-danger-emphasis);
184
+ &.FormControl-warning {
185
+ border-color: var(--color-attention-emphasis);
147
186
  }
187
+ }
148
188
 
149
- // if leadingVisual is present
150
- &.FormControl-fieldWrap--input-leadingVisual {
151
- grid-template-columns: var(--base-size-16, 16px) minmax(0, auto);
189
+ // positioning for leading/trailing items for TextInput
190
+ .FormControl-input-wrap {
191
+ position: relative;
192
+ display: grid;
193
+
194
+ .FormControl-input-leadingVisualWrap {
195
+ position: absolute;
196
+ top: var(--base-size-8, 8px);
197
+ left: var(--base-size-8, 8px);
198
+ display: block;
199
+ width: var(--base-size-16, 16px);
200
+ height: var(--base-size-16, 16px);
201
+ color: var(--color-fg-muted);
202
+ pointer-events: none;
203
+
204
+ // octicon
205
+ .FormControl-input-leadingVisual {
206
+ display: block;
207
+ user-select: none;
208
+ }
209
+ }
152
210
 
153
- .FormControl--input {
154
- padding-inline-start: calc(var(--base-size-16, 16px) + var(--primer-control-medium-paddingInline-condensed, 8px));
211
+ // TODO: replace with new Button component
212
+ .FormControl-input-trailingAction {
213
+ position: absolute;
214
+ top: var(--base-size-4, 4px);
215
+ right: var(--base-size-4, 4px);
216
+ z-index: 4;
217
+ display: grid;
218
+ width: var(--primer-control-xsmall-size, 24px);
219
+ height: var(--primer-control-xsmall-size, 24px);
220
+ padding: 0;
221
+ color: var(--color-fg-muted);
222
+ cursor: pointer;
223
+ background: transparent;
224
+ border: 0;
225
+ border-radius: var(--primer-borderRadius-small);
226
+ transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1);
227
+ transition-property: color, background-color, border-color;
228
+ align-items: center;
229
+ justify-content: center;
230
+
231
+ svg {
232
+ user-select: none;
155
233
  }
156
234
 
157
- // if leadingVisual and trailingAction are present
158
- &.FormControl-fieldWrap--input-trailingAction {
159
- grid-template-columns: var(--base-size-16, 16px) minmax(0, auto) min-content;
235
+ &[disabled] {
236
+ color: var(--color-primer-fg-disabled);
237
+ pointer-events: none;
238
+ }
239
+
240
+ &:hover {
241
+ background: var(--color-action-list-item-default-hover-bg);
242
+ }
160
243
 
161
- .FormControl--input-trailingAction {
162
- grid-column: 3 / 4;
244
+ &:active {
245
+ background: var(--color-action-list-item-default-active-bg);
246
+ }
247
+
248
+ // show vertical divider line between field and button
249
+ &.FormControl-input-trailingAction--divider {
250
+ &::before {
251
+ position: absolute;
252
+ top: calc((var(--primer-control-xsmall-size) - var(--base-size-16)) / 2);
253
+ left: calc(var(--base-size-4, 4px) * -1);
254
+ display: block;
255
+ width: var(--primer-borderWidth-thin);
256
+ height: var(--base-size-16);
257
+ content: '';
258
+ background: var(--color-border-default);
259
+ }
260
+ }
261
+
262
+ &::after {
263
+ @include minTouchTarget(var(--primer-control-medium-size, 32px), var(--primer-control-medium-size, 32px));
264
+
265
+ @media (pointer: coarse) {
266
+ min-width: var(--primer-control-minTarget-coarse, 44px);
267
+ min-height: var(--primer-control-minTarget-coarse, 44px);
163
268
  }
164
269
  }
165
270
  }
166
271
 
167
- // if trailingAction is present
168
- &.FormControl-fieldWrap--input-trailingAction {
169
- grid-template-columns: minmax(0, auto) min-content;
272
+ // if leadingVisual is present
273
+
274
+ /*
275
+ ┌──32px──┬────────────────────┐
276
+ ╎ ┌───┐ ┌────────────────┐ ╎
277
+ ╎ 16px 16px ╎
278
+ ╎ └───┘ └────────────────┘ ╎
279
+ └───────8px───────────────────┘
280
+ */
281
+ &.FormControl-input-wrap--leadingVisual {
282
+ .FormControl-input {
283
+ padding-inline-start:
284
+ calc(
285
+ var(--primer-control-medium-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
286
+ var(--primer-control-medium-gap, 8px)
287
+ ); /* 32px */
288
+ }
170
289
  }
171
290
 
172
- &.FormControl-fieldWrap--input {
173
- grid-template-rows: max-content;
291
+ /*
292
+ ┌──────────────────┬──32px──┐
293
+ ╎ ┌──────────────┐ ┌────┐ ╎
294
+ ╎ 24px 24px ╎
295
+ ╎ └──────────────┘ └────┘ ╎
296
+ └──────────────────┴────────┘
297
+ */
298
+ // if trailingAction is present
299
+ &.FormControl-input-wrap--trailingAction {
300
+ .FormControl-input {
301
+ padding-inline-end:
302
+ calc(
303
+ var(--primer-control-medium-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
304
+ var(--primer-control-medium-gap, 8px)
305
+ ); /* 32px */
306
+ }
174
307
 
175
- .FormControl--input-leadingVisual {
176
- grid-column: 1 / 2;
177
- grid-row: 1;
308
+ /*
309
+ 32px + 1px border
310
+ ┌──────────────────┬──33px──┐
311
+ ╎ ┌──────────────┐ ┌────┐ ╎
312
+ ╎ 24px 24px ╎
313
+ ╎ └──────────────┘ └────┘ ╎
314
+ └──────────────────┴────────┘
315
+ */
316
+ // if trailingAction divider is present, add 1px padding to accomodate input field text
317
+ // can be refactored to has(.FormControl-input-trailingAction--divider)
318
+ &.FormControl-input-wrap-trailingAction--divider {
319
+ .FormControl-input {
320
+ padding-inline-end:
321
+ calc(
322
+ var(--primer-control-medium-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
323
+ var(--primer-control-medium-gap, 8px) + var(--primer-borderWidth-thin, 1px)
324
+ ); /* 33px */
325
+ }
178
326
  }
327
+ }
179
328
 
180
- // <input> spans entire grid
181
- .FormControl--input {
182
- grid-column: 1 / -1;
183
- grid-row: 1;
329
+ // size modifications can be refactored with :has() - FormControl-input-wrap:has(.FormControl-large)
330
+ // sizes
331
+ &.FormControl-small {
332
+ .FormControl-input-leadingVisualWrap {
333
+ top: calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem); /* 6px */
334
+ left: calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem); /* 6px */
184
335
  }
185
336
 
186
- // TODO: replace with new Button component
187
- // trailingAction will auto fill last grid slot
188
- .FormControl--input-trailingAction {
189
- display: grid;
190
- width: calc(var(--primer-control-medium-size, 32px) - var(--base-size-4, 4px));
191
- height: calc(var(--primer-control-medium-size, 32px) - var(--base-size-4, 4px));
192
- padding: 0;
193
- // optically align the icon with field padding
194
- margin-right: calc(calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem) * -1);
195
- color: var(--color-fg-muted);
196
- cursor: pointer;
197
- user-select: none;
198
- background-color: transparent;
199
- border: solid var(--primer-borderWidth-thin, 1px) transparent;
200
- border-radius: var(--primer-borderRadius-medium, 6px);
201
- transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1);
202
- transition-property: color, background-color, border-color;
203
- grid-row: 1;
204
- place-content: center;
205
- place-self: center end;
206
- grid-column: 2 / 3;
207
-
208
- &:hover {
209
- background-color: var(--color-btn-hover-bg);
210
- border: solid var(--primer-borderWidth-thin, 1px) var(--color-btn-hover-bg);
337
+ /*
338
+ ┌──────────────────┬──28px──┐
339
+ ╎ ┌──────────────┐ ┌────┐
340
+ ╎ 20px 20px ╎
341
+ ╎ └──────────────┘ └────┘
342
+ └──────────────────┴────────┘
343
+ */
344
+ &.FormControl-input-wrap--trailingAction {
345
+ .FormControl-input.FormControl-small {
346
+ padding-inline-end:
347
+ calc(
348
+ var(--primer-control-small-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
349
+ var(--primer-control-small-gap, 8px)
350
+ ); /* 28px */
211
351
  }
212
352
 
213
- &[disabled] {
214
- color: var(--color-primer-fg-disabled);
215
- pointer-events: none;
353
+ /*
354
+ 28px + 1px border
355
+ ┌──────────────────┬──29px──┐
356
+ ╎ ┌──────────────┐ ┌────┐ ╎
357
+ ╎ 20px 20px ╎
358
+ ╎ └──────────────┘ └────┘ ╎
359
+ └──────────────────┴────────┘
360
+ */
361
+ &.FormControl-input-wrap-trailingAction--divider {
362
+ .FormControl-input.FormControl-small {
363
+ padding-inline-end:
364
+ calc(
365
+ var(--primer-control-small-paddingInline-condensed, 8px) + var(--base-size-16, 16px) +
366
+ var(--primer-control-small-gap, 8px) + var(--primer-borderWidth-thin, 1px)
367
+ ); /* 29px */
368
+ }
216
369
  }
217
370
  }
218
371
 
219
- // sizes
372
+ .FormControl-input-trailingAction {
373
+ width: calc(var(--primer-control-small-size, 28px) - var(--base-size-8, 8px));
374
+ height: calc(var(--primer-control-small-size, 28px) - var(--base-size-8, 8px));
220
375
 
221
- // these selectors can be refactored to use :has()
222
- &.FormControl--small {
223
- height: var(--primer-control-small-size, 28px);
224
- font-size: var(--primer-text-body-size-small, 12px);
376
+ &::before {
377
+ top: calc((var(--primer-control-xsmall-size) - var(--base-size-16)) / 4); /* 2px */
378
+ }
379
+ }
380
+ }
225
381
 
226
- .FormControl--input-trailingAction {
227
- width: calc(var(--primer-control-small-size, 28px) - var(--base-size-8, 8px));
228
- height: calc(var(--primer-control-small-size, 28px) - var(--base-size-8, 8px));
382
+ &.FormControl-large {
383
+ .FormControl-input-leadingVisualWrap {
384
+ top: var(--primer-control-medium-paddingInline-normal, 12px);
385
+ left: var(--primer-control-medium-paddingInline-normal, 12px);
386
+ }
387
+
388
+ /*
389
+ ┌──36px──┬───12px padding──────┐
390
+ ╎ ┌───┐ ┌────────────────┐ ╎
391
+ ╎ 16px 16px ╎
392
+ ╎ └───┘ └────────────────┘ ╎
393
+ └12px───8px───────────────────┘
394
+ */
395
+ &.FormControl-input-wrap--leadingVisual {
396
+ .FormControl-input.FormControl-large {
397
+ padding-inline-start:
398
+ calc(
399
+ var(--primer-control-large-paddingInline-normal, 12px) + var(--base-size-16, 16px) +
400
+ var(--primer-control-large-gap, 8px)
401
+ ); /* 36px */
229
402
  }
230
403
  }
231
404
 
232
- &.FormControl--medium {
233
- height: var(--primer-control-medium-size, 32px);
405
+ /*
406
+ ┌──────────────────┬──36px──┐
407
+ ╎ ┌──────────────┐ ┌────┐ ╎
408
+ ╎ 28px 28px ╎
409
+ ╎ └──────────────┘ └────┘ ╎
410
+ └──────────────────┴────────┘
411
+ */
412
+ &.FormControl-input-wrap--trailingAction {
413
+ .FormControl-input.FormControl-large {
414
+ padding-inline-end:
415
+ calc(
416
+ var(--primer-control-large-paddingInline-normal, 12px) + var(--base-size-16, 16px) +
417
+ var(--primer-control-large-gap, 8px)
418
+ ); /* 36px */
419
+ }
234
420
 
235
- .FormControl--input-trailingAction {
236
- width: calc(var(--primer-control-medium-size, 32px) - var(--base-size-8, 8px));
237
- height: calc(var(--primer-control-medium-size, 32px) - var(--base-size-8, 8px));
421
+ /*
422
+ ┌──────────────────┬──37px──┐
423
+ ╎ ┌──────────────┐ ┌────┐
424
+ ╎ 28px 28px ╎
425
+ ╎ └──────────────┘ └────┘ ╎
426
+ └──────────────────┴────────┘
427
+ */
428
+ &.FormControl-input-wrap-trailingAction--divider {
429
+ .FormControl-input.FormControl-large {
430
+ padding-inline-end:
431
+ calc(
432
+ var(--primer-control-large-paddingInline-normal, 12px) + var(--base-size-16, 16px) +
433
+ var(--primer-control-large-gap, 8px) + var(--primer-borderWidth-thin, 1px)
434
+ ); /* 37px */
435
+ }
238
436
  }
239
437
  }
240
438
 
241
- &.FormControl--large {
242
- height: var(--primer-control-large-size, 40px);
243
- padding-inline: var(--primer-control-large-paddingInline-normal, 12px);
439
+ .FormControl-input-trailingAction {
440
+ top: calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem); /* 6px */
441
+ right: calc(var(--primer-control-medium-paddingInline-condensed, 8px) - 0.125rem); /* 6px */
442
+ width: var(--primer-control-small-size, 28px);
443
+ height: var(--primer-control-small-size, 28px);
244
444
 
245
- .FormControl--input-trailingAction {
246
- width: calc(var(--primer-control-large-size, 40px) - var(--base-size-8, 8px));
247
- height: calc(var(--primer-control-large-size, 40px) - var(--base-size-8, 8px));
248
- margin-right: calc(calc(var(--primer-control-medium-paddingInline-normal, 12px) - 0.125rem) * -1);
445
+ &::before {
446
+ top: unset;
447
+ height: var(--base-size-20);
249
448
  }
250
449
  }
251
450
  }
252
451
  }
253
452
 
254
- .FormControl-fieldWrap--select {
453
+ .FormControl-select-wrap {
454
+ display: grid;
255
455
  grid-template-columns: minmax(0, auto) var(--base-size-16, 16px);
256
- padding-inline-end: var(--primer-control-medium-paddingInline-condensed, 8px);
257
456
 
258
457
  // mask allows for background-color to respect themes
259
458
  &::after {
260
459
  width: var(--base-size-16, 16px);
261
460
  height: var(--base-size-16, 16px);
461
+ padding-right: var(--base-size-4, 4px);
262
462
  pointer-events: none;
263
463
  content: '';
264
464
  background-color: var(--color-fg-muted);
265
465
  mask: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzU4NjA2OSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNC40MjcgOS40MjdsMy4zOTYgMy4zOTZhLjI1MS4yNTEgMCAwMC4zNTQgMGwzLjM5Ni0zLjM5NkEuMjUuMjUgMCAwMDExLjM5NiA5SDQuNjA0YS4yNS4yNSAwIDAwLS4xNzcuNDI3ek00LjQyMyA2LjQ3TDcuODIgMy4wNzJhLjI1LjI1IDAgMDEuMzU0IDBMMTEuNTcgNi40N2EuMjUuMjUgMCAwMS0uMTc3LjQyN0g0LjZhLjI1LjI1IDAgMDEtLjE3Ny0uNDI3eiIgLz48L3N2Zz4=');
266
- mask-size: cover;
466
+ mask-size: contain;
467
+ mask-repeat: no-repeat;
267
468
  grid-column: 2;
268
469
  grid-row: 1;
269
470
  place-self: center end;
270
471
  }
271
472
 
272
473
  // spans entire grid below mask
273
- .FormControl {
474
+ .FormControl-select {
274
475
  grid-column: 1/-1;
275
476
  grid-row: 1;
276
477
  appearance: none;
277
- padding-right: var(--base-size-16, 16px);
478
+ padding-right: var(--base-size-20, 20px);
278
479
  }
279
480
  }
280
481
 
281
- .FormGroup--checkbox {
482
+ // checkbox + radio specific styles
483
+
484
+ // Checkbox + Radio structure
485
+ // ===================
486
+ //
487
+ // .FormControl-radio-wrap
488
+ // ├─ .FormControl-radio
489
+ // ├─ .FormControl-radio-labelWrap
490
+ // │ ├─ .FormControl-label
491
+ // │ ├─ .FormControl-caption
492
+
493
+ .FormControl-checkbox-wrap,
494
+ .FormControl-radio-wrap {
282
495
  display: inline-grid;
283
- grid-template-areas: 'field label' '. caption';
496
+ grid-template-columns: min-content auto;
284
497
  gap: var(--base-size-8, 8px);
285
498
 
286
- .FormControl-label {
287
- grid-area: label;
499
+ .FormControl-checkbox-labelWrap,
500
+ .FormControl-radio-labelWrap {
501
+ display: flex;
502
+ flex-direction: column;
503
+ gap: var(--base-size-4, 4px);
288
504
  }
289
505
 
290
- .FormControl-caption {
291
- grid-area: caption;
506
+ .FormControl-label {
507
+ cursor: pointer;
292
508
  }
293
509
  }
294
510
 
295
- // checkbox + radio specific styles
511
+ // these selectors are temporary to override base.scss
512
+ // once Field styles are widely adopted, we can adjust this and the global base styles
513
+ input[type='checkbox'].FormControl-checkbox,
514
+ input[type='radio'].FormControl-radio {
515
+ @include Field;
296
516
 
297
- .FormControl--checkbox,
298
- .FormControl--radio {
299
- position: absolute;
517
+ position: relative;
518
+ display: grid;
300
519
  width: var(--base-size-16, 16px);
301
520
  height: var(--base-size-16, 16px);
302
- opacity: 0;
521
+ margin: 0;
522
+ margin-top: 0.125rem; // 2px to center align with label (20px line-height)
523
+ cursor: pointer;
524
+ border: solid var(--primer-borderWidth-thin, 1px) var(--color-border-default);
525
+ border-radius: var(--primer-borderRadius-small, 3px);
526
+ transition: background-color, border-color 80ms cubic-bezier(0.33, 1, 0.68, 1); // checked -> unchecked - add 120ms delay to fully see animation-out
527
+ appearance: none;
528
+ place-content: center;
303
529
 
304
- &:checked {
305
- + .FormControl--checkbox-svg {
306
- .FormControl--checkbox-background {
307
- fill: var(--color-accent-fg);
308
- stroke: var(--color-accent-fg);
309
- }
530
+ &::before {
531
+ width: var(--base-size-16, 16px);
532
+ height: var(--base-size-16, 16px);
533
+ visibility: hidden;
534
+ content: '';
535
+ background-color: var(--color-fg-on-emphasis);
536
+ transition: visibility 0s linear 230ms;
537
+ clip-path: inset(var(--base-size-16, 16px) 0 0 0);
538
+ mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOSIgdmlld0JveD0iMCAwIDEyIDkiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTEuNzgwMyAwLjIxOTYyNUMxMS45MjEgMC4zNjA0MjcgMTIgMC41NTEzMDUgMTIgMC43NTAzMTNDMTIgMC45NDkzMjEgMTEuOTIxIDEuMTQwMTkgMTEuNzgwMyAxLjI4MUw0LjUxODYgOC41NDA0MkM0LjM3Nzc1IDguNjgxIDQuMTg2ODIgOC43NiAzLjk4Nzc0IDguNzZDMy43ODg2NyA4Ljc2IDMuNTk3NzMgOC42ODEgMy40NTY4OSA4LjU0MDQyTDAuMjAxNjIyIDUuMjg2MkMwLjA2ODkyNzcgNS4xNDM4MyAtMC4wMDMzMDkwNSA0Ljk1NTU1IDAuMDAwMTE2NDkzIDQuNzYwOThDMC4wMDM1NTIwNSA0LjU2NjQzIDAuMDgyMzg5NCA0LjM4MDgxIDAuMjIwMDMyIDQuMjQzMjFDMC4zNTc2NjUgNC4xMDU2MiAwLjU0MzM1NSA0LjAyNjgxIDAuNzM3OTcgNC4wMjMzOEMwLjkzMjU4NCA0LjAxOTk0IDEuMTIwOTMgNC4wOTIxNyAxLjI2MzM0IDQuMjI0ODJMMy45ODc3NCA2Ljk0ODM1TDEwLjcxODYgMC4yMTk2MjVDMTAuODU5NSAwLjA3ODk5MjMgMTEuMDUwNCAwIDExLjI0OTUgMEMxMS40NDg1IDAgMTEuNjM5NSAwLjA3ODk5MjMgMTEuNzgwMyAwLjIxOTYyNVoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo='); // octicon checkmark image
539
+ mask-size: 75%;
540
+ mask-repeat: no-repeat;
541
+ mask-position: center;
310
542
 
311
- .FormControl--checkbox-check {
312
- fill: var(--color-fg-on-emphasis);
313
- visibility: visible;
543
+ @media screen and (prefers-reduced-motion: no-preference) {
544
+ animation: checkmarkOut 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards; // slightly snappier animation out
545
+ }
546
+ }
314
547
 
315
- @media screen and (prefers-reduced-motion: no-preference) {
316
- animation: checkmarkIn 200ms cubic-bezier(0.11, 0, 0.5, 0) forwards;
317
- }
548
+ // extend touch target
549
+ &::after {
550
+ @include minTouchTarget(var(--primer-control-medium-size, 32px), var(--primer-control-medium-size, 32px));
551
+ }
552
+
553
+ &[disabled] {
554
+ ~ .FormControl-checkbox-labelWrap,
555
+ ~ .FormControl-radio-labelWrap {
556
+ .FormControl-label {
557
+ color: var(--color-primer-fg-disabled);
558
+ cursor: not-allowed;
318
559
  }
319
560
  }
320
561
  }
321
562
 
322
- &:indeterminate {
323
- + .FormControl--checkbox-svg {
324
- .FormControl--checkbox-background {
325
- fill: var(--color-accent-fg);
326
- stroke: var(--color-accent-fg);
563
+ &:checked {
564
+ background: var(--color-accent-fg);
565
+ border-color: var(--color-accent-fg);
566
+ transition: background-color, border-color 80ms cubic-bezier(0.32, 0, 0.67, 0) 0ms; // unchecked -> checked
567
+
568
+ &::before {
569
+ visibility: visible;
570
+ transition: visibility 0s linear 0s;
571
+
572
+ @media screen and (prefers-reduced-motion: no-preference) {
573
+ animation: checkmarkIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;
327
574
  }
575
+ }
576
+
577
+ &:disabled {
578
+ cursor: not-allowed;
579
+ background-color: var(--color-primer-fg-disabled);
580
+ border-color: var(--color-primer-fg-disabled);
581
+ opacity: 1;
328
582
 
329
- .FormControl--checkbox-indeterminate {
330
- fill: var(--color-fg-on-emphasis);
331
- visibility: visible;
583
+ &::before {
584
+ background-color: var(--color-fg-on-emphasis);
332
585
  }
333
586
  }
587
+
588
+ // Windows High Contrast mode
589
+ // stylelint-disable primer/colors
590
+ @media (forced-colors: active) {
591
+ background-color: CanvasText;
592
+ border-color: CanvasText;
593
+ }
594
+ // stylelint-enable primer/colors
334
595
  }
335
596
 
336
- @keyframes checkmarkIn {
337
- from {
338
- clip-path: inset(16px 0 0 0);
597
+ &:focus,
598
+ &:focus-visible {
599
+ outline-offset: 2px;
600
+ }
601
+
602
+ &:indeterminate {
603
+ &::before {
604
+ mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMiIgdmlld0JveD0iMCAwIDEwIDIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMCAxQzAgMC40NDc3MTUgMC40NDc3MTUgMCAxIDBIOUM5LjU1MjI5IDAgMTAgMC40NDc3MTUgMTAgMUMxMCAxLjU1MjI4IDkuNTUyMjkgMiA5IDJIMUMwLjQ0NzcxNSAyIDAgMS41NTIyOCAwIDFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');
605
+ visibility: visible;
339
606
  }
607
+ }
608
+ }
609
+
610
+ input[type='radio'].FormControl-radio {
611
+ border-radius: var(--primer-borderRadius-full, 100vh);
612
+
613
+ &::before {
614
+ clip-path: circle(0%);
615
+ mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUgOS4zNzVDNy40MTYyMiA5LjM3NSA5LjM3NSA3LjQxNjIyIDkuMzc1IDVDOS4zNzUgMi41ODM3NiA3LjQxNjIyIDAuNjI1IDUgMC42MjVDMi41ODM3NiAwLjYyNSAwLjYyNSAyLjU4Mzc2IDAuNjI1IDVDMC42MjUgNy40MTYyMiAyLjU4Mzc2IDkuMzc1IDUgOS4zNzVaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'); // checked circle image
616
+ mask-size: 65%;
340
617
 
341
- to {
342
- clip-path: inset(0 0 0 0);
618
+ @media screen and (prefers-reduced-motion: no-preference) {
619
+ animation: radioOut 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards; // slightly snappier animation out
343
620
  }
344
621
  }
345
622
 
346
- @keyframes checkmarkOut {
347
- from {
348
- clip-path: inset(0 0 0 0);
623
+ &:checked {
624
+ &::before {
625
+ @media screen and (prefers-reduced-motion: no-preference) {
626
+ animation: radioIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;
627
+ }
349
628
  }
629
+ }
350
630
 
351
- to {
352
- clip-path: inset(16px 0 0 0);
631
+ &:indeterminate {
632
+ &::before {
633
+ visibility: hidden;
353
634
  }
354
635
  }
355
636
  }
356
637
 
357
- .FormControl--checkbox-svg {
358
- width: var(--base-size-16, 16px);
359
- height: var(--base-size-16, 16px);
360
- margin-top: 0.125rem;
361
- cursor: pointer;
638
+ @keyframes checkmarkIn {
639
+ from {
640
+ clip-path: inset(var(--base-size-16, 16px) 0 0 0);
641
+ }
362
642
 
363
- .FormControl--checkbox-background {
364
- fill: var(--color-canvas-default);
365
- stroke: var(--color-border-default);
643
+ to {
644
+ clip-path: inset(0 0 0 0);
366
645
  }
646
+ }
367
647
 
368
- .FormControl--checkbox-check {
369
- visibility: hidden;
370
- transition: visibility 0s linear 200ms;
371
- clip-path: inset(16px 0 0 0);
648
+ @keyframes checkmarkOut {
649
+ from {
650
+ clip-path: inset(0 0 0 0);
651
+ }
372
652
 
373
- @media screen and (prefers-reduced-motion: no-preference) {
374
- animation: checkmarkOut 200ms cubic-bezier(0.11, 0, 0.5, 0) forwards;
375
- }
653
+ to {
654
+ clip-path: inset(var(--base-size-16, 16px) 0 0 0);
376
655
  }
656
+ }
377
657
 
378
- .FormControl--checkbox-indeterminate {
379
- fill: transparent;
380
- visibility: hidden;
658
+ @keyframes radioIn {
659
+ from {
660
+ clip-path: circle(0%);
661
+ }
662
+
663
+ to {
664
+ clip-path: circle(100%);
665
+ }
666
+ }
667
+
668
+ @keyframes radioOut {
669
+ from {
670
+ clip-path: circle(100%);
671
+ }
672
+
673
+ to {
674
+ clip-path: circle(0%);
381
675
  }
382
676
  }