@slotted/styles 0.0.0-stage → 0.1.0-beta.1
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,754 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* One surface for four members. Button, ButtonLink, IconButton and
|
|
4
|
+
* ToggleButton are one control with different jobs — an action, a
|
|
5
|
+
* navigation, an action with no room for a label, and an action that stays
|
|
6
|
+
* on — so they share an identifier, a geometry scale and a state machine,
|
|
7
|
+
* and differ only in what they contain.
|
|
8
|
+
*
|
|
9
|
+
* Every geometric decision is read from `--slotted-control-*` rather than
|
|
10
|
+
* aliased into `--slotted-button-*`. The alias looked harmless and was not:
|
|
11
|
+
* the theme raises the control tokens under `@media (pointer: coarse)`, and
|
|
12
|
+
* a family reading its own copy never grew to the touch target.
|
|
13
|
+
*
|
|
14
|
+
* The tone roles travel through private properties because three variants
|
|
15
|
+
* paint the same roles differently; naming them once per tone is what keeps
|
|
16
|
+
* the variant rules from repeating five colour maps each.
|
|
17
|
+
*
|
|
18
|
+
* Nothing in this sheet changes a size when a state changes. Every state is
|
|
19
|
+
* a colour; the only one drawn outside the border box is focus, which is an
|
|
20
|
+
* outline. The rebuild that preceded this one thickened the border on a
|
|
21
|
+
* pressed toggle, which moved every control beside it by two pixels.
|
|
22
|
+
*/
|
|
23
|
+
[data-ui='button'] {
|
|
24
|
+
--_solid: var(--slotted-tone-accent-solid);
|
|
25
|
+
--_solid-hover: var(--slotted-tone-accent-solid-hover);
|
|
26
|
+
--_solid-active: var(--slotted-tone-accent-solid-active);
|
|
27
|
+
--_on-solid: var(--slotted-tone-accent-on-solid);
|
|
28
|
+
--_text: var(--slotted-tone-accent-text);
|
|
29
|
+
/**
|
|
30
|
+
* The edge of a filled control is the fill itself. A border a shade away
|
|
31
|
+
* from what it encloses reads as a halo around the shape rather than as
|
|
32
|
+
* its edge, and one borrowed from another role reads as a line laid over
|
|
33
|
+
* the control. One tone overrides it, and only just.
|
|
34
|
+
*/
|
|
35
|
+
--_edge: var(--_solid);
|
|
36
|
+
|
|
37
|
+
--_height: var(--slotted-control-height-md, 34px);
|
|
38
|
+
--_padding-inline: var(--slotted-control-padding-inline-md, 13px);
|
|
39
|
+
--_font-size: var(--slotted-control-font-size-md, 13px);
|
|
40
|
+
--_icon-size: var(--slotted-control-icon-size-md, 16px);
|
|
41
|
+
--_gap: var(--slotted-control-gap-md, 8px);
|
|
42
|
+
/**
|
|
43
|
+
* A glyph reaches closer to the edge than a letter does, so the side that
|
|
44
|
+
* holds one is inset less. Without the correction an icon looks pushed
|
|
45
|
+
* into the middle of the control while the label looks pinned to its
|
|
46
|
+
* edge — the same padding measured against two different shapes.
|
|
47
|
+
*/
|
|
48
|
+
--_padding-inline-visual: max(
|
|
49
|
+
var(--slotted-space-2, 0.25rem),
|
|
50
|
+
calc(var(--_padding-inline) - var(--slotted-space-2, 0.25rem))
|
|
51
|
+
);
|
|
52
|
+
/**
|
|
53
|
+
* The band the control waits behind, measured against the control rather
|
|
54
|
+
* than in pixels: a fixed length reads as a wash across a small button and
|
|
55
|
+
* as a glint across a wide one. Two heights is the tile, so a button that
|
|
56
|
+
* holds a phrase carries two or three bands rather than one long smear —
|
|
57
|
+
* a train crossing the control is what reads as waiting, and one band wide
|
|
58
|
+
* enough to cover it reads as a gradient someone left on.
|
|
59
|
+
*/
|
|
60
|
+
--_sweep-width: calc(var(--_height) * 2);
|
|
61
|
+
/**
|
|
62
|
+
* A band of the control's own ink over the control's own fill, and the two
|
|
63
|
+
* grounds do not take it the same way. A saturated fill is already far from
|
|
64
|
+
* the page, so the band has only the distance left between the fill and the
|
|
65
|
+
* label to work in and needs most of it; a bare variant lays the same ink
|
|
66
|
+
* straight on the page, where nothing dilutes it and half as much reads
|
|
67
|
+
* louder. Both numbers were read in `light`, `dark` and `high-contrast`
|
|
68
|
+
* rather than preferred: above these the filled band stops being a band and
|
|
69
|
+
* becomes a gloss over the fill, and the one tone whose label is dark turns
|
|
70
|
+
* it into a shadow.
|
|
71
|
+
*/
|
|
72
|
+
--_sweep-strength: 0.34;
|
|
73
|
+
|
|
74
|
+
align-items: center;
|
|
75
|
+
appearance: none;
|
|
76
|
+
background: transparent;
|
|
77
|
+
border: var(--slotted-control-border-width, 1px) solid transparent;
|
|
78
|
+
border-radius: var(--slotted-control-radius, 4px);
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
font-family: var(--slotted-control-font-family, ui-sans-serif, system-ui, sans-serif);
|
|
83
|
+
font-size: var(--_font-size);
|
|
84
|
+
font-weight: var(--slotted-control-font-weight, 600);
|
|
85
|
+
gap: var(--_gap);
|
|
86
|
+
justify-content: center;
|
|
87
|
+
letter-spacing: var(--slotted-control-letter-spacing, 0);
|
|
88
|
+
line-height: var(--slotted-control-line-height, 1);
|
|
89
|
+
/**
|
|
90
|
+
* A control never pushes past the container it was placed in, whatever it
|
|
91
|
+
* holds. With the label allowed to wrap and to break inside a word, this
|
|
92
|
+
* is what turns an unbreakable file name into a wrapped one instead of a
|
|
93
|
+
* horizontal scrollbar on the page.
|
|
94
|
+
*/
|
|
95
|
+
max-inline-size: 100%;
|
|
96
|
+
/**
|
|
97
|
+
* A minimum rather than a height, because the label wraps: a control that
|
|
98
|
+
* refuses to wrap pushes a long action past the edge of a narrow
|
|
99
|
+
* container.
|
|
100
|
+
*
|
|
101
|
+
* There is no block padding under it, and that is measured rather than
|
|
102
|
+
* preferred. A padding is added to the label's own line box, so at the
|
|
103
|
+
* `dense` density — where the small step is 24px and the medium 26px, and
|
|
104
|
+
* the line box is already 17.4px — four pixels of padding took both steps
|
|
105
|
+
* to the same 27.4 pixels. The density stopped meaning the height the
|
|
106
|
+
* theme states, and two steps of the scale collapsed into one. A wrapped
|
|
107
|
+
* label is kept off the border by the half-leading its own line height
|
|
108
|
+
* already carries, which is the same three pixels the padding was adding.
|
|
109
|
+
*/
|
|
110
|
+
min-block-size: var(--_height);
|
|
111
|
+
padding-block: 0;
|
|
112
|
+
padding-inline: var(--_padding-inline);
|
|
113
|
+
/* The loading indicator is placed against this box. */
|
|
114
|
+
position: relative;
|
|
115
|
+
text-align: center;
|
|
116
|
+
text-decoration: none;
|
|
117
|
+
/** A single-word difference between two lines is worse than a shorter first one. */
|
|
118
|
+
text-wrap: balance;
|
|
119
|
+
/* No tap delay waiting for a second tap the control cannot use. */
|
|
120
|
+
touch-action: manipulation;
|
|
121
|
+
transition:
|
|
122
|
+
background-color var(--slotted-control-transition-duration)
|
|
123
|
+
var(--slotted-control-transition-easing),
|
|
124
|
+
border-color var(--slotted-control-transition-duration)
|
|
125
|
+
var(--slotted-control-transition-easing),
|
|
126
|
+
box-shadow var(--slotted-control-transition-duration) var(--slotted-control-transition-easing),
|
|
127
|
+
color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing),
|
|
128
|
+
/* The press below moves the control, and the release eases it back. */ transform
|
|
129
|
+
var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
|
|
130
|
+
user-select: none;
|
|
131
|
+
/* A control written inside a sentence sits on the text's centre, not on its baseline. */
|
|
132
|
+
vertical-align: middle;
|
|
133
|
+
-webkit-tap-highlight-color: transparent;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* ── Tone: which of the five semantic colours the control speaks in ───── */
|
|
137
|
+
|
|
138
|
+
[data-ui='button'][data-tone='secondary'] {
|
|
139
|
+
--_solid: var(--slotted-tone-secondary-solid);
|
|
140
|
+
--_solid-hover: var(--slotted-tone-secondary-solid-hover);
|
|
141
|
+
--_solid-active: var(--slotted-tone-secondary-solid-active);
|
|
142
|
+
--_on-solid: var(--slotted-tone-secondary-on-solid);
|
|
143
|
+
--_text: var(--slotted-tone-secondary-text);
|
|
144
|
+
/**
|
|
145
|
+
* The one fill that cannot be its own edge: measured against the surface it
|
|
146
|
+
* sits on, `--slotted-tone-secondary-solid` is 1.23:1 in `light`, 1.25:1 in
|
|
147
|
+
* `dark` and 1.00:1 in `high-contrast`, where it is the page's own white. A
|
|
148
|
+
* neutral control with no edge at all is a control that cannot be found.
|
|
149
|
+
*
|
|
150
|
+
* The edge is the next darker step of the tone's own ramp — the active
|
|
151
|
+
* fill — and nothing stronger. It was the hover fill while a hover
|
|
152
|
+
* darkened; a hover now lightens (the rule is in the theme), and an edge
|
|
153
|
+
* lighter than what it encloses is no edge. The tone's `border` role was
|
|
154
|
+
* tried and is far too loud: a filled neutral button is a quiet control,
|
|
155
|
+
* and an edge that announces itself turns it into an outline button
|
|
156
|
+
* wearing a fill.
|
|
157
|
+
*/
|
|
158
|
+
--_edge: var(--_solid-active);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
[data-ui='button'][data-tone='success'] {
|
|
162
|
+
--_solid: var(--slotted-tone-success-solid);
|
|
163
|
+
--_solid-hover: var(--slotted-tone-success-solid-hover);
|
|
164
|
+
--_solid-active: var(--slotted-tone-success-solid-active);
|
|
165
|
+
--_on-solid: var(--slotted-tone-success-on-solid);
|
|
166
|
+
--_text: var(--slotted-tone-success-text);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
[data-ui='button'][data-tone='warning'] {
|
|
170
|
+
--_solid: var(--slotted-tone-warning-solid);
|
|
171
|
+
--_solid-hover: var(--slotted-tone-warning-solid-hover);
|
|
172
|
+
--_solid-active: var(--slotted-tone-warning-solid-active);
|
|
173
|
+
--_on-solid: var(--slotted-tone-warning-on-solid);
|
|
174
|
+
--_text: var(--slotted-tone-warning-text);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
[data-ui='button'][data-tone='danger'] {
|
|
178
|
+
--_solid: var(--slotted-tone-danger-solid);
|
|
179
|
+
--_solid-hover: var(--slotted-tone-danger-solid-hover);
|
|
180
|
+
--_solid-active: var(--slotted-tone-danger-solid-active);
|
|
181
|
+
--_on-solid: var(--slotted-tone-danger-on-solid);
|
|
182
|
+
--_text: var(--slotted-tone-danger-text);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ── Size: the control scale, one step of it per member instance ──────── */
|
|
186
|
+
|
|
187
|
+
[data-ui='button'][data-size='sm'] {
|
|
188
|
+
--_height: var(--slotted-control-height-sm, 30px);
|
|
189
|
+
--_padding-inline: var(--slotted-control-padding-inline-sm, 10px);
|
|
190
|
+
--_font-size: var(--slotted-control-font-size-sm, 12px);
|
|
191
|
+
--_icon-size: var(--slotted-control-icon-size-sm, 14px);
|
|
192
|
+
--_gap: var(--slotted-control-gap-sm, 6px);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
[data-ui='button'][data-size='lg'] {
|
|
196
|
+
--_height: var(--slotted-control-height-lg, 38px);
|
|
197
|
+
--_padding-inline: var(--slotted-control-padding-inline-lg, 16px);
|
|
198
|
+
--_font-size: var(--slotted-control-font-size-lg, 14px);
|
|
199
|
+
--_icon-size: var(--slotted-control-icon-size-lg, 18px);
|
|
200
|
+
--_gap: var(--slotted-control-gap-lg, 8px);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* ── Variant: how the tone is painted ──────────────────────────────────
|
|
205
|
+
*
|
|
206
|
+
* Three, not four. `soft` painted the tone's own surface and was removed at
|
|
207
|
+
* the visual review: a pale tinted fill read as a control on its way to
|
|
208
|
+
* being unavailable rather than as a quieter action, and three ways to
|
|
209
|
+
* paint one tone is one more than a page can tell apart at a glance. The
|
|
210
|
+
* `surface` role each tone carries belongs to the families a tinted
|
|
211
|
+
* rectangle is — `badge`, `tag`, `alert` — and this family no longer reads
|
|
212
|
+
* it.
|
|
213
|
+
*
|
|
214
|
+
* Every variant that is not `ghost` draws an edge, and the edge is the
|
|
215
|
+
* tone's `border` role rather than the fill repeated. On a filled control
|
|
216
|
+
* that reads as a rim a shade away from the fill; on the neutral tone,
|
|
217
|
+
* whose solid and surface are the same pale grey, it is the whole
|
|
218
|
+
* difference between a secondary action and a disabled one.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* ── Elevation, and the one variant that has none ──────────────────────
|
|
223
|
+
*
|
|
224
|
+
* `--slotted-control-shadow` is, in the token foundation's own words, "the
|
|
225
|
+
* shadow a framed control carries at rest": it separates a control from the
|
|
226
|
+
* ground it sits on so the border can soften. That makes it a property of
|
|
227
|
+
* the frame, not of the family, and it is written on the two variants that
|
|
228
|
+
* draw one.
|
|
229
|
+
*
|
|
230
|
+
* `ghost` draws neither a fill nor an edge. It is a label the pointer can
|
|
231
|
+
* press, and a label that casts a shadow is a rectangle nobody drew — the
|
|
232
|
+
* control announces a box on a ground it is supposed to sit flat against,
|
|
233
|
+
* and a toolbar of ghost actions turns into a row of invisible cards. The
|
|
234
|
+
* sheet carried the shadow on the base rule for one release and every
|
|
235
|
+
* member inherited it, which is the defect this split exists to close.
|
|
236
|
+
*/
|
|
237
|
+
[data-ui='button'][data-variant='solid'] {
|
|
238
|
+
background: var(--_solid, ButtonFace);
|
|
239
|
+
border-color: var(--_edge, ButtonBorder);
|
|
240
|
+
box-shadow: var(--slotted-control-shadow, none);
|
|
241
|
+
color: var(--_on-solid, ButtonText);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* The edge of an outline button is neutral and its label carries the tone.
|
|
246
|
+
* A saturated border on all five tones turned a row of actions into a row
|
|
247
|
+
* of status chips: the eye read five different colours of rectangle before
|
|
248
|
+
* it read a single word. The edge is the control step, the one a field
|
|
249
|
+
* carries at rest: reading `--slotted-border-strong` here made the outline
|
|
250
|
+
* button the darkest line on the form, and the foundation now keeps that
|
|
251
|
+
* step for the line between two surfaces.
|
|
252
|
+
*/
|
|
253
|
+
[data-ui='button'][data-variant='outline'] {
|
|
254
|
+
background: transparent;
|
|
255
|
+
border-color: var(--slotted-control-border-color, ButtonBorder);
|
|
256
|
+
box-shadow: var(--slotted-control-shadow, none);
|
|
257
|
+
color: var(--_text, ButtonText);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
[data-ui='button'][data-variant='ghost'] {
|
|
261
|
+
background: transparent;
|
|
262
|
+
border-color: transparent;
|
|
263
|
+
color: var(--_text, ButtonText);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The half of the pair for a variant with no fill of its own. `secondary` is
|
|
268
|
+
* the third case for the same reason it is the exception to every other fill
|
|
269
|
+
* rule: its solid step is the page's own colour, so it takes the band the way
|
|
270
|
+
* a bare control does.
|
|
271
|
+
*/
|
|
272
|
+
[data-ui='button'][data-variant='outline'],
|
|
273
|
+
[data-ui='button'][data-variant='ghost'],
|
|
274
|
+
[data-ui='button'][data-variant='solid'][data-tone='secondary'] {
|
|
275
|
+
--_sweep-strength: 0.18;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* ── Pressed ───────────────────────────────────────────────────────────
|
|
280
|
+
*
|
|
281
|
+
* A toggle that is on is a selected thing, and it is painted the way every
|
|
282
|
+
* selected thing in the foundation is (R1): the accent selection ground
|
|
283
|
+
* under the accent text, inside the accent edge — whatever tone and variant
|
|
284
|
+
* the toggle was written as, because "on" is one colour across the system
|
|
285
|
+
* and not a shade of each control's own. It took the tone's solid fill
|
|
286
|
+
* before this, which made a neutral toggle that was on the same rectangle
|
|
287
|
+
* as one being pressed (`#e2e8f0` for both in `light`) and made a toggle
|
|
288
|
+
* that was on look like a primary action.
|
|
289
|
+
*
|
|
290
|
+
* The ground is the tone's `subtle-active` step: the same value as its
|
|
291
|
+
* `surface` in `light` and `dark`, and in `high-contrast` the one tinted
|
|
292
|
+
* step the scheme keeps (`#d3e0fa`) where the surface is the page's white.
|
|
293
|
+
* It sits above hover and below loading and disabled, which is the order
|
|
294
|
+
* `STATE_PRECEDENCE` sets.
|
|
295
|
+
*/
|
|
296
|
+
[data-ui='button'][data-pressed]:not(:disabled):not([data-disabled]) {
|
|
297
|
+
background: var(--slotted-tone-accent-subtle-active, Highlight);
|
|
298
|
+
border-color: var(--slotted-tone-accent-border, Highlight);
|
|
299
|
+
/* A ghost toggle that is on has taken a fill and an edge, so it is a framed
|
|
300
|
+
control and carries what one carries. Without this the same toggle would
|
|
301
|
+
be lifted or flat depending on the variant it was written as. */
|
|
302
|
+
box-shadow: var(--slotted-control-shadow, none);
|
|
303
|
+
color: var(--slotted-tone-accent-text, HighlightText);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* ── Hover and active, held back for every stronger state ──────────────
|
|
308
|
+
*
|
|
309
|
+
* A hovered control changes one thing, its fill, in one step of the
|
|
310
|
+
* control's transition. A filled control lightens: `--slotted-tone-*-solid-
|
|
311
|
+
* hover` is the fill moved a share of the way toward white, and the theme
|
|
312
|
+
* derives it from the fill by one rule rather than choosing it beside it. A
|
|
313
|
+
* bare control takes the neutral hover ground whatever its tone, because a
|
|
314
|
+
* tinted ground under an outline or ghost action read as five different
|
|
315
|
+
* status chips before it read as five hovered buttons; the tone stays in
|
|
316
|
+
* the label, where it was.
|
|
317
|
+
*
|
|
318
|
+
* A press answers at once and releases at the control's own pace. The
|
|
319
|
+
* active rules set the transition into the state to the instant step,
|
|
320
|
+
* so the fill, the shadow and the shift land on the same frame the button
|
|
321
|
+
* went down on; the base rule's duration is what the release eases out
|
|
322
|
+
* with, because a transition's timing is read from the state being
|
|
323
|
+
* entered. A filled control drops one hairline and loses its shadow —
|
|
324
|
+
* Primer's press, chosen over Radix's brightness filter: the control that
|
|
325
|
+
* was lifted off the page is put down on it. A bare control has no shadow
|
|
326
|
+
* to lose and nothing to put down, so it takes the darker neutral ground
|
|
327
|
+
* and nothing moves.
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
[data-ui='button'][data-variant='solid']:hover:not(:disabled):not([data-disabled]):not(
|
|
331
|
+
[data-loading]
|
|
332
|
+
):not([data-pressed]) {
|
|
333
|
+
background: var(--_solid-hover, Highlight);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
[data-ui='button'][data-variant='solid']:active:not(:disabled):not([data-disabled]):not(
|
|
337
|
+
[data-loading]
|
|
338
|
+
):not([data-pressed]) {
|
|
339
|
+
background: var(--_solid-active, Highlight);
|
|
340
|
+
box-shadow: none;
|
|
341
|
+
transform: translateY(var(--slotted-border-width-thin, 1px));
|
|
342
|
+
transition-duration: var(--slotted-duration-instant);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
[data-ui='button'][data-variant='outline']:hover:not(:disabled):not([data-disabled]):not(
|
|
346
|
+
[data-loading]
|
|
347
|
+
):not([data-pressed]),
|
|
348
|
+
[data-ui='button'][data-variant='ghost']:hover:not(:disabled):not([data-disabled]):not(
|
|
349
|
+
[data-loading]
|
|
350
|
+
):not([data-pressed]) {
|
|
351
|
+
background: var(--slotted-tone-secondary-subtle-hover, ButtonFace);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
[data-ui='button'][data-variant='outline']:active:not(:disabled):not([data-disabled]):not(
|
|
355
|
+
[data-loading]
|
|
356
|
+
):not([data-pressed]),
|
|
357
|
+
[data-ui='button'][data-variant='ghost']:active:not(:disabled):not([data-disabled]):not(
|
|
358
|
+
[data-loading]
|
|
359
|
+
):not([data-pressed]) {
|
|
360
|
+
background: var(--slotted-tone-secondary-subtle-active, ButtonFace);
|
|
361
|
+
transition-duration: var(--slotted-duration-instant);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* A toggle that is already on still answers the pointer, on the accent
|
|
366
|
+
* ground's own hover step and never on the neutral one a toggle that is off
|
|
367
|
+
* hovers with — the two states share no surface in any of theirs.
|
|
368
|
+
*/
|
|
369
|
+
[data-ui='button'][data-pressed]:hover:not(:disabled):not([data-disabled]):not([data-loading]) {
|
|
370
|
+
background: var(--slotted-tone-accent-subtle-hover, Highlight);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
[data-ui='button'][data-pressed]:active:not(:disabled):not([data-disabled]):not([data-loading]) {
|
|
374
|
+
background: var(--slotted-tone-accent-subtle-active, Highlight);
|
|
375
|
+
transition-duration: var(--slotted-duration-instant);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* ── Focus ────────────────────────────────────────────────────────────── */
|
|
379
|
+
|
|
380
|
+
[data-ui='button']:focus-visible {
|
|
381
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
382
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* ── Disabled ──────────────────────────────────────────────────────────
|
|
387
|
+
*
|
|
388
|
+
* Per variant, because a flat control that fills with grey on the way to
|
|
389
|
+
* being unavailable reads as a different control rather than as the same
|
|
390
|
+
* one unavailable. A ghost button stays flat and loses only its colour.
|
|
391
|
+
*
|
|
392
|
+
* `[data-disabled]` rather than `:disabled` alone, because a control that
|
|
393
|
+
* keeps its place in the tab sequence carries `aria-disabled` and no
|
|
394
|
+
* `disabled` attribute; the attribute the contract declares is the one that
|
|
395
|
+
* is always there.
|
|
396
|
+
*/
|
|
397
|
+
|
|
398
|
+
[data-ui='button']:disabled,
|
|
399
|
+
[data-ui='button'][data-disabled] {
|
|
400
|
+
/* Nothing a person cannot reach is lifted off the page — the rule
|
|
401
|
+
`text-field` states for the same shadow. */
|
|
402
|
+
box-shadow: none;
|
|
403
|
+
color: var(--slotted-disabled-foreground, GrayText);
|
|
404
|
+
cursor: not-allowed;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
[data-ui='button'][data-variant='solid']:disabled,
|
|
408
|
+
[data-ui='button'][data-variant='solid'][data-disabled] {
|
|
409
|
+
background: var(--slotted-disabled-background, ButtonFace);
|
|
410
|
+
/**
|
|
411
|
+
* The disabled border rather than the disabled surface, because in the
|
|
412
|
+
* `high-contrast` scheme that surface is the page's white and the control
|
|
413
|
+
* would lose its edge exactly where an edge matters most.
|
|
414
|
+
*/
|
|
415
|
+
border-color: var(--slotted-disabled-border, ButtonBorder);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
[data-ui='button'][data-variant='outline']:disabled,
|
|
419
|
+
[data-ui='button'][data-variant='outline'][data-disabled] {
|
|
420
|
+
background: transparent;
|
|
421
|
+
border-color: var(--slotted-disabled-border, GrayText);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* A toggle that is on and unavailable takes the disabled fill and the
|
|
426
|
+
* disabled edge, like any other unavailable control. What still says it is on
|
|
427
|
+
* is `aria-pressed`, which the platform announces whether or not the control
|
|
428
|
+
* can be operated.
|
|
429
|
+
*/
|
|
430
|
+
[data-ui='button'][data-pressed]:disabled,
|
|
431
|
+
[data-ui='button'][data-pressed][data-disabled] {
|
|
432
|
+
background: var(--slotted-disabled-background, ButtonFace);
|
|
433
|
+
border-color: var(--slotted-disabled-border, GrayText);
|
|
434
|
+
color: var(--slotted-disabled-foreground, GrayText);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* ── Unavailable, and still reachable ──────────────────────────────────
|
|
439
|
+
*
|
|
440
|
+
* Being unavailable and being unreachable are two decisions, and the sheet
|
|
441
|
+
* painted them as one: a control that stays in the tab sequence carrying
|
|
442
|
+
* `aria-disabled` — because it holds a tooltip, an explanation, a place in a
|
|
443
|
+
* toolbar — wore the same grey as one the platform took out, and a disabled
|
|
444
|
+
* accent action was the same rectangle as a resting neutral one (the fill,
|
|
445
|
+
* the edge and the ground of `solid secondary` are the disabled tokens'
|
|
446
|
+
* values in `light`).
|
|
447
|
+
*
|
|
448
|
+
* A control a person can reach has to be read, so its label is muted text
|
|
449
|
+
* rather than disabled text: `--slotted-text-muted` on the neutral subtle
|
|
450
|
+
* ground is 4.97:1 in `light`, 6.91:1 in `dark` and 11.76:1 in
|
|
451
|
+
* `high-contrast`, where the disabled pair is held to 3:1 and sits at 3.86.
|
|
452
|
+
* The ground is the quiet neutral step and not the neutral fill, because the
|
|
453
|
+
* neutral fill is what a secondary action rests on; the edge is the subtle
|
|
454
|
+
* border, one step quieter than any control at rest; nothing is lifted and
|
|
455
|
+
* nothing answers the pointer. `:not(:disabled)` is the whole difference:
|
|
456
|
+
* a natively disabled control keeps the disabled tokens above, and a ghost
|
|
457
|
+
* stays flat and takes only the text.
|
|
458
|
+
*/
|
|
459
|
+
[data-ui='button'][data-disabled]:not(:disabled) {
|
|
460
|
+
color: var(--slotted-text-muted, GrayText);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
[data-ui='button'][data-variant='solid'][data-disabled]:not(:disabled),
|
|
464
|
+
[data-ui='button'][data-pressed][data-disabled]:not(:disabled) {
|
|
465
|
+
background: var(--slotted-tone-secondary-subtle-hover, ButtonFace);
|
|
466
|
+
border-color: var(--slotted-border-subtle, GrayText);
|
|
467
|
+
color: var(--slotted-text-muted, GrayText);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
[data-ui='button'][data-variant='outline'][data-disabled]:not(:disabled) {
|
|
471
|
+
border-color: var(--slotted-border-subtle, GrayText);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* ── Loading ───────────────────────────────────────────────────────────
|
|
476
|
+
*
|
|
477
|
+
* The ring is what the control falls back to, and the sweep below is what it
|
|
478
|
+
* shows when the page can carry one. Both are written, in that order, so an
|
|
479
|
+
* engine that drops the sweep is left with a state rather than with nothing.
|
|
480
|
+
*
|
|
481
|
+
* The ring takes two shapes, decided by what the control already holds.
|
|
482
|
+
*
|
|
483
|
+
* A control with a start or end visual keeps its label and gives the ring
|
|
484
|
+
* that one box: the wait is legible, the action stays readable, and the box
|
|
485
|
+
* was already the size of the ring. Primer takes the same decision, and it
|
|
486
|
+
* is the one that stops a loading button from being a coloured rectangle
|
|
487
|
+
* with nothing in it.
|
|
488
|
+
*
|
|
489
|
+
* A control with no visual has nowhere to put the ring but over its own
|
|
490
|
+
* label, so the label goes to `opacity: 0` — in the flow, so the control
|
|
491
|
+
* cannot change size, and in the accessibility tree, so the name it
|
|
492
|
+
* supplies survives the wait and combines with the indicator's own.
|
|
493
|
+
*/
|
|
494
|
+
|
|
495
|
+
[data-ui='button'][data-loading] {
|
|
496
|
+
cursor: progress;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
[data-ui='button'][data-loading]:has(> [data-part='indicator']) > :not([data-part='indicator']) {
|
|
500
|
+
opacity: 0;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* The ring is the `spinner` sheet's: one arc, drawn once, for every family
|
|
505
|
+
* that waits. This family only says how big it is — the glyph box of its
|
|
506
|
+
* own step — and where it sits.
|
|
507
|
+
*/
|
|
508
|
+
[data-ui='button'] [data-part='indicator'] {
|
|
509
|
+
--slotted-spinner-size: var(--_icon-size);
|
|
510
|
+
|
|
511
|
+
flex: none;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/* Only the ring that has no visual slot to sit in is placed against the box. */
|
|
515
|
+
[data-ui='button'] > [data-part='indicator'] {
|
|
516
|
+
inset: 0;
|
|
517
|
+
/* `inset: 0` with a definite size and automatic margins centres on both
|
|
518
|
+
axes without naming a physical side. */
|
|
519
|
+
margin: auto;
|
|
520
|
+
position: absolute;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* ── The sweep ─────────────────────────────────────────────────────────
|
|
525
|
+
*
|
|
526
|
+
* A band of the control's own ink crosses it for as long as it waits. It
|
|
527
|
+
* replaces the ring on every control that has words in it, and what it buys
|
|
528
|
+
* is the words: the ring had to be drawn over the label of a control with no
|
|
529
|
+
* visual slot, so the reader lost the name of the action they had just
|
|
530
|
+
* pressed. The band is painted behind everything instead, so the label and
|
|
531
|
+
* the glyph both stay where they were and nothing in the control moves.
|
|
532
|
+
*
|
|
533
|
+
* The ring stays in the document. It is the element that carries
|
|
534
|
+
* `progressbar` and the name of the wait, so it steps out of the flow rather
|
|
535
|
+
* than out of the tree, and the glyph it was standing in for takes its own
|
|
536
|
+
* width back.
|
|
537
|
+
*
|
|
538
|
+
* An icon-only control keeps the ring drawn. There is no label for a band to
|
|
539
|
+
* spare, and a sweep across a box the size of one glyph is a flicker rather
|
|
540
|
+
* than a direction.
|
|
541
|
+
*
|
|
542
|
+
* The band waits for the ring to be in the document, not for
|
|
543
|
+
* `data-loading`: the refusal starts at once, and `loadingDelay` holds back
|
|
544
|
+
* every picture of the wait, so a wait that ends inside the delay shows none.
|
|
545
|
+
*
|
|
546
|
+
* `currentColor` is what makes the band answer every tone and every variant
|
|
547
|
+
* without a token of its own: it is the ink the control is already painting
|
|
548
|
+
* its own label with, so it contrasts with that control's ground by
|
|
549
|
+
* construction — the on-tone colour over a filled one, the tone itself over
|
|
550
|
+
* a bare one, and whatever the scheme remapped either to.
|
|
551
|
+
*/
|
|
552
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
553
|
+
@media not (forced-colors: active) {
|
|
554
|
+
[data-ui='button']:not([data-shape])[data-loading]:has([data-part='indicator'])::after {
|
|
555
|
+
/**
|
|
556
|
+
* The tile is crossed once per cycle, so the pair sets the speed the
|
|
557
|
+
* band travels rather than the time it takes: two heights per loop
|
|
558
|
+
* step is a rate the eye reads as work in progress at every size.
|
|
559
|
+
*/
|
|
560
|
+
animation: slotted-button-sweep var(--slotted-duration-loop) linear infinite;
|
|
561
|
+
background-image: linear-gradient(to right, transparent, currentColor 40%, transparent 80%);
|
|
562
|
+
/* One tile, travelled exactly once per cycle, is a loop with no seam. */
|
|
563
|
+
background-repeat: repeat-x;
|
|
564
|
+
background-size: var(--_sweep-width) 100%;
|
|
565
|
+
border-radius: inherit;
|
|
566
|
+
content: '';
|
|
567
|
+
inset: 0;
|
|
568
|
+
opacity: var(--_sweep-strength);
|
|
569
|
+
/* The band is over the label and must not take the press away from it. */
|
|
570
|
+
pointer-events: none;
|
|
571
|
+
position: absolute;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* A band that travels against the reading direction reads as undoing
|
|
576
|
+
* rather than as progressing, so it is mirrored rather than reused.
|
|
577
|
+
*/
|
|
578
|
+
[data-ui='button']:not([data-shape])[data-loading]:has([data-part='indicator']):dir(
|
|
579
|
+
rtl
|
|
580
|
+
)::after {
|
|
581
|
+
animation-direction: reverse;
|
|
582
|
+
background-image: linear-gradient(to left, transparent, currentColor 40%, transparent 80%);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
[data-ui='button']:not([data-shape])[data-loading]:has(> [data-part='indicator'])
|
|
586
|
+
> :not([data-part='indicator']) {
|
|
587
|
+
opacity: 1;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
[data-ui='button']:not([data-shape])[data-loading] [data-part='indicator'] {
|
|
591
|
+
opacity: 0;
|
|
592
|
+
position: absolute;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/* ── Parts ────────────────────────────────────────────────────────────── */
|
|
598
|
+
|
|
599
|
+
[data-ui='button'] [data-part='start'],
|
|
600
|
+
[data-ui='button'] [data-part='end'],
|
|
601
|
+
[data-ui='button'] [data-part='icon'] {
|
|
602
|
+
align-items: center;
|
|
603
|
+
block-size: var(--_icon-size);
|
|
604
|
+
box-sizing: border-box;
|
|
605
|
+
display: inline-flex;
|
|
606
|
+
flex: none;
|
|
607
|
+
font-size: var(--_icon-size);
|
|
608
|
+
inline-size: var(--_icon-size);
|
|
609
|
+
justify-content: center;
|
|
610
|
+
line-height: 1;
|
|
611
|
+
/* The ring that stands in for this glyph is placed against this box. */
|
|
612
|
+
position: relative;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
[data-ui='button'] [data-part='start'] > *:not([data-part='indicator']),
|
|
616
|
+
[data-ui='button'] [data-part='end'] > *:not([data-part='indicator']),
|
|
617
|
+
[data-ui='button'] [data-part='icon'] > *:not([data-part='indicator']) {
|
|
618
|
+
block-size: 100%;
|
|
619
|
+
display: block;
|
|
620
|
+
inline-size: 100%;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* The label carries a wrapping line height of its own: the control's line
|
|
625
|
+
* height is 1, which centres a single line inside the box and crushes the
|
|
626
|
+
* second one. A token longer than the container breaks instead of escaping.
|
|
627
|
+
*/
|
|
628
|
+
[data-ui='button'] [data-part='label'] {
|
|
629
|
+
line-height: var(--slotted-line-height-sm, 1.45);
|
|
630
|
+
min-inline-size: 0;
|
|
631
|
+
overflow-wrap: anywhere;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* A flex container centres the line box, and a line box is not symmetric
|
|
636
|
+
* around the letters inside it. Measured on this family, the word sat 1.2
|
|
637
|
+
* pixels above the centre of the glyph beside it at every step of every
|
|
638
|
+
* density except the two where the rounding of the font size made it 0.92
|
|
639
|
+
* and 0.16 — the label and the icon were centred against different things.
|
|
640
|
+
* `text-box` trims the line to the cap and the baseline, so what the flex
|
|
641
|
+
* container centres is the ink, and the offset is 0 everywhere.
|
|
642
|
+
*
|
|
643
|
+
* The trim is paid for rather than taken. Trimming a label that wraps also
|
|
644
|
+
* takes the half-leading off the top and bottom of the whole block, which is
|
|
645
|
+
* the only thing keeping a wrapped word off the border — measured, a
|
|
646
|
+
* two-line control lost 8.39 pixels of height and its descenders fell two
|
|
647
|
+
* pixels below the edge. The padding gives that leading back symmetrically,
|
|
648
|
+
* so the ink stays centred and the control is the height it was: 36.77
|
|
649
|
+
* pixels against 36.78 for the same two lines.
|
|
650
|
+
*
|
|
651
|
+
* Both terms are proportions of the type rather than steps of a scale.
|
|
652
|
+
* `0.75em` is the cap height the trim leaves standing, measured on the UI
|
|
653
|
+
* font at every step; the line height is the token the label already reads.
|
|
654
|
+
* A theme that changes the line height moves the padding with it, and one
|
|
655
|
+
* that changes the font moves the clearance of a wrapped word by a fraction
|
|
656
|
+
* of a pixel, which is the only thing the term governs.
|
|
657
|
+
*/
|
|
658
|
+
@supports (text-box: trim-both cap alphabetic) {
|
|
659
|
+
[data-ui='button'] [data-part='label'] {
|
|
660
|
+
padding-block: calc((var(--slotted-line-height-sm, 1.45) * 1em - 0.75em) / 2);
|
|
661
|
+
text-box: trim-both cap alphabetic;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* The side that holds a glyph is inset less than the side that holds a
|
|
667
|
+
* letter. An icon-only control is exempt, because both of its sides hold
|
|
668
|
+
* the same glyph and its inset is zero.
|
|
669
|
+
*/
|
|
670
|
+
[data-ui='button']:not([data-shape]):has(> [data-part='start']) {
|
|
671
|
+
padding-inline-start: var(--_padding-inline-visual);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
[data-ui='button']:not([data-shape]):has(> [data-part='end']) {
|
|
675
|
+
padding-inline-end: var(--_padding-inline-visual);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/* ── Inline size ──────────────────────────────────────────────────────── */
|
|
679
|
+
|
|
680
|
+
[data-ui='button'][data-full-width] {
|
|
681
|
+
inline-size: 100%;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* An icon-only control is square from the control height, so its visual box
|
|
686
|
+
* and its target are the same box. The two minimums are the WCAG 2.2 floor
|
|
687
|
+
* stated by the family rather than inferred from the theme: a target this
|
|
688
|
+
* small in one axis is the family's to correct, and theme validation only
|
|
689
|
+
* guarantees the block axis.
|
|
690
|
+
*/
|
|
691
|
+
[data-ui='button'][data-shape] {
|
|
692
|
+
aspect-ratio: 1;
|
|
693
|
+
inline-size: var(--_height);
|
|
694
|
+
min-block-size: max(var(--_height), var(--slotted-target-min, 24px));
|
|
695
|
+
min-inline-size: var(--slotted-target-min, 24px);
|
|
696
|
+
padding-block: 0;
|
|
697
|
+
padding-inline: 0;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
[data-ui='button'][data-shape='circle'] {
|
|
701
|
+
border-radius: var(--slotted-radius-full, 9999px);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* ── Forced colours ────────────────────────────────────────────────────
|
|
706
|
+
*
|
|
707
|
+
* The theme remaps every token it owns, so this block exists only for what
|
|
708
|
+
* the family paints itself: the transparent border of `ghost` and of a
|
|
709
|
+
* control at rest, which leaves it with no edge at all when the system
|
|
710
|
+
* replaces the palette. `Highlight` on a pressed toggle is the same repair
|
|
711
|
+
* for a fill the system has flattened.
|
|
712
|
+
*/
|
|
713
|
+
@media (forced-colors: active) {
|
|
714
|
+
[data-ui='button'] {
|
|
715
|
+
border-color: ButtonBorder;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
[data-ui='button'][data-pressed] {
|
|
719
|
+
border-color: Highlight;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
[data-ui='button']:disabled,
|
|
723
|
+
[data-ui='button'][data-disabled] {
|
|
724
|
+
border-color: GrayText;
|
|
725
|
+
color: GrayText;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* A frozen ring still communicates waiting. The spinner sheet stops itself
|
|
730
|
+
* under reduced motion; it is restated here because this is the family that
|
|
731
|
+
* shows the wait, and the sweep above is the loop this sheet owns.
|
|
732
|
+
*/
|
|
733
|
+
@media (prefers-reduced-motion: reduce) {
|
|
734
|
+
[data-ui='button'] [data-part='indicator'] {
|
|
735
|
+
animation: none;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* The band moves by exactly one tile of a repeating background, so the frame
|
|
742
|
+
* the loop ends on is the frame it started from and there is no seam. Moving
|
|
743
|
+
* the background rather than the box is what keeps it inside the control
|
|
744
|
+
* without a second element to clip it.
|
|
745
|
+
*/
|
|
746
|
+
@keyframes slotted-button-sweep {
|
|
747
|
+
from {
|
|
748
|
+
background-position-x: calc(-1 * var(--_sweep-width));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
to {
|
|
752
|
+
background-position-x: 0;
|
|
753
|
+
}
|
|
754
|
+
}
|