@slotted/styles 0.0.0-stage → 0.1.0-beta.0
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.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/package.json +276 -3
- package/src/accordion/accordion.css +96 -0
- package/src/accordion/accordion.tokens.json +14 -0
- package/src/action-bar/action-bar.css +455 -0
- package/src/action-bar/action-bar.tokens.json +50 -0
- package/src/action-sheet/action-sheet.css +462 -0
- package/src/action-sheet/action-sheet.tokens.json +59 -0
- package/src/alert/alert.css +764 -0
- package/src/alert/alert.tokens.json +77 -0
- package/src/app-shell/app-shell.css +1899 -0
- package/src/app-shell/app-shell.tokens.json +88 -0
- package/src/aspect-ratio/aspect-ratio.css +131 -0
- package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
- package/src/attachment/attachment.css +906 -0
- package/src/attachment/attachment.tokens.json +85 -0
- package/src/avatar/avatar.css +598 -0
- package/src/avatar/avatar.tokens.json +64 -0
- package/src/avatar-group/avatar-group.css +268 -0
- package/src/avatar-group/avatar-group.tokens.json +16 -0
- package/src/badge/badge.css +462 -0
- package/src/badge/badge.tokens.json +58 -0
- package/src/breadcrumb/breadcrumb.css +441 -0
- package/src/breadcrumb/breadcrumb.tokens.json +50 -0
- package/src/button/button.css +754 -0
- package/src/button/button.tokens.json +72 -0
- package/src/button-group/button-group.css +455 -0
- package/src/button-group/button-group.tokens.json +11 -0
- package/src/calendar/calendar.css +846 -0
- package/src/calendar/calendar.tokens.json +67 -0
- package/src/card/card.css +602 -0
- package/src/card/card.tokens.json +68 -0
- package/src/carousel/carousel.css +860 -0
- package/src/carousel/carousel.tokens.json +62 -0
- package/src/cascader/cascader.css +1320 -0
- package/src/cascader/cascader.tokens.json +103 -0
- package/src/chart/chart.css +1473 -0
- package/src/chart/chart.tokens.json +107 -0
- package/src/checkbox/checkbox.css +1011 -0
- package/src/checkbox/checkbox.tokens.json +102 -0
- package/src/checkbox-group/checkbox-group.css +70 -0
- package/src/checkbox-group/checkbox-group.tokens.json +6 -0
- package/src/code-block/code-block.css +555 -0
- package/src/code-block/code-block.tokens.json +69 -0
- package/src/collapsible/collapsible.css +546 -0
- package/src/collapsible/collapsible.tokens.json +55 -0
- package/src/color-picker/color-picker.css +607 -0
- package/src/color-picker/color-picker.tokens.json +58 -0
- package/src/column-manager/column-manager.css +452 -0
- package/src/column-manager/column-manager.tokens.json +43 -0
- package/src/combobox/combobox.css +1048 -0
- package/src/combobox/combobox.tokens.json +112 -0
- package/src/command-palette/command-palette.css +1000 -0
- package/src/command-palette/command-palette.tokens.json +75 -0
- package/src/container/container.css +198 -0
- package/src/container/container.tokens.json +18 -0
- package/src/context-menu/context-menu.css +104 -0
- package/src/context-menu/context-menu.tokens.json +11 -0
- package/src/currency-field/currency-field.css +787 -0
- package/src/currency-field/currency-field.tokens.json +80 -0
- package/src/dashboard-grid/dashboard-grid.css +748 -0
- package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
- package/src/data-grid/data-grid.css +2484 -0
- package/src/data-grid/data-grid.tokens.json +100 -0
- package/src/data-list/data-list.css +455 -0
- package/src/data-list/data-list.tokens.json +35 -0
- package/src/date-field/date-field.css +560 -0
- package/src/date-field/date-field.tokens.json +80 -0
- package/src/date-picker/date-picker.css +420 -0
- package/src/date-picker/date-picker.tokens.json +49 -0
- package/src/date-range-picker/date-range-picker.css +983 -0
- package/src/date-range-picker/date-range-picker.tokens.json +109 -0
- package/src/datetime-field/datetime-field.css +850 -0
- package/src/datetime-field/datetime-field.tokens.json +91 -0
- package/src/datetime-picker/datetime-picker.css +843 -0
- package/src/datetime-picker/datetime-picker.tokens.json +78 -0
- package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
- package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
- package/src/description-list/description-list.css +675 -0
- package/src/description-list/description-list.tokens.json +66 -0
- package/src/detail-panel/detail-panel.css +329 -0
- package/src/detail-panel/detail-panel.tokens.json +33 -0
- package/src/dialog/dialog.css +968 -0
- package/src/dialog/dialog.tokens.json +66 -0
- package/src/divider/divider.css +131 -0
- package/src/divider/divider.tokens.json +18 -0
- package/src/drawer/drawer.css +1303 -0
- package/src/drawer/drawer.tokens.json +75 -0
- package/src/entity-picker/entity-picker.css +1367 -0
- package/src/entity-picker/entity-picker.tokens.json +118 -0
- package/src/event-calendar/event-calendar.css +2148 -0
- package/src/event-calendar/event-calendar.tokens.json +87 -0
- package/src/field/field.css +707 -0
- package/src/field/field.tokens.json +58 -0
- package/src/field-group/field-group.css +226 -0
- package/src/field-group/field-group.tokens.json +33 -0
- package/src/filter-bar/filter-bar.css +1306 -0
- package/src/filter-bar/filter-bar.tokens.json +78 -0
- package/src/form/form.css +218 -0
- package/src/form/form.tokens.json +32 -0
- package/src/fullscreen/fullscreen.css +376 -0
- package/src/fullscreen/fullscreen.tokens.json +50 -0
- package/src/grid/grid.css +173 -0
- package/src/grid/grid.tokens.json +20 -0
- package/src/highlight/highlight.css +135 -0
- package/src/highlight/highlight.tokens.json +16 -0
- package/src/hover-card/hover-card.css +93 -0
- package/src/hover-card/hover-card.tokens.json +17 -0
- package/src/icon/icon.css +136 -0
- package/src/icon/icon.tokens.json +14 -0
- package/src/image/image.css +449 -0
- package/src/image/image.tokens.json +27 -0
- package/src/import-export-center/import-export-center.css +1175 -0
- package/src/import-export-center/import-export-center.tokens.json +84 -0
- package/src/inline/inline.css +149 -0
- package/src/inline/inline.tokens.json +14 -0
- package/src/inline-message/inline-message.css +396 -0
- package/src/inline-message/inline-message.tokens.json +19 -0
- package/src/kbd/kbd.css +490 -0
- package/src/kbd/kbd.tokens.json +40 -0
- package/src/link/link.css +237 -0
- package/src/link/link.tokens.json +25 -0
- package/src/list/list.css +240 -0
- package/src/list/list.tokens.json +42 -0
- package/src/list-item/list-item.css +937 -0
- package/src/list-item/list-item.tokens.json +114 -0
- package/src/listbox/listbox.css +674 -0
- package/src/listbox/listbox.tokens.json +72 -0
- package/src/loading-overlay/loading-overlay.css +730 -0
- package/src/loading-overlay/loading-overlay.tokens.json +48 -0
- package/src/markdown/markdown.css +168 -0
- package/src/markdown/markdown.tokens.json +18 -0
- package/src/menu/menu.css +817 -0
- package/src/menu/menu.tokens.json +78 -0
- package/src/menubar/menubar.css +170 -0
- package/src/menubar/menubar.tokens.json +32 -0
- package/src/message/message.css +1453 -0
- package/src/message/message.tokens.json +85 -0
- package/src/message-scroller/message-scroller.css +371 -0
- package/src/message-scroller/message-scroller.tokens.json +47 -0
- package/src/multi-select/multi-select.css +1427 -0
- package/src/multi-select/multi-select.tokens.json +114 -0
- package/src/navigation-menu/navigation-menu.css +1029 -0
- package/src/navigation-menu/navigation-menu.tokens.json +70 -0
- package/src/notification-center/notification-center.css +856 -0
- package/src/notification-center/notification-center.tokens.json +84 -0
- package/src/number-field/number-field.css +794 -0
- package/src/number-field/number-field.tokens.json +80 -0
- package/src/otp-input/otp-input.css +525 -0
- package/src/otp-input/otp-input.tokens.json +69 -0
- package/src/page/page.css +179 -0
- package/src/page/page.tokens.json +20 -0
- package/src/page-header/page-header.css +1181 -0
- package/src/page-header/page-header.tokens.json +71 -0
- package/src/pagination/pagination.css +500 -0
- package/src/pagination/pagination.tokens.json +56 -0
- package/src/popover/popover.css +526 -0
- package/src/popover/popover.tokens.json +60 -0
- package/src/progress-indicator/progress-indicator.css +1584 -0
- package/src/progress-indicator/progress-indicator.tokens.json +50 -0
- package/src/prompt-composer/prompt-composer.css +680 -0
- package/src/prompt-composer/prompt-composer.tokens.json +99 -0
- package/src/prose/prose.css +733 -0
- package/src/prose/prose.tokens.json +79 -0
- package/src/radio-group/radio-group.css +778 -0
- package/src/radio-group/radio-group.tokens.json +103 -0
- package/src/rating/rating.css +1071 -0
- package/src/rating/rating.tokens.json +88 -0
- package/src/result-state/result-state.css +980 -0
- package/src/result-state/result-state.tokens.json +63 -0
- package/src/runtime-properties.mjs +321 -0
- package/src/saved-views/saved-views.css +394 -0
- package/src/saved-views/saved-views.tokens.json +41 -0
- package/src/scroll-area/scroll-area.css +481 -0
- package/src/scroll-area/scroll-area.tokens.json +15 -0
- package/src/section/section.css +177 -0
- package/src/section/section.tokens.json +39 -0
- package/src/segmented-control/segmented-control.css +564 -0
- package/src/segmented-control/segmented-control.tokens.json +67 -0
- package/src/select/select.css +1593 -0
- package/src/select/select.tokens.json +110 -0
- package/src/side-navigation/side-navigation.css +1096 -0
- package/src/side-navigation/side-navigation.tokens.json +87 -0
- package/src/skeleton/skeleton.css +431 -0
- package/src/skeleton/skeleton.tokens.json +29 -0
- package/src/slider/slider.css +792 -0
- package/src/slider/slider.tokens.json +75 -0
- package/src/sparkline/sparkline.css +282 -0
- package/src/sparkline/sparkline.tokens.json +45 -0
- package/src/spinner/spinner.css +224 -0
- package/src/spinner/spinner.tokens.json +8 -0
- package/src/split-button/split-button.css +253 -0
- package/src/split-button/split-button.tokens.json +12 -0
- package/src/splitter/splitter.css +336 -0
- package/src/splitter/splitter.tokens.json +24 -0
- package/src/stack/stack.css +128 -0
- package/src/stack/stack.tokens.json +14 -0
- package/src/stat-card/stat-card.css +641 -0
- package/src/stat-card/stat-card.tokens.json +95 -0
- package/src/status-indicator/status-indicator.css +481 -0
- package/src/status-indicator/status-indicator.tokens.json +38 -0
- package/src/stepper/stepper.css +629 -0
- package/src/stepper/stepper.tokens.json +72 -0
- package/src/switch/switch.css +703 -0
- package/src/switch/switch.tokens.json +75 -0
- package/src/table/table.css +1482 -0
- package/src/table/table.tokens.json +69 -0
- package/src/table-of-contents/table-of-contents.css +533 -0
- package/src/table-of-contents/table-of-contents.tokens.json +53 -0
- package/src/tabs/tabs.css +998 -0
- package/src/tabs/tabs.tokens.json +82 -0
- package/src/tag/tag.css +768 -0
- package/src/tag/tag.tokens.json +85 -0
- package/src/tags-input/tags-input.css +757 -0
- package/src/tags-input/tags-input.tokens.json +84 -0
- package/src/text-field/text-field.css +900 -0
- package/src/text-field/text-field.tokens.json +87 -0
- package/src/textarea/textarea.css +365 -0
- package/src/textarea/textarea.tokens.json +59 -0
- package/src/time-field/time-field.css +719 -0
- package/src/time-field/time-field.tokens.json +86 -0
- package/src/time-picker/time-picker.css +665 -0
- package/src/time-picker/time-picker.tokens.json +66 -0
- package/src/time-range-picker/time-range-picker.css +1287 -0
- package/src/time-range-picker/time-range-picker.tokens.json +134 -0
- package/src/timeline/timeline.css +1596 -0
- package/src/timeline/timeline.tokens.json +96 -0
- package/src/toast/toast.css +943 -0
- package/src/toast/toast.tokens.json +90 -0
- package/src/toggle/toggle.css +290 -0
- package/src/toggle/toggle.tokens.json +54 -0
- package/src/toggle-group/toggle-group.css +463 -0
- package/src/toggle-group/toggle-group.tokens.json +54 -0
- package/src/toolbar/toolbar.css +434 -0
- package/src/toolbar/toolbar.tokens.json +49 -0
- package/src/tooltip/tooltip.css +368 -0
- package/src/tooltip/tooltip.tokens.json +39 -0
- package/src/top-navigation/top-navigation.css +1287 -0
- package/src/top-navigation/top-navigation.tokens.json +91 -0
- package/src/tour/tour.css +680 -0
- package/src/tour/tour.tokens.json +67 -0
- package/src/transfer/transfer.css +571 -0
- package/src/transfer/transfer.tokens.json +49 -0
- package/src/tree/tree.css +1000 -0
- package/src/tree/tree.tokens.json +80 -0
- package/src/typography/typography.css +211 -0
- package/src/typography/typography.tokens.json +31 -0
- package/src/upload/upload.css +1706 -0
- package/src/upload/upload.tokens.json +123 -0
- package/src/visually-hidden/visually-hidden.css +52 -0
- package/src/visually-hidden/visually-hidden.tokens.json +1 -0
- package/src/wizard/wizard.css +740 -0
- package/src/wizard/wizard.tokens.json +63 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* ── The binary setting that takes effect immediately ───────────────────
|
|
4
|
+
*
|
|
5
|
+
* The control is an `input[type=checkbox][role=switch]` and the pill is
|
|
6
|
+
* painted around it. ARIA 1.2 declares the `switch` role **Children
|
|
7
|
+
* Presentational**, so the role belongs on the void `input` and never on the
|
|
8
|
+
* box: a root carrying it would fold the label and the description beside it
|
|
9
|
+
* into the control's own accessible name.
|
|
10
|
+
*
|
|
11
|
+
* The anatomy is the one `checkbox` settled one item earlier in this wave,
|
|
12
|
+
* and the metrics below are its metrics: a two-column grid, the label in the
|
|
13
|
+
* second column pointing at the control through `for`, and the description
|
|
14
|
+
* under the label. Two binary controls in one list have to line up, and the
|
|
15
|
+
* way to guarantee that is to derive both from the same numbers — including
|
|
16
|
+
* where the target comes from, which in both is the control and not the row
|
|
17
|
+
* (SW2).
|
|
18
|
+
*
|
|
19
|
+
* Every rule keys on `data-ui` and `data-part`. The checked, disabled and
|
|
20
|
+
* read-only states are read from the element as well as from the attribute
|
|
21
|
+
* the binding mirrors, because a control can be disabled by something that
|
|
22
|
+
* never told it so.
|
|
23
|
+
*/
|
|
24
|
+
[data-ui='switch'][data-part='root'] {
|
|
25
|
+
/**
|
|
26
|
+
* The thumb is the control glyph, which is the scale the theme restates
|
|
27
|
+
* per density — so a switch in a dense table shrinks whole rather than in
|
|
28
|
+
* parts. Everything else is a proportion of it: a pill two thumbs and
|
|
29
|
+
* three gutters wide, whose thumb travels exactly its own diameter plus
|
|
30
|
+
* one gutter, so the same air is left at both ends in both states.
|
|
31
|
+
*/
|
|
32
|
+
--_thumb-size: var(--slotted-control-icon-size-md, 16px);
|
|
33
|
+
--_gutter: var(--slotted-space-1, 0.125rem);
|
|
34
|
+
--_border: var(--slotted-control-border-width, 1px);
|
|
35
|
+
--_track-block: calc(var(--_thumb-size) + (var(--_gutter) + var(--_border)) * 2);
|
|
36
|
+
--_track-inline: var(
|
|
37
|
+
--slotted-switch-track-inline-size,
|
|
38
|
+
calc(var(--_thumb-size) * 2 + var(--_gutter) * 3 + var(--_border) * 2)
|
|
39
|
+
);
|
|
40
|
+
--_travel: calc(var(--_thumb-size) + var(--_gutter));
|
|
41
|
+
--_font-size: var(--slotted-control-font-size-md, 13px);
|
|
42
|
+
--_gap: var(--slotted-control-label-gap-md, 12px);
|
|
43
|
+
--_line-height: var(--slotted-switch-line-height, var(--slotted-line-height-md, 1.5));
|
|
44
|
+
/**
|
|
45
|
+
* The height the label's line is missing before it is as tall as the pill,
|
|
46
|
+
* split above and below its text, so the text reads centred on the pill
|
|
47
|
+
* rather than sitting at the top of a row the pill made.
|
|
48
|
+
*
|
|
49
|
+
* It was the height a one-line label was missing before it was a WCAG 2.2
|
|
50
|
+
* target, which made a row of Switch four and a half pixels taller than
|
|
51
|
+
* the row of Checkbox beside it in the same form — 28.5px against 19.5 at
|
|
52
|
+
* `md` — for a target the control above the pill already carries. CK3 took
|
|
53
|
+
* that model out of `checkbox` and `radio-group`; this is the same removal
|
|
54
|
+
* (SW2). A row is the taller of its label's line and its pill.
|
|
55
|
+
*/
|
|
56
|
+
--_label-slack: max(0px, (var(--_track-block) - var(--_font-size) * var(--_line-height)) / 2);
|
|
57
|
+
/**
|
|
58
|
+
* The distance from the top of the row to the top of the pill, so the pill
|
|
59
|
+
* sits on the first line of a label that wraps to three rather than in the
|
|
60
|
+
* middle of the block. The pill is taller than a line box, so this is
|
|
61
|
+
* usually the slack less a pixel; `max()` keeps it from going negative at
|
|
62
|
+
* a step where the pill is taller still.
|
|
63
|
+
*/
|
|
64
|
+
--_lift: max(
|
|
65
|
+
0px,
|
|
66
|
+
calc(
|
|
67
|
+
var(--_label-slack) + (var(--_font-size) * var(--_line-height) - var(--_track-block)) / 2
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
/** The box the pointer presses: the pill, or the WCAG 2.2 minimum where
|
|
71
|
+
the pill is shorter than it. */
|
|
72
|
+
--_target-block: max(var(--_track-block), var(--slotted-target-min, 24px));
|
|
73
|
+
/** The distance from the label's text to the description under it. */
|
|
74
|
+
--_description-gap: max(0px, var(--slotted-space-3, 0.375rem) - var(--_label-slack));
|
|
75
|
+
|
|
76
|
+
/* The rows are as tall as they read. Stretched, the description drifted
|
|
77
|
+
away from the label it belongs to; the surplus is the container's. */
|
|
78
|
+
align-content: start;
|
|
79
|
+
color: var(--slotted-switch-color, var(--slotted-text, CanvasText));
|
|
80
|
+
display: grid;
|
|
81
|
+
font-family: var(--slotted-control-font-family, system-ui, sans-serif);
|
|
82
|
+
font-size: var(--_font-size);
|
|
83
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
84
|
+
line-height: var(--_line-height);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The control. It gives up every pixel of its own appearance and keeps
|
|
89
|
+
* everything that makes it a control: the state, the identifier, the name,
|
|
90
|
+
* the tab stop, Space, the label association and form submission.
|
|
91
|
+
*
|
|
92
|
+
* It is transparent rather than clipped away, because a control removed from
|
|
93
|
+
* the layout takes its hit area with it — and this one *is* the hit area
|
|
94
|
+
* over the pill. It is the WCAG minimum in both axes wherever the pill is
|
|
95
|
+
* smaller than it, and it overhangs the pill by the difference rather than
|
|
96
|
+
* pushing the row open: the two negative margins take its own overhang back
|
|
97
|
+
* out of what it contributes to the row, so a Switch row is the height of
|
|
98
|
+
* its pill and its label and never of its target (SW2). The overhang is on
|
|
99
|
+
* a transparent box, so nothing about it is visible.
|
|
100
|
+
*/
|
|
101
|
+
[data-ui='switch'] > [data-part='control'] {
|
|
102
|
+
align-self: start;
|
|
103
|
+
appearance: none;
|
|
104
|
+
background-color: transparent;
|
|
105
|
+
block-size: var(--_target-block);
|
|
106
|
+
border: 0;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
grid-area: 1 / 1;
|
|
109
|
+
inline-size: var(--_track-inline);
|
|
110
|
+
/* It follows the pill onto the label's first line, and gives back what it
|
|
111
|
+
overhangs at both ends. */
|
|
112
|
+
margin-block-end: calc((var(--_track-block) - var(--_target-block)) / 2);
|
|
113
|
+
margin-block-start: calc(var(--_lift) + (var(--_track-block) - var(--_target-block)) / 2);
|
|
114
|
+
margin-inline: 0;
|
|
115
|
+
opacity: 0;
|
|
116
|
+
/* The ring is drawn on the track, so the control never draws a second. */
|
|
117
|
+
outline: none;
|
|
118
|
+
padding: 0;
|
|
119
|
+
/* No tap delay waiting for a second tap a switch cannot use. */
|
|
120
|
+
touch-action: manipulation;
|
|
121
|
+
/**
|
|
122
|
+
* Above the paint, and this is not optional. The track shares this grid
|
|
123
|
+
* cell and comes later in the DOM, so it paints over the control: a
|
|
124
|
+
* pointer press landed on the track and the switch answered nothing, and
|
|
125
|
+
* only the label toggled it. That is the defect a reviewer found by
|
|
126
|
+
* pressing the pill rather than by reading the rule.
|
|
127
|
+
*
|
|
128
|
+
* It is a local stacking order rather than a step on the z-index scale:
|
|
129
|
+
* that scale ranks application layers — sticky, overlay, popover, toast —
|
|
130
|
+
* and this ranks two elements inside one control.
|
|
131
|
+
*/
|
|
132
|
+
z-index: 1;
|
|
133
|
+
-webkit-tap-highlight-color: transparent;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The track. It carries the ground, the edge and the ring, and it is the
|
|
138
|
+
* containing block the thumb travels inside. It is lifted onto the label's
|
|
139
|
+
* first line rather than centred on the label, because `align-items: center`
|
|
140
|
+
* on a label that wraps to three lines puts the pill beside the second one.
|
|
141
|
+
*/
|
|
142
|
+
[data-ui='switch'] > [data-part='track'] {
|
|
143
|
+
background-color: var(
|
|
144
|
+
--slotted-switch-track-background,
|
|
145
|
+
var(--slotted-tone-secondary-solid, ButtonFace)
|
|
146
|
+
);
|
|
147
|
+
block-size: var(--_track-block);
|
|
148
|
+
border: var(--_border) solid
|
|
149
|
+
var(--slotted-switch-border-color, var(--slotted-control-border-color, ButtonBorder));
|
|
150
|
+
border-radius: var(--slotted-switch-radius, var(--slotted-radius-full, 9999px));
|
|
151
|
+
box-sizing: border-box;
|
|
152
|
+
grid-area: 1 / 1;
|
|
153
|
+
inline-size: var(--_track-inline);
|
|
154
|
+
margin-block-start: var(--_lift);
|
|
155
|
+
/* The pointer belongs to the control above it, not to the paint. */
|
|
156
|
+
pointer-events: none;
|
|
157
|
+
position: relative;
|
|
158
|
+
transition:
|
|
159
|
+
background-color var(--slotted-control-transition-duration)
|
|
160
|
+
var(--slotted-control-transition-easing),
|
|
161
|
+
border-color var(--slotted-control-transition-duration)
|
|
162
|
+
var(--slotted-control-transition-easing);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The thumb, and the whole of the family's motion.
|
|
167
|
+
*
|
|
168
|
+
* It travels on `inset-inline-start` rather than on a translation, so a
|
|
169
|
+
* right-to-left document mirrors the movement with no direction-specific
|
|
170
|
+
* rule at all — the axis is the writing direction's, not the screen's.
|
|
171
|
+
*/
|
|
172
|
+
[data-ui='switch'] > [data-part='track'] > [data-part='thumb'] {
|
|
173
|
+
/* A flex box, so the mark's icon is centred in the knob as a box and not
|
|
174
|
+
set on a line of text. */
|
|
175
|
+
align-items: center;
|
|
176
|
+
/**
|
|
177
|
+
* One knob, in both states and all three schemes. `on-solid` is the ink the
|
|
178
|
+
* theme guarantees against a solid tone, and both tracks are solid tones —
|
|
179
|
+
* so the thumb is white wherever the theme is legible. Reading `surface`
|
|
180
|
+
* here instead inverted the relationship between scheme and state: a white
|
|
181
|
+
* knob on a light track in the light scheme, and a near-black one on a
|
|
182
|
+
* lighter track in the dark scheme, which is two components rather than one.
|
|
183
|
+
*/
|
|
184
|
+
background-color: var(--slotted-switch-thumb-background, var(--slotted-tone-accent-on-solid));
|
|
185
|
+
/**
|
|
186
|
+
* The thumb keeps an edge in every state. A thumb separated from its track
|
|
187
|
+
* by colour alone disappears in the `high-contrast` scheme, where the
|
|
188
|
+
* unchecked track is white, and under forced colours, where it is Canvas.
|
|
189
|
+
*/
|
|
190
|
+
border: var(--_border) solid
|
|
191
|
+
var(--slotted-switch-thumb-border-color, var(--slotted-control-border-color, ButtonBorder));
|
|
192
|
+
border-radius: var(--slotted-radius-full, 9999px);
|
|
193
|
+
block-size: var(--_thumb-size);
|
|
194
|
+
/**
|
|
195
|
+
* The shadow every framed control carries, on the one part of this family
|
|
196
|
+
* that is meant to look raised. It is barely visible on purpose: on a
|
|
197
|
+
* light unchecked track the knob and the ground are two steps apart, and
|
|
198
|
+
* without it the knob rests on its own hairline border alone. The theme
|
|
199
|
+
* withdraws it in `high-contrast` and under forced colours, where the
|
|
200
|
+
* border is doing the work instead.
|
|
201
|
+
*/
|
|
202
|
+
box-shadow: var(--slotted-switch-thumb-shadow, var(--slotted-control-shadow, none));
|
|
203
|
+
box-sizing: border-box;
|
|
204
|
+
display: flex;
|
|
205
|
+
inline-size: var(--_thumb-size);
|
|
206
|
+
inset-block-start: var(--_gutter);
|
|
207
|
+
inset-inline-start: var(--_gutter);
|
|
208
|
+
justify-content: center;
|
|
209
|
+
position: absolute;
|
|
210
|
+
transition:
|
|
211
|
+
inset-inline-start var(--slotted-control-transition-duration)
|
|
212
|
+
var(--slotted-control-transition-easing),
|
|
213
|
+
background-color var(--slotted-control-transition-duration)
|
|
214
|
+
var(--slotted-control-transition-easing),
|
|
215
|
+
border-color var(--slotted-control-transition-duration)
|
|
216
|
+
var(--slotted-control-transition-easing);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The mark. `docs/component-features.md` asks for the state to be
|
|
221
|
+
* perceptible beyond colour *and* beyond position, and this is what supplies
|
|
222
|
+
* it: the check the binding renders in the knob as the `Glyph` element,
|
|
223
|
+
* never a character. `indicator="none"` is the binding rendering none.
|
|
224
|
+
*
|
|
225
|
+
* It is always in the DOM and fades in, so the eye follows one control
|
|
226
|
+
* changing rather than two marks swapping places. Off is the absence of the
|
|
227
|
+
* mark, which is the same statement a checkbox makes.
|
|
228
|
+
*/
|
|
229
|
+
[data-ui='switch'] > [data-part='track'] > [data-part='thumb'] > [data-ui='icon'] {
|
|
230
|
+
/**
|
|
231
|
+
* Seventy per cent of the knob rather than all of it. The glyph is drawn
|
|
232
|
+
* on a 24-unit grid with a 3-unit stroke, so filling the knob the check
|
|
233
|
+
* read as a checkbox on a pill.
|
|
234
|
+
*/
|
|
235
|
+
block-size: 70%;
|
|
236
|
+
color: var(--slotted-switch-mark-color, var(--slotted-tone-accent-solid));
|
|
237
|
+
inline-size: 70%;
|
|
238
|
+
opacity: 0;
|
|
239
|
+
transition: opacity var(--slotted-control-transition-duration)
|
|
240
|
+
var(--slotted-control-transition-easing);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* ── The label ────────────────────────────────────────────────────────
|
|
244
|
+
*
|
|
245
|
+
* The label is the control's own element and it names it through `for`, so a
|
|
246
|
+
* description below it can be read without toggling anything.
|
|
247
|
+
*
|
|
248
|
+
* It does not carry the target. The pill is 22 pixels tall at `md` and less
|
|
249
|
+
* at the tighter densities, and what makes up the difference is the control
|
|
250
|
+
* stretched over the pill, which is a transparent box with a minimum of its
|
|
251
|
+
* own — the correction `docs/token-foundation.md` asks the family for. The
|
|
252
|
+
* padding here is optical: it centres the words on the pill.
|
|
253
|
+
*/
|
|
254
|
+
[data-ui='switch'] > [data-part='label'] {
|
|
255
|
+
cursor: pointer;
|
|
256
|
+
grid-column: 2;
|
|
257
|
+
grid-row: 1;
|
|
258
|
+
/** A label that is one unbroken token — an id, a hash, a URL — breaks. */
|
|
259
|
+
overflow-wrap: anywhere;
|
|
260
|
+
padding-block: var(--_label-slack);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The gap between the pill and the text is the grid's, opened only beside a
|
|
265
|
+
* label, and not the label's padding: a padding is laid out in the label's
|
|
266
|
+
* own direction, and a label in Arabic inside a row in Portuguese took the
|
|
267
|
+
* gap on its far side and set its text against the pill.
|
|
268
|
+
*
|
|
269
|
+
* The padding was also what made the gap toggle the setting. The control
|
|
270
|
+
* takes that over: it grows by the gap towards the text and gives the growth
|
|
271
|
+
* back as a negative margin, as it gives back its block overhang, so the row
|
|
272
|
+
* is no wider and a press between the pill and the words still lands on it.
|
|
273
|
+
*/
|
|
274
|
+
[data-ui='switch'][data-part='root']:has(> [data-part='label']) {
|
|
275
|
+
column-gap: var(--_gap);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* Beside the pill, the label is as wide as its words: stretched across the
|
|
279
|
+
column, a label in Arabic in a row in Portuguese aligned its text to its
|
|
280
|
+
own start and set it at the far end of the row, away from the pill. */
|
|
281
|
+
[data-ui='switch'] > [data-part='label'] {
|
|
282
|
+
justify-self: start;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
[data-ui='switch'][data-part='root']:has(> [data-part='label']) > [data-part='control'] {
|
|
286
|
+
inline-size: calc(var(--_track-inline) + var(--_gap));
|
|
287
|
+
margin-inline-end: calc(-1 * var(--_gap));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Aligned under the label rather than under the pill, and never inside the
|
|
292
|
+
* label: inside, it would join the accessible name, and a paragraph of help
|
|
293
|
+
* text that toggles a setting when it is clicked is a paragraph nobody reads.
|
|
294
|
+
*/
|
|
295
|
+
[data-ui='switch'] > [data-part='description'] {
|
|
296
|
+
color: var(--slotted-switch-description-color, var(--slotted-text-muted, GrayText));
|
|
297
|
+
font-size: var(--slotted-switch-description-font-size, var(--slotted-font-size-xs, 0.75rem));
|
|
298
|
+
grid-column: 2;
|
|
299
|
+
grid-row: 2;
|
|
300
|
+
line-height: var(--slotted-line-height-xs, 1.4);
|
|
301
|
+
margin: 0;
|
|
302
|
+
overflow-wrap: anywhere;
|
|
303
|
+
padding-block-start: var(--_description-gap);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* ── Placement ────────────────────────────────────────────────────────
|
|
307
|
+
*
|
|
308
|
+
* The pill on the other side of its label, which is the settings row every
|
|
309
|
+
* product writes: the label takes the free width and the control sits at the
|
|
310
|
+
* end. Two columns swap and the column gap stays between them, so the
|
|
311
|
+
* distance is the same number in both arrangements and in both writing
|
|
312
|
+
* directions.
|
|
313
|
+
*/
|
|
314
|
+
|
|
315
|
+
[data-ui='switch'][data-part='root'][data-label-placement='start'] {
|
|
316
|
+
/**
|
|
317
|
+
* `1fr` and not `max-content`, for the reason `checkbox` records: sizing
|
|
318
|
+
* the first column from each row's own label ends a list with the controls
|
|
319
|
+
* at as many different distances from the edge as there are rows.
|
|
320
|
+
*/
|
|
321
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
[data-ui='switch'][data-part='root'][data-label-placement='start'] > [data-part='control'],
|
|
325
|
+
[data-ui='switch'][data-part='root'][data-label-placement='start'] > [data-part='track'] {
|
|
326
|
+
grid-area: 1 / 2;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
[data-ui='switch'][data-part='root'][data-label-placement='start'] > [data-part='label'],
|
|
330
|
+
[data-ui='switch'][data-part='root'][data-label-placement='start'] > [data-part='description'] {
|
|
331
|
+
grid-column: 1;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* Before the pill, the label takes the row, so the row is the settings row. */
|
|
335
|
+
[data-ui='switch'][data-part='root'][data-label-placement='start'] > [data-part='label'] {
|
|
336
|
+
justify-self: stretch;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* The control reaches across the gap towards the label, now on its start side. */
|
|
340
|
+
[data-ui='switch'][data-part='root'][data-label-placement='start']:has(> [data-part='label'])
|
|
341
|
+
> [data-part='control'] {
|
|
342
|
+
margin-inline-end: 0;
|
|
343
|
+
margin-inline-start: calc(-1 * var(--_gap));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* ── Size ─────────────────────────────────────────────────────────────
|
|
347
|
+
*
|
|
348
|
+
* Three steps, matching the field the control sits in. The control reads the
|
|
349
|
+
* field's step before its own default, because a `size="sm"` field holding
|
|
350
|
+
* an `md` control is a field and a control disagreeing about one row.
|
|
351
|
+
*/
|
|
352
|
+
|
|
353
|
+
[data-ui='switch'][data-part='root'][data-size='sm'] {
|
|
354
|
+
--_thumb-size: var(--slotted-control-icon-size-sm, 14px);
|
|
355
|
+
--_font-size: var(--slotted-control-font-size-sm, 12px);
|
|
356
|
+
--_gap: var(--slotted-control-label-gap-sm, 8px);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
[data-ui='switch'][data-part='root'][data-size='md'] {
|
|
360
|
+
--_thumb-size: var(--slotted-control-icon-size-md, 16px);
|
|
361
|
+
--_font-size: var(--slotted-control-font-size-md, 13px);
|
|
362
|
+
--_gap: var(--slotted-control-label-gap-md, 12px);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
[data-ui='switch'][data-part='root'][data-size='lg'] {
|
|
366
|
+
--_thumb-size: var(--slotted-control-icon-size-lg, 18px);
|
|
367
|
+
--_font-size: var(--slotted-control-font-size-lg, 14px);
|
|
368
|
+
--_gap: var(--slotted-control-label-gap-lg, 12px);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ── States ───────────────────────────────────────────────────────────
|
|
372
|
+
*
|
|
373
|
+
* Written strongest last, and each weaker one guarded against every stronger
|
|
374
|
+
* one it can meet, which is the order `STATE_PRECEDENCE` sets.
|
|
375
|
+
*
|
|
376
|
+
* Each state is matched on the element as well as on the attribute the
|
|
377
|
+
* binding mirrors. The element is the truth a mirror can lag behind: a
|
|
378
|
+
* control inside a disabled `fieldset` is never told that it is disabled,
|
|
379
|
+
* and the deleted `input` family shipped that defect once already.
|
|
380
|
+
*/
|
|
381
|
+
|
|
382
|
+
[data-ui='switch'][data-part='root']:is([data-checked], :has(> [data-part='control']:checked))
|
|
383
|
+
> [data-part='track'] {
|
|
384
|
+
background-color: var(
|
|
385
|
+
--slotted-switch-checked-background,
|
|
386
|
+
var(--slotted-tone-accent-solid, Highlight)
|
|
387
|
+
);
|
|
388
|
+
border-color: var(
|
|
389
|
+
--slotted-switch-checked-border-color,
|
|
390
|
+
var(--slotted-tone-accent-solid, Highlight)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
[data-ui='switch'][data-part='root']:is([data-checked], :has(> [data-part='control']:checked))
|
|
395
|
+
> [data-part='track']
|
|
396
|
+
> [data-part='thumb'] {
|
|
397
|
+
border-color: var(
|
|
398
|
+
--slotted-switch-checked-thumb-border-color,
|
|
399
|
+
var(--slotted-tone-accent-solid, Highlight)
|
|
400
|
+
);
|
|
401
|
+
inset-inline-start: calc(var(--_gutter) + var(--_travel));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
[data-ui='switch'][data-part='root']:is(
|
|
405
|
+
[data-checked],
|
|
406
|
+
:has(> [data-part='control']:checked)
|
|
407
|
+
):not([data-loading])
|
|
408
|
+
> [data-part='track']
|
|
409
|
+
> [data-part='thumb']
|
|
410
|
+
> [data-ui='icon'] {
|
|
411
|
+
opacity: 1;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Hover and press are the track's, because the thumb is the part that says
|
|
416
|
+
* where the value is and a second thing moving under the pointer only
|
|
417
|
+
* competes with it. Both are held back for disabled, loading and read-only,
|
|
418
|
+
* and both are written before `invalid`, which outranks them.
|
|
419
|
+
*/
|
|
420
|
+
[data-ui='switch'][data-part='root']:hover:not([data-disabled]):not([data-loading]):not(
|
|
421
|
+
[data-readonly]
|
|
422
|
+
):not(:has(> [data-part='control']:disabled))
|
|
423
|
+
> [data-part='track'] {
|
|
424
|
+
background-color: var(
|
|
425
|
+
--slotted-switch-hover-background,
|
|
426
|
+
var(--slotted-tone-secondary-solid-hover, ButtonFace)
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
[data-ui='switch'][data-part='root']:is(
|
|
431
|
+
[data-checked],
|
|
432
|
+
:has(> [data-part='control']:checked)
|
|
433
|
+
):hover:not([data-disabled]):not([data-loading]):not([data-readonly]):not(
|
|
434
|
+
:has(> [data-part='control']:disabled)
|
|
435
|
+
)
|
|
436
|
+
> [data-part='track'] {
|
|
437
|
+
background-color: var(
|
|
438
|
+
--slotted-switch-checked-hover-background,
|
|
439
|
+
var(--slotted-tone-accent-solid-hover, Highlight)
|
|
440
|
+
);
|
|
441
|
+
border-color: var(
|
|
442
|
+
--slotted-switch-checked-hover-background,
|
|
443
|
+
var(--slotted-tone-accent-solid-hover, Highlight)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
[data-ui='switch'][data-part='root']:active:not([data-disabled]):not([data-loading]):not(
|
|
448
|
+
[data-readonly]
|
|
449
|
+
):not(:has(> [data-part='control']:disabled))
|
|
450
|
+
> [data-part='track'] {
|
|
451
|
+
background-color: var(
|
|
452
|
+
--slotted-switch-active-background,
|
|
453
|
+
var(--slotted-tone-secondary-solid-active, ButtonFace)
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
[data-ui='switch'][data-part='root']:is(
|
|
458
|
+
[data-checked],
|
|
459
|
+
:has(> [data-part='control']:checked)
|
|
460
|
+
):active:not([data-disabled]):not([data-loading]):not([data-readonly]):not(
|
|
461
|
+
:has(> [data-part='control']:disabled)
|
|
462
|
+
)
|
|
463
|
+
> [data-part='track'] {
|
|
464
|
+
background-color: var(
|
|
465
|
+
--slotted-switch-checked-active-background,
|
|
466
|
+
var(--slotted-tone-accent-solid-active, Highlight)
|
|
467
|
+
);
|
|
468
|
+
border-color: var(
|
|
469
|
+
--slotted-switch-checked-active-background,
|
|
470
|
+
var(--slotted-tone-accent-solid-active, Highlight)
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The ring belongs to the track, because the track is the switch a reader
|
|
476
|
+
* sees; the control it answers is the transparent element over it. The
|
|
477
|
+
* offset keeps the ring off the pill's own edge, which is what makes a
|
|
478
|
+
* focused unchecked switch legible against a sunken surface.
|
|
479
|
+
*/
|
|
480
|
+
[data-ui='switch'][data-part='root']:has(> [data-part='control']:focus-visible)
|
|
481
|
+
> [data-part='track'] {
|
|
482
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
483
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Read-only stays focusable, announced and submitted, and it must not be
|
|
488
|
+
* mistakable for disabled. The pill goes hollow rather than grey: the state
|
|
489
|
+
* keeps its own colour in the mark, and the ground stops saying "press me".
|
|
490
|
+
*/
|
|
491
|
+
[data-ui='switch'][data-part='root'][data-readonly] > [data-part='track'] {
|
|
492
|
+
background-color: var(--slotted-switch-readonly-background, var(--slotted-surface, Canvas));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
[data-ui='switch'][data-part='root'][data-readonly] > [data-part='control'],
|
|
496
|
+
[data-ui='switch'][data-part='root'][data-readonly] > [data-part='label'] {
|
|
497
|
+
cursor: default;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Read-only outranks checked, so it is written with enough weight to take
|
|
502
|
+
* the ground back from it: the pill is hollow whichever state it holds.
|
|
503
|
+
*
|
|
504
|
+
* The edge stays the neutral control edge rather than turning accent. A
|
|
505
|
+
* focused read-only switch drew the ring and the border in the same blue,
|
|
506
|
+
* two pixels apart, and neither could be told from the other. The state is
|
|
507
|
+
* carried where it always is — the thumb's position, and the mark on it.
|
|
508
|
+
*/
|
|
509
|
+
[data-ui='switch'][data-part='root'][data-readonly]:is(
|
|
510
|
+
[data-checked],
|
|
511
|
+
:has(> [data-part='control']:checked)
|
|
512
|
+
)
|
|
513
|
+
> [data-part='track'] {
|
|
514
|
+
background-color: var(--slotted-switch-readonly-background, var(--slotted-surface, Canvas));
|
|
515
|
+
border-color: var(
|
|
516
|
+
--slotted-switch-readonly-border-color,
|
|
517
|
+
var(--slotted-control-border-color, ButtonBorder)
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
[data-ui='switch'][data-part='root'][data-readonly]:is(
|
|
522
|
+
[data-checked],
|
|
523
|
+
:has(> [data-part='control']:checked)
|
|
524
|
+
)
|
|
525
|
+
> [data-part='track']
|
|
526
|
+
> [data-part='thumb'] {
|
|
527
|
+
border-color: var(
|
|
528
|
+
--slotted-switch-readonly-border-color,
|
|
529
|
+
var(--slotted-control-border-color, ButtonBorder)
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Invalid outranks hover and press and is written after them: a setting
|
|
535
|
+
* reporting an error should not stop looking wrong because the pointer is
|
|
536
|
+
* over it.
|
|
537
|
+
*
|
|
538
|
+
* The ring is retargeted rather than repainted. Every rule that draws focus
|
|
539
|
+
* reads `--slotted-focus-ring-color`, so pointing that one name at the
|
|
540
|
+
* invalid colour on the root moves the ring without restating it — the rule
|
|
541
|
+
* `text-field` wrote for this family to inherit.
|
|
542
|
+
*/
|
|
543
|
+
[data-ui='switch'][data-part='root'][data-invalid] {
|
|
544
|
+
--slotted-focus-ring-color: var(
|
|
545
|
+
--slotted-switch-invalid-focus-ring-color,
|
|
546
|
+
var(--slotted-focus-ring-color-invalid, Highlight)
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
[data-ui='switch'][data-part='root'][data-invalid] > [data-part='track'],
|
|
551
|
+
[data-ui='switch'][data-part='root'][data-invalid] > [data-part='track'] > [data-part='thumb'] {
|
|
552
|
+
border-color: var(--slotted-switch-invalid-border-color, var(--slotted-tone-danger-border));
|
|
553
|
+
/*
|
|
554
|
+
* Width as well as colour (WCAG 1.4.1), the edge `text-field` draws: in
|
|
555
|
+
* `high-contrast` the danger border is the resting edge's black. An edge
|
|
556
|
+
* inside the border of the track and of the thumb, so neither moves, in
|
|
557
|
+
* place of the thumb's resting lift; the thumb carries it where a checked
|
|
558
|
+
* track's fill would hide the track's own.
|
|
559
|
+
*/
|
|
560
|
+
--_invalid-edge-width: var(
|
|
561
|
+
--slotted-switch-invalid-edge-width,
|
|
562
|
+
var(--slotted-control-invalid-edge-width, 1px)
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
box-shadow: inset 0 0 0 var(--_invalid-edge-width)
|
|
566
|
+
var(--slotted-switch-invalid-border-color, var(--slotted-tone-danger-border));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Loading. The geometry does not change — the thumb stays where it is and
|
|
571
|
+
* the track keeps its size — because a control that resizes while it saves
|
|
572
|
+
* moves the row under the pointer. The mark gives way to the arc, which is
|
|
573
|
+
* the one thing in the family that says the value is not settled yet.
|
|
574
|
+
*/
|
|
575
|
+
[data-ui='switch'][data-part='root'][data-loading] > [data-part='control'] {
|
|
576
|
+
cursor: progress;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
[data-ui='switch'][data-part='root'][data-loading]
|
|
580
|
+
> [data-part='track']
|
|
581
|
+
> [data-part='thumb']
|
|
582
|
+
> [data-ui='icon'] {
|
|
583
|
+
opacity: 0;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* The arc. It is geometry rather than an icon — a ring with one quarter of
|
|
588
|
+
* its border left transparent — so no glyph token is invented for a shape
|
|
589
|
+
* the border already draws. `progress-indicator` is wave 2 and not built;
|
|
590
|
+
* when it is, this is the first thing it should replace.
|
|
591
|
+
*
|
|
592
|
+
* Three quarters rather than one. A single coloured side read as a shape
|
|
593
|
+
* rather than as a spinner in a still image, and a still image is what a
|
|
594
|
+
* reader under `prefers-reduced-motion` is looking at.
|
|
595
|
+
*/
|
|
596
|
+
[data-ui='switch'][data-part='root'][data-loading]
|
|
597
|
+
> [data-part='track']
|
|
598
|
+
> [data-part='thumb']::after {
|
|
599
|
+
animation: slotted-switch-pending var(--slotted-duration-loop) linear infinite;
|
|
600
|
+
border: var(--slotted-border-width-thin, 1px) solid
|
|
601
|
+
var(--slotted-switch-pending-color, var(--slotted-tone-accent-solid));
|
|
602
|
+
border-inline-start-color: transparent;
|
|
603
|
+
border-radius: var(--slotted-radius-full, 9999px);
|
|
604
|
+
box-sizing: border-box;
|
|
605
|
+
content: '';
|
|
606
|
+
display: block;
|
|
607
|
+
/* Inset by an eighth so the arc sits inside the thumb's own edge. */
|
|
608
|
+
inset: 12.5%;
|
|
609
|
+
position: absolute;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
@keyframes slotted-switch-pending {
|
|
613
|
+
to {
|
|
614
|
+
rotate: 360deg;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* An animation that repeats forever has to stop rather than hurry, which is
|
|
620
|
+
* what `docs/token-foundation.md` requires and what the motion scale cannot
|
|
621
|
+
* do for a loop. The arc stays drawn, so the pending state is still visible
|
|
622
|
+
* without anything moving.
|
|
623
|
+
*/
|
|
624
|
+
@media (prefers-reduced-motion: reduce) {
|
|
625
|
+
[data-ui='switch'][data-part='root'][data-loading]
|
|
626
|
+
> [data-part='track']
|
|
627
|
+
> [data-part='thumb']::after {
|
|
628
|
+
animation: none;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Disabled is the strongest state and is written last. `:disabled` on the
|
|
634
|
+
* control is matched beside `[data-disabled]` on the root, because a control
|
|
635
|
+
* inside a disabled `fieldset` is disabled by something that never told it
|
|
636
|
+
* so — and left to the attribute alone it would look fully operable and
|
|
637
|
+
* answer nothing.
|
|
638
|
+
*/
|
|
639
|
+
[data-ui='switch'][data-part='root']:is([data-disabled], :has(> [data-part='control']:disabled))
|
|
640
|
+
> [data-part='control'],
|
|
641
|
+
[data-ui='switch'][data-part='root']:is([data-disabled], :has(> [data-part='control']:disabled))
|
|
642
|
+
> [data-part='label'] {
|
|
643
|
+
cursor: not-allowed;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
[data-ui='switch'][data-part='root']:is([data-disabled], :has(> [data-part='control']:disabled))
|
|
647
|
+
> [data-part='track'] {
|
|
648
|
+
background-color: var(
|
|
649
|
+
--slotted-switch-disabled-background,
|
|
650
|
+
var(--slotted-disabled-background, ButtonFace)
|
|
651
|
+
);
|
|
652
|
+
border-color: var(--slotted-switch-disabled-border-color, var(--slotted-disabled-border));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Muted to the colour the theme measures rather than faded by opacity: a
|
|
657
|
+
* setting nobody can read is a setting nobody can check, and a disabled form
|
|
658
|
+
* still has to say what it holds.
|
|
659
|
+
*/
|
|
660
|
+
[data-ui='switch'][data-part='root']:is([data-disabled], :has(> [data-part='control']:disabled))
|
|
661
|
+
> [data-part='track']
|
|
662
|
+
> [data-part='thumb'] {
|
|
663
|
+
/**
|
|
664
|
+
* Grey rather than white. The knob is the brightest thing in the family,
|
|
665
|
+
* and a bright knob on a grey track reads as a control still waiting to be
|
|
666
|
+
* used. The mark inverts with it, so the state stays readable.
|
|
667
|
+
*/
|
|
668
|
+
background-color: var(
|
|
669
|
+
--slotted-switch-disabled-thumb-background,
|
|
670
|
+
var(--slotted-disabled-foreground)
|
|
671
|
+
);
|
|
672
|
+
border-color: var(--slotted-switch-disabled-border-color, var(--slotted-disabled-border));
|
|
673
|
+
box-shadow: none;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
[data-ui='switch'][data-part='root']:is([data-disabled], :has(> [data-part='control']:disabled))
|
|
677
|
+
> [data-part='track']
|
|
678
|
+
> [data-part='thumb']
|
|
679
|
+
> [data-ui='icon'] {
|
|
680
|
+
color: var(--slotted-switch-disabled-mark-color, var(--slotted-disabled-background));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
[data-ui='switch'][data-part='root']:is([data-disabled], :has(> [data-part='control']:disabled))
|
|
684
|
+
> [data-part='label'],
|
|
685
|
+
[data-ui='switch'][data-part='root']:is([data-disabled], :has(> [data-part='control']:disabled))
|
|
686
|
+
> [data-part='description'] {
|
|
687
|
+
color: var(--slotted-switch-disabled-color, var(--slotted-disabled-foreground, GrayText));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/*
|
|
691
|
+
* Forced colours remove every box shadow, and the edge with them. The width
|
|
692
|
+
* comes back as an outline inside the border, on the
|
|
693
|
+
* track and on the thumb; the focus ring takes the track and the thumb keeps
|
|
694
|
+
* its edge.
|
|
695
|
+
*/
|
|
696
|
+
@media (forced-colors: active) {
|
|
697
|
+
[data-ui='switch'][data-part='root'][data-invalid] > [data-part='track'],
|
|
698
|
+
[data-ui='switch'][data-part='root'][data-invalid] > [data-part='track'] > [data-part='thumb'] {
|
|
699
|
+
outline: var(--_invalid-edge-width) solid CanvasText;
|
|
700
|
+
outline-offset: calc(-1 * (var(--_border) + var(--_invalid-edge-width)));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|