@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,943 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* ── The region, and the messages in it ──────────────────────────────────
|
|
4
|
+
*
|
|
5
|
+
* Transient feedback about something that already happened: a tone, a mark,
|
|
6
|
+
* a sentence, sometimes a control that acts on it, and a timer.
|
|
7
|
+
*
|
|
8
|
+
* ┌─ root ─ section, role="region", named ──────────────┐
|
|
9
|
+
* │ ┌─ list ─ ol ────────────────────────────────────┐ │
|
|
10
|
+
* │ │ ┌─ item ─ li ────────────────────────────────┐ │ │
|
|
11
|
+
* │ │ │ ◆ icon │ content ▸ title │ ✕ │ │ │
|
|
12
|
+
* │ │ │ │ description │close │ │ │
|
|
13
|
+
* │ │ │ │ actions │ │ │ │
|
|
14
|
+
* │ │ │ ▁▁▁▁▁▁▁▁ progress ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ │ │ │
|
|
15
|
+
* │ │ └────────────────────────────────────────────┘ │ │
|
|
16
|
+
* │ └────────────────────────────────────────────────┘ │
|
|
17
|
+
* └─────────────────────────────────────────────────────┘
|
|
18
|
+
*
|
|
19
|
+
* ── The root is the region and not a message ────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* A message cannot own the corner of the screen. The region does, and it is
|
|
22
|
+
* the part that carries the family's identity, the placement, the container
|
|
23
|
+
* query and the accessible name a reader finds it by. `list` is a second
|
|
24
|
+
* element because `container-type: inline-size` makes an element a query
|
|
25
|
+
* container for its descendants and not for itself — the decision
|
|
26
|
+
* `docs/build-briefing.md` records, and the split `stepper` already had.
|
|
27
|
+
*
|
|
28
|
+
* ── The region takes no pointer ─────────────────────────────────────────
|
|
29
|
+
*
|
|
30
|
+
* A box pinned over a page's content must not eat a press meant for the
|
|
31
|
+
* page. Every surveyed implementation does this and none of them states it,
|
|
32
|
+
* which is how a consumer discovers the rule by finding a dead corner of
|
|
33
|
+
* their own application.
|
|
34
|
+
*
|
|
35
|
+
* ── Nothing here is a live region ───────────────────────────────────────
|
|
36
|
+
*
|
|
37
|
+
* The announcement is made by `packages/core/src/live-announcer`, and the
|
|
38
|
+
* reason is in `specs/components/toast/contract.json` under `departures`.
|
|
39
|
+
* The stylesheet's part in it is that no rule assumes a role: the same
|
|
40
|
+
* picture is drawn whether a reader hears the words or reads them.
|
|
41
|
+
*/
|
|
42
|
+
[data-ui='toast'][data-part='root'] {
|
|
43
|
+
/**
|
|
44
|
+
* The distance from the edge of the screen, and the greatest width a
|
|
45
|
+
* message is allowed. The width is the smaller of the two, so at 320
|
|
46
|
+
* pixels and at 200% zoom the region is the screen less two offsets rather
|
|
47
|
+
* than a stated width running off the side.
|
|
48
|
+
*/
|
|
49
|
+
--_offset: var(--slotted-toast-offset, var(--slotted-space-6, 1rem));
|
|
50
|
+
--_measure: var(--slotted-toast-measure, 24rem);
|
|
51
|
+
/**
|
|
52
|
+
* Twelve pixels rather than eight, and the elevation a step below the one
|
|
53
|
+
* a single message wanted. Read as a run of five, `--slotted-shadow-lg` at
|
|
54
|
+
* an eight-pixel gap laid each message's shadow across the top of the next
|
|
55
|
+
* one, and the column read as five boxes with dirt between them. The pair
|
|
56
|
+
* is one decision: the gap is what a shadow needs to fall into, and the
|
|
57
|
+
* shadow is what says the message is not part of the page under it.
|
|
58
|
+
*/
|
|
59
|
+
--_gap: var(--slotted-toast-gap, var(--slotted-space-5, 0.75rem));
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* How far a collapsed message behind the front one peeks out, and how much
|
|
63
|
+
* of its ink each step back gives up. The fade is a bare number rather than
|
|
64
|
+
* a length because it multiplies an index, and both are tokens so a theme
|
|
65
|
+
* that wants a flatter stack has one name to set for each.
|
|
66
|
+
*/
|
|
67
|
+
--_stack-step: var(--slotted-toast-stack-step, var(--slotted-space-3, 0.375rem));
|
|
68
|
+
--_stack-fade: var(--slotted-toast-stack-fade, 0.15);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Where a message comes from. It is the block axis in both directions: a
|
|
72
|
+
* message at the block end rises into place and one at the block start
|
|
73
|
+
* drops in, which is the one motion that says *this arrived* rather than
|
|
74
|
+
* *this moved*.
|
|
75
|
+
*/
|
|
76
|
+
--_enter-shift: var(--slotted-space-7, 1.25rem);
|
|
77
|
+
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
container-name: slotted-toast;
|
|
80
|
+
container-type: inline-size;
|
|
81
|
+
inline-size: min(var(--_measure), calc(100% - 2 * var(--_offset)));
|
|
82
|
+
/* The single stack, above every overlay: a message about what just
|
|
83
|
+
happened has to be readable over the dialog that caused it. */
|
|
84
|
+
position: fixed;
|
|
85
|
+
z-index: var(--slotted-z-index-toast, 1200);
|
|
86
|
+
|
|
87
|
+
pointer-events: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The region rendered inside a panel rather than against the screen. It is
|
|
92
|
+
* the axis Fluent calls `inline`, and it is what makes the family
|
|
93
|
+
* demonstrable on a documentation page: everything else about it is the
|
|
94
|
+
* same, including the placement, which now names the edges of the panel.
|
|
95
|
+
*/
|
|
96
|
+
[data-ui='toast'][data-part='root'][data-attach='inline'] {
|
|
97
|
+
position: absolute;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ── Placement ─────────────────────────────────────────────────────────
|
|
101
|
+
*
|
|
102
|
+
* Six values, and every one of them written in logical properties, so
|
|
103
|
+
* `bottom-end` is the bottom right of an English page and the bottom left of
|
|
104
|
+
* an Arabic one. Sonner's `bottom-right` is `bottom-right` in both.
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
[data-ui='toast'][data-part='root'][data-placement^='top'] {
|
|
108
|
+
inset-block-start: var(--_offset);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[data-ui='toast'][data-part='root'][data-placement^='bottom'] {
|
|
112
|
+
inset-block-end: var(--_offset);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
[data-ui='toast'][data-part='root'][data-placement$='-start'] {
|
|
116
|
+
inset-inline-start: var(--_offset);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
[data-ui='toast'][data-part='root'][data-placement$='-end'] {
|
|
120
|
+
inset-inline-end: var(--_offset);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Centred by giving the box both insets and letting the automatic margins
|
|
125
|
+
* divide what is left. It is the one centring that needs no transform, so
|
|
126
|
+
* the transform stays free for the enter, the exit and the swipe.
|
|
127
|
+
*/
|
|
128
|
+
[data-ui='toast'][data-part='root'][data-placement='top'],
|
|
129
|
+
[data-ui='toast'][data-part='root'][data-placement='bottom'] {
|
|
130
|
+
inset-inline: var(--_offset);
|
|
131
|
+
margin-inline: auto;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ── The list ──────────────────────────────────────────────────────────
|
|
135
|
+
*
|
|
136
|
+
* Newest first in the document, so `F6` and the reading order both reach the
|
|
137
|
+
* message that just arrived before the ones the reader has already seen. A
|
|
138
|
+
* region at the block end reverses the visual order instead of the document
|
|
139
|
+
* order, which puts the newest nearest the edge of the screen — where the
|
|
140
|
+
* reader's eye already is — without moving it in the accessibility tree.
|
|
141
|
+
*/
|
|
142
|
+
[data-ui='toast'] > [data-part='list'] {
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
gap: var(--_gap);
|
|
146
|
+
list-style: none;
|
|
147
|
+
margin: 0;
|
|
148
|
+
padding: 0;
|
|
149
|
+
pointer-events: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
[data-ui='toast'][data-placement^='bottom'] > [data-part='list'] {
|
|
153
|
+
flex-direction: column-reverse;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* While the reader is in the region, the gaps between messages belong to
|
|
158
|
+
* it. The list takes no pointer, so a pointer crossing the twelve pixels
|
|
159
|
+
* between two messages left the region: the pause cleared, the timers ran
|
|
160
|
+
* again, and a pile opened by the pointer closed under it. The list takes
|
|
161
|
+
* the pointer only while the region is expanded, which only a pointer or
|
|
162
|
+
* focus already inside a message can cause, so no press meant for the page
|
|
163
|
+
* lands on an empty region.
|
|
164
|
+
*/
|
|
165
|
+
[data-ui='toast'][data-expanded] > [data-part='list'] {
|
|
166
|
+
pointer-events: auto;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ── One message ───────────────────────────────────────────────────────
|
|
170
|
+
*
|
|
171
|
+
* The grid `alert` settled: the mark, the words, and the control that puts
|
|
172
|
+
* the message away, with the actions under the words rather than beside
|
|
173
|
+
* them so a second row exists only when there are actions to put in it.
|
|
174
|
+
*/
|
|
175
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] {
|
|
176
|
+
/* The tone's five roles, defaulted to `accent`, restated by each tone
|
|
177
|
+
below. Every one of them is a role the theme's contrast test measures. */
|
|
178
|
+
--_solid: var(--slotted-tone-accent-solid);
|
|
179
|
+
--_on-solid: var(--slotted-tone-accent-on-solid);
|
|
180
|
+
--_tone-surface: var(--slotted-tone-accent-surface);
|
|
181
|
+
--_tone-border: var(--slotted-tone-accent-border);
|
|
182
|
+
--_tone-text: var(--slotted-tone-accent-text);
|
|
183
|
+
|
|
184
|
+
/* What the variant resolves the five into: a ground, an edge, the ink the
|
|
185
|
+
words are written in, the quieter ink under them, and the mark. */
|
|
186
|
+
--_ground: var(--slotted-toast-surface, var(--slotted-surface-raised, #ffffff));
|
|
187
|
+
--_edge: var(--slotted-toast-border-color, var(--slotted-border-subtle, #e2e8f0));
|
|
188
|
+
--_ink: var(--slotted-toast-text, var(--slotted-text, #0f172a));
|
|
189
|
+
--_ink-muted: var(--slotted-toast-text-muted, var(--slotted-text-muted, #5b6b7f));
|
|
190
|
+
--_mark: var(--_tone-text);
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A state layer rather than the tone's own hover ramp, for the reason
|
|
194
|
+
* `alert` measured: on a tinted ground `subtle-hover` is lighter than the
|
|
195
|
+
* ground it sits on, and on a solid one the ramp is calibrated against the
|
|
196
|
+
* page instead. `currentcolor` is legible on whatever ground the control
|
|
197
|
+
* already has, because it is the colour its own words are read in.
|
|
198
|
+
*
|
|
199
|
+
* Ten and sixteen per cent, the pair `alert` settled: white at eight per
|
|
200
|
+
* cent over the `danger` fill moved OKLab lightness by 0.019 in the light
|
|
201
|
+
* and dark schemes, under the 0.02 a surface step has to reach
|
|
202
|
+
* (`docs/history/designs/2026-09-29-toast-reui-dossier.md` § 4).
|
|
203
|
+
*/
|
|
204
|
+
--_state-hover: color-mix(
|
|
205
|
+
in oklab,
|
|
206
|
+
currentcolor var(--slotted-toast-state-layer-hover, 10%),
|
|
207
|
+
transparent
|
|
208
|
+
);
|
|
209
|
+
--_state-active: color-mix(
|
|
210
|
+
in oklab,
|
|
211
|
+
currentcolor var(--slotted-toast-state-layer-active, 16%),
|
|
212
|
+
transparent
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
/* Type and the close glyph from the control scales, the only ones the
|
|
216
|
+
theme restates per density, so a message inside a dense application
|
|
217
|
+
shrinks with it. The box's own inset is on the space scale, which does
|
|
218
|
+
not — the cost `card` and `alert` both accepted and recorded. */
|
|
219
|
+
--_font-size: var(--slotted-control-font-size-md, 13px);
|
|
220
|
+
--_glyph-size: var(--slotted-control-icon-size-md, 16px);
|
|
221
|
+
--_inner-size: var(--slotted-control-inner-size-md, 24px);
|
|
222
|
+
/**
|
|
223
|
+
* The status mark is `alert`'s: a filled disc a step above the title's
|
|
224
|
+
* `1em`, rounded to an even number of pixels so its centre falls on a
|
|
225
|
+
* whole pixel — 16 at 13-pixel type, 14 at 12. A message about a failure
|
|
226
|
+
* draws the same silhouette as the alert about it. A filled mark carries
|
|
227
|
+
* more weight per pixel than an outline, so it stays within a few pixels
|
|
228
|
+
* of the capitals and still reads as the loudest thing on the line.
|
|
229
|
+
*/
|
|
230
|
+
--_mark-size: round(nearest, 1.2em, 2px);
|
|
231
|
+
--_line-height: var(--slotted-line-height-md, 1.5);
|
|
232
|
+
|
|
233
|
+
--_inset-block: var(--slotted-toast-padding-block, var(--slotted-space-5, 0.75rem));
|
|
234
|
+
--_inset-inline: var(--slotted-toast-padding-inline, var(--slotted-space-6, 1rem));
|
|
235
|
+
--_column-gap: var(--slotted-toast-column-gap, var(--slotted-space-4, 0.5rem));
|
|
236
|
+
--_row-gap: var(--slotted-toast-row-gap, var(--slotted-space-5, 0.75rem));
|
|
237
|
+
--_text-gap: var(--slotted-toast-text-gap, var(--slotted-space-2, 0.25rem));
|
|
238
|
+
--_radius: var(--slotted-toast-radius, var(--slotted-radius-lg, 8px));
|
|
239
|
+
--_border-width: var(--slotted-toast-border-width, var(--slotted-border-width-thin, 1px));
|
|
240
|
+
/* An inner control, not a button (R2): the glyph plus a step of air on
|
|
241
|
+
each side, floored at WCAG 2.2 SC 2.5.8's minimum so the target holds at
|
|
242
|
+
every density. It took the smallest control height before, a thirty-pixel
|
|
243
|
+
square beside a sixteen-pixel mark. */
|
|
244
|
+
--_close-size: var(
|
|
245
|
+
--slotted-toast-close-size,
|
|
246
|
+
max(
|
|
247
|
+
var(--slotted-target-min, 24px),
|
|
248
|
+
min(calc(var(--_glyph-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
|
|
249
|
+
)
|
|
250
|
+
);
|
|
251
|
+
--_progress-size: var(--slotted-toast-progress-size, var(--slotted-space-1, 0.125rem));
|
|
252
|
+
|
|
253
|
+
background-color: var(--_ground);
|
|
254
|
+
border: var(--_border-width) solid var(--_edge);
|
|
255
|
+
border-radius: var(--_radius);
|
|
256
|
+
box-shadow: var(--slotted-toast-shadow, var(--slotted-shadow-md));
|
|
257
|
+
box-sizing: border-box;
|
|
258
|
+
color: var(--_ink);
|
|
259
|
+
/* The distance from the mark to the words belongs to the mark, not to the
|
|
260
|
+
grid: a column gap is drawn between two tracks whether or not either
|
|
261
|
+
holds anything, so a message with no mark would start one gap further
|
|
262
|
+
in than its own inset. */
|
|
263
|
+
column-gap: 0;
|
|
264
|
+
display: grid;
|
|
265
|
+
font-family: var(--slotted-toast-font-family, var(--slotted-control-font-family, sans-serif));
|
|
266
|
+
font-size: var(--_font-size);
|
|
267
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
268
|
+
/**
|
|
269
|
+
* The line box is rounded to an even number of pixels, the rule `alert`
|
|
270
|
+
* draws its lines with. At 13-pixel type the scale's 1.5 gave 19.5, so a
|
|
271
|
+
* message of one line was 45.5 pixels tall and one with a description
|
|
272
|
+
* 88.5 in the `content` scene; an even line also centres the 24-pixel
|
|
273
|
+
* close control on a whole pixel: 20 at 13, 18 at 12.
|
|
274
|
+
*/
|
|
275
|
+
line-height: round(nearest, calc(var(--_line-height) * 1em), 2px);
|
|
276
|
+
/* The message takes the pointer the region refuses. */
|
|
277
|
+
pointer-events: auto;
|
|
278
|
+
/* The remaining-time bar is positioned against this box, so its width
|
|
279
|
+
changing never moves a word. */
|
|
280
|
+
position: relative;
|
|
281
|
+
/* Nothing inside a message may be drawn past its own rounded corner, which
|
|
282
|
+
is what the bar at its block end would do. */
|
|
283
|
+
overflow: hidden;
|
|
284
|
+
padding-block: var(--_inset-block);
|
|
285
|
+
padding-inline: var(--_inset-inline);
|
|
286
|
+
row-gap: var(--_row-gap);
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Closed is the state the element is born in, so a message that mounts
|
|
290
|
+
* already open has a style to transition from. The exit is the same
|
|
291
|
+
* declaration, which is why it is written here and not under `[data-open]`.
|
|
292
|
+
*/
|
|
293
|
+
opacity: 0;
|
|
294
|
+
translate: 0 var(--_enter-shift);
|
|
295
|
+
transition:
|
|
296
|
+
opacity var(--slotted-duration-short) var(--slotted-easing-exit),
|
|
297
|
+
translate var(--slotted-duration-short) var(--slotted-easing-exit);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* A message at the block start of the screen arrives from above it. The
|
|
302
|
+
* shift is the same distance in the other direction rather than a second
|
|
303
|
+
* number, so the two placements are one motion seen twice.
|
|
304
|
+
*/
|
|
305
|
+
[data-ui='toast'][data-placement^='top'] > [data-part='list'] > [data-part='item'] {
|
|
306
|
+
translate: 0 calc(-1 * var(--_enter-shift));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-open] {
|
|
310
|
+
opacity: 1;
|
|
311
|
+
translate: 0;
|
|
312
|
+
transition:
|
|
313
|
+
opacity var(--slotted-duration-medium) var(--slotted-easing-enter),
|
|
314
|
+
translate var(--slotted-duration-medium) var(--slotted-easing-enter);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* ── Tone: the colour, and the mark that says the same thing ───────────
|
|
318
|
+
*
|
|
319
|
+
* The map is `specs/components/toast/contract.json`, and it is the map
|
|
320
|
+
* `alert` and `status-indicator` already share — so a message about a
|
|
321
|
+
* failure draws the same silhouette as the alert about it. The binding
|
|
322
|
+
* draws the mark as a `Glyph` from that map; this sheet gives it only the
|
|
323
|
+
* tone's colour. Under forced
|
|
324
|
+
* colours every ground becomes one system colour and the shape is all that
|
|
325
|
+
* is left, which is WCAG 2.2 SC 1.4.1 as well as a preference.
|
|
326
|
+
*/
|
|
327
|
+
|
|
328
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='accent'] {
|
|
329
|
+
--_solid: var(--slotted-tone-accent-solid);
|
|
330
|
+
--_on-solid: var(--slotted-tone-accent-on-solid);
|
|
331
|
+
--_tone-surface: var(--slotted-tone-accent-surface);
|
|
332
|
+
--_tone-border: var(--slotted-tone-accent-border);
|
|
333
|
+
--_tone-text: var(--slotted-tone-accent-text);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='secondary'] {
|
|
337
|
+
--_solid: var(--slotted-tone-secondary-solid);
|
|
338
|
+
--_on-solid: var(--slotted-tone-secondary-on-solid);
|
|
339
|
+
--_tone-surface: var(--slotted-tone-secondary-surface);
|
|
340
|
+
--_tone-border: var(--slotted-tone-secondary-border);
|
|
341
|
+
--_tone-text: var(--slotted-tone-secondary-text);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='success'] {
|
|
345
|
+
--_solid: var(--slotted-tone-success-solid);
|
|
346
|
+
--_on-solid: var(--slotted-tone-success-on-solid);
|
|
347
|
+
--_tone-surface: var(--slotted-tone-success-surface);
|
|
348
|
+
--_tone-border: var(--slotted-tone-success-border);
|
|
349
|
+
--_tone-text: var(--slotted-tone-success-text);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='warning'] {
|
|
353
|
+
--_solid: var(--slotted-tone-warning-solid);
|
|
354
|
+
--_on-solid: var(--slotted-tone-warning-on-solid);
|
|
355
|
+
--_tone-surface: var(--slotted-tone-warning-surface);
|
|
356
|
+
--_tone-border: var(--slotted-tone-warning-border);
|
|
357
|
+
--_tone-text: var(--slotted-tone-warning-text);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='danger'] {
|
|
361
|
+
--_solid: var(--slotted-tone-danger-solid);
|
|
362
|
+
--_on-solid: var(--slotted-tone-danger-on-solid);
|
|
363
|
+
--_tone-surface: var(--slotted-tone-danger-surface);
|
|
364
|
+
--_tone-border: var(--slotted-tone-danger-border);
|
|
365
|
+
--_tone-text: var(--slotted-tone-danger-text);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* ── Variant: the three ways one tone is painted ───────────────────────
|
|
369
|
+
*
|
|
370
|
+
* `raised` is the default and the one the other families do not have: a
|
|
371
|
+
* message floats over content this family does not own, and tinting that
|
|
372
|
+
* content's corner is a decision the message has no standing to take. The
|
|
373
|
+
* tone is in the mark and in nothing else, which is enough — it is the
|
|
374
|
+
* silhouette that survives forced colours anyway.
|
|
375
|
+
*/
|
|
376
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-variant='soft'] {
|
|
377
|
+
--_ground: var(--_tone-surface);
|
|
378
|
+
--_edge: var(--_tone-border);
|
|
379
|
+
/* The tone's own text on the tone's own surface is the pair the theme
|
|
380
|
+
measures. Painting a tinted ground with the neutral ink would be a ratio
|
|
381
|
+
nobody holds, which is the shortfall `alert` records. */
|
|
382
|
+
--_ink: var(--_tone-text);
|
|
383
|
+
--_ink-muted: var(--_tone-text);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-variant='solid'] {
|
|
387
|
+
--_ground: var(--_solid);
|
|
388
|
+
--_edge: var(--_solid);
|
|
389
|
+
--_ink: var(--_on-solid);
|
|
390
|
+
--_ink-muted: var(--_on-solid);
|
|
391
|
+
--_mark: var(--_on-solid);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* ── The mark ─────────────────────────────────────────────────────────
|
|
395
|
+
*
|
|
396
|
+
* `alert`'s mark, rule for rule: a filled disc on the title's first line,
|
|
397
|
+
* the binding's `Glyph` in its `solid` form, which fills the glyph's ring
|
|
398
|
+
* and cuts the tone's symbol out of the fill, so the ground shows through
|
|
399
|
+
* the symbol on every variant. Every tone's glyph is a circle, so every
|
|
400
|
+
* mark is the same round silhouette on the same centre, and the symbol
|
|
401
|
+
* inside is what tells the five apart when colour is gone.
|
|
402
|
+
*
|
|
403
|
+
* The glyph's filled ring reaches radius 11 of its 24-unit box, so the box
|
|
404
|
+
* is 12/11 of the mark and the disc is the mark's own size.
|
|
405
|
+
*
|
|
406
|
+
* The mark hangs from the first line's baseline. Grid baseline alignment
|
|
407
|
+
* puts the mark's bottom edge on the words' baseline, and a relative offset
|
|
408
|
+
* lowers it by half of what the mark exceeds the capitals by, so its centre
|
|
409
|
+
* is the centre of the capitals in whatever face the title is set in. The
|
|
410
|
+
* offset is rounded to a whole pixel and the mark is a whole number of
|
|
411
|
+
* pixels, so the glyphs and the drawing snap by the same amount. The
|
|
412
|
+
* negative block-start margin takes the mark's own height out of the
|
|
413
|
+
* baseline group, so the words sit where they sit with no mark at all, and
|
|
414
|
+
* layout containment keeps the glyph's own baseline out of the box's.
|
|
415
|
+
*
|
|
416
|
+
* The slot carries the colour, and the disc is painted in `currentcolor`,
|
|
417
|
+
* so on a `solid` message the mark takes the on-solid ink with the words,
|
|
418
|
+
* and under forced colours one declaration repaints it.
|
|
419
|
+
*
|
|
420
|
+
* A mark the page supplied instead of the tone's own — a spinner from
|
|
421
|
+
* `progress-indicator` while a promise is in flight, a product's own
|
|
422
|
+
* artwork — takes the same place: an icon a consumer wrote is an icon, and
|
|
423
|
+
* the line it belongs to has not moved. The artwork is centred in the box,
|
|
424
|
+
* so an icon drawn at the mark's size fills it and a larger one overhangs it
|
|
425
|
+
* equally on both sides of the same axis rather than pushing the words along. A
|
|
426
|
+
* message waiting on a promise with no mark of its own draws none, and the
|
|
427
|
+
* box stays, so the layout does not jump when the spinner becomes a tick.
|
|
428
|
+
*/
|
|
429
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='icon'] {
|
|
430
|
+
--slotted-icon-size: calc(var(--_mark-size) * 12 / 11);
|
|
431
|
+
/**
|
|
432
|
+
* The loading mark is `progress-indicator`'s contained loader, and these
|
|
433
|
+
* three are the whole of what this family says to it: the disc is the
|
|
434
|
+
* mark's size, so the loader and the tick that replaces it are one
|
|
435
|
+
* silhouette, and it is drawn in the mark's ink with the track the
|
|
436
|
+
* loader's own 24 per cent of that ink over this message's ground — on a
|
|
437
|
+
* `solid` message that is the on-solid ink over the fill, which the
|
|
438
|
+
* loader's tone could not know.
|
|
439
|
+
*/
|
|
440
|
+
--slotted-progress-indicator-size: var(--_mark-size);
|
|
441
|
+
--slotted-progress-indicator-indicator-color: var(--_mark);
|
|
442
|
+
--slotted-progress-indicator-track-color: color-mix(in oklab, var(--_mark) 24%, var(--_ground));
|
|
443
|
+
|
|
444
|
+
align-items: center;
|
|
445
|
+
align-self: baseline;
|
|
446
|
+
block-size: var(--_mark-size);
|
|
447
|
+
color: var(--_mark);
|
|
448
|
+
contain: layout;
|
|
449
|
+
display: flex;
|
|
450
|
+
grid-column: 1;
|
|
451
|
+
grid-row: 1;
|
|
452
|
+
inline-size: var(--_mark-size);
|
|
453
|
+
inset-block-start: round(nearest, (var(--_mark-size) - 1cap) / 2, 1px);
|
|
454
|
+
justify-content: center;
|
|
455
|
+
margin-block-start: calc(-1 * var(--_mark-size));
|
|
456
|
+
margin-inline-end: var(--_column-gap);
|
|
457
|
+
position: relative;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* ── The words ────────────────────────────────────────────────────────
|
|
461
|
+
*
|
|
462
|
+
* `min-inline-size: 0` is what lets the column shrink below its content
|
|
463
|
+
* inside the message's grid, and it is the half of the pair that
|
|
464
|
+
* `minmax(0, 1fr)` on the track cannot supply on its own. The same pair is
|
|
465
|
+
* restated inside: the words are a grid of their own, and its one track has
|
|
466
|
+
* to be clamped too, or an unbreakable word in the title sizes the track to
|
|
467
|
+
* itself and the words run past the box that holds them.
|
|
468
|
+
*/
|
|
469
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='content'] {
|
|
470
|
+
/* The other half of the mark's baseline group: see the mark above. */
|
|
471
|
+
align-self: baseline;
|
|
472
|
+
display: grid;
|
|
473
|
+
grid-column: 2;
|
|
474
|
+
grid-row: 1;
|
|
475
|
+
grid-template-columns: minmax(0, 1fr);
|
|
476
|
+
min-inline-size: 0;
|
|
477
|
+
row-gap: var(--_text-gap);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* `anywhere` breaks the one string a message is likeliest to carry that no
|
|
482
|
+
* language can break — a deployment id, a container digest, a compound noun,
|
|
483
|
+
* a host name without a hyphen — and, unlike `break-word`, lowers the
|
|
484
|
+
* min-content size the grid reads, so the track never grows past the box to
|
|
485
|
+
* fit the word. Nothing is removed: the whole string stays in the DOM, in the
|
|
486
|
+
* accessibility tree, and findable by a page search.
|
|
487
|
+
*
|
|
488
|
+
* The 240-pixel capture is what asked for it: with `break-word` the title
|
|
489
|
+
* `Zwischenspeicherung` set the track to 183 pixels inside a box of 155, and
|
|
490
|
+
* the words stood outside the message in every scheme.
|
|
491
|
+
*/
|
|
492
|
+
[data-ui='toast']
|
|
493
|
+
> [data-part='list']
|
|
494
|
+
> [data-part='item']
|
|
495
|
+
> [data-part='content']
|
|
496
|
+
> [data-part='title'] {
|
|
497
|
+
font-family: var(
|
|
498
|
+
--slotted-toast-title-font-family,
|
|
499
|
+
var(
|
|
500
|
+
--slotted-font-family-heading,
|
|
501
|
+
var(--slotted-font-family-sans, ui-sans-serif, system-ui, sans-serif)
|
|
502
|
+
)
|
|
503
|
+
);
|
|
504
|
+
font-weight: var(--slotted-toast-title-font-weight, var(--slotted-font-weight-semibold, 600));
|
|
505
|
+
margin: 0;
|
|
506
|
+
min-inline-size: 0;
|
|
507
|
+
overflow-wrap: anywhere;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
[data-ui='toast']
|
|
511
|
+
> [data-part='list']
|
|
512
|
+
> [data-part='item']
|
|
513
|
+
> [data-part='content']
|
|
514
|
+
> [data-part='description'] {
|
|
515
|
+
color: var(--_ink-muted);
|
|
516
|
+
margin: 0;
|
|
517
|
+
min-inline-size: 0;
|
|
518
|
+
overflow-wrap: anywhere;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* ── The actions ──────────────────────────────────────────────────────
|
|
522
|
+
*
|
|
523
|
+
* Under the words rather than beside them, which is the only place they can
|
|
524
|
+
* wrap without taking the message's own column with them. What goes in is
|
|
525
|
+
* the consumer's own buttons: `button` already owns what a button is, and
|
|
526
|
+
* naming one of two controls `cancel` would privilege one arrangement.
|
|
527
|
+
*/
|
|
528
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='actions'] {
|
|
529
|
+
align-items: center;
|
|
530
|
+
display: flex;
|
|
531
|
+
flex-wrap: wrap;
|
|
532
|
+
gap: var(--slotted-toast-actions-gap, var(--slotted-space-3, 0.375rem));
|
|
533
|
+
grid-column: 2;
|
|
534
|
+
grid-row: 2;
|
|
535
|
+
min-inline-size: 0;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/* ── The control that puts the message away ───────────────────────────
|
|
539
|
+
*
|
|
540
|
+
* The negative margins are optical rather than decorative, and they are the
|
|
541
|
+
* pair `alert` arrived at: the button's box is taller than the line it sits
|
|
542
|
+
* beside, so half the overhang comes off each end. With both, the control's
|
|
543
|
+
* margin box is exactly one line and the inset above the message equals the
|
|
544
|
+
* inset below.
|
|
545
|
+
*/
|
|
546
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close'] {
|
|
547
|
+
/* The cross is the binding's `Glyph` in an `icon` box at the glyph size
|
|
548
|
+
the formula for this box reads. */
|
|
549
|
+
--slotted-icon-size: var(--_glyph-size);
|
|
550
|
+
|
|
551
|
+
align-items: center;
|
|
552
|
+
align-self: start;
|
|
553
|
+
appearance: none;
|
|
554
|
+
background-color: transparent;
|
|
555
|
+
block-size: var(--_close-size);
|
|
556
|
+
border: 0;
|
|
557
|
+
border-radius: var(--slotted-toast-close-radius, var(--slotted-control-radius, 4px));
|
|
558
|
+
color: inherit;
|
|
559
|
+
cursor: pointer;
|
|
560
|
+
display: inline-flex;
|
|
561
|
+
flex: none;
|
|
562
|
+
/* The user agent gives every button a font of its own, and the `1lh` below
|
|
563
|
+
is measured against whatever that font resolves to. */
|
|
564
|
+
font: inherit;
|
|
565
|
+
grid-column: 3;
|
|
566
|
+
grid-row: 1;
|
|
567
|
+
inline-size: var(--_close-size);
|
|
568
|
+
justify-content: center;
|
|
569
|
+
margin-block-end: calc((1lh - var(--_close-size)) / 2);
|
|
570
|
+
margin-block-start: calc((1lh - var(--_close-size)) / 2);
|
|
571
|
+
margin-inline-end: calc((1lh - var(--_close-size)) / 2);
|
|
572
|
+
margin-inline-start: var(--_column-gap);
|
|
573
|
+
padding: 0;
|
|
574
|
+
transition:
|
|
575
|
+
background-color var(--slotted-control-transition-duration)
|
|
576
|
+
var(--slotted-control-transition-easing),
|
|
577
|
+
color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* `:enabled` is the guard the shared state test requires, and the reason it
|
|
581
|
+
exists: a control the browser has already refused answers no pointer. */
|
|
582
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:enabled:hover {
|
|
583
|
+
background-color: var(--_state-hover);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:enabled:active {
|
|
587
|
+
background-color: var(--_state-active);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* On a finger the target is the comfortable size in both axes and the box
|
|
591
|
+
is the one a mouse sees: a transparent box centred on the control reaches
|
|
592
|
+
44 pixels, and the ground and the ring stay square. The message clips its
|
|
593
|
+
overflow, so the target ends at the message's padding edge. */
|
|
594
|
+
@media (pointer: coarse) {
|
|
595
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close'] {
|
|
596
|
+
position: relative;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']::after {
|
|
600
|
+
content: '';
|
|
601
|
+
inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
|
|
602
|
+
position: absolute;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/* ── Focus ────────────────────────────────────────────────────────────
|
|
607
|
+
*
|
|
608
|
+
* Two rings, because two things can hold focus. The control's is inset: it
|
|
609
|
+
* sits at the corner of a box that clips its own overflow, so a ring outside
|
|
610
|
+
* it would be cut in half. The message's is inset for the same reason, and
|
|
611
|
+
* it exists because `F6` sends the keyboard here — a message the reader
|
|
612
|
+
* cannot see they have arrived at is a message they cannot act on.
|
|
613
|
+
*/
|
|
614
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:focus-visible,
|
|
615
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item']:focus-visible {
|
|
616
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The control's ring is inset because the message clips its own overflow —
|
|
621
|
+
* the remaining-time bar has to stop at the rounded corner — and a ring
|
|
622
|
+
* outside a control at that corner would be cut in half by it.
|
|
623
|
+
*/
|
|
624
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:focus-visible {
|
|
625
|
+
outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* The message's own ring sits outside it. Nothing clips a layer at the edge
|
|
630
|
+
* of the screen, and read on the box itself the ring was a thick blue border
|
|
631
|
+
* rather than a ring around a card.
|
|
632
|
+
*/
|
|
633
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item']:focus-visible {
|
|
634
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Measured rather than assumed, and `alert` measured it first: the ring the
|
|
639
|
+
* theme calibrated against the page's surface reads about 1.3 : 1 against a
|
|
640
|
+
* tone's own fill. The ring keeps the theme's colour in every scheme (R3).
|
|
641
|
+
* The message's own ring sits outside it, against the page the theme
|
|
642
|
+
* calibrated for, and needs nothing more. The control's ring is inset in the
|
|
643
|
+
* fill, so a solid message adds a halo one ring-width wide just inside it,
|
|
644
|
+
* in the ink its own words are in, for the ring to be read against.
|
|
645
|
+
*/
|
|
646
|
+
[data-ui='toast']
|
|
647
|
+
> [data-part='list']
|
|
648
|
+
> [data-part='item'][data-variant='solid']
|
|
649
|
+
> [data-part='close']:focus-visible {
|
|
650
|
+
box-shadow: inset 0 0 0 calc(2 * var(--slotted-focus-ring-width, 2px)) var(--_on-solid);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* ── How much time is left ────────────────────────────────────────────
|
|
654
|
+
*
|
|
655
|
+
* The bar runs for exactly as long as the message it belongs to, because it
|
|
656
|
+
* reads the same number the timer does — `--slotted-toast-duration`, written
|
|
657
|
+
* per message by `packages/core/src/toast`. `--slotted-toast-play-state`
|
|
658
|
+
* carries the other half: the timer pauses on hover, on focus inside the
|
|
659
|
+
* region, and on a hidden document, and only the first two of those are
|
|
660
|
+
* things CSS can see. Resolving all three in script and publishing one value
|
|
661
|
+
* is what makes it impossible for the picture and the timer to disagree.
|
|
662
|
+
*
|
|
663
|
+
* It animates its own inline size rather than a transform, so it shortens
|
|
664
|
+
* from the inline start in every writing direction with no origin to mirror.
|
|
665
|
+
* The box is positioned, so nothing it does moves a word.
|
|
666
|
+
*/
|
|
667
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='progress'] {
|
|
668
|
+
animation-duration: var(--slotted-toast-duration, var(--slotted-duration-long));
|
|
669
|
+
animation-fill-mode: forwards;
|
|
670
|
+
animation-name: slotted-toast-countdown;
|
|
671
|
+
animation-play-state: var(--slotted-toast-play-state, running);
|
|
672
|
+
animation-timing-function: linear;
|
|
673
|
+
/**
|
|
674
|
+
* The tone's saturated step rather than the ink its words are in. Read at
|
|
675
|
+
* half opacity in the ink colour the bar was a grey smear under a message;
|
|
676
|
+
* two pixels of the tone's own fill is the one mark on the box that is
|
|
677
|
+
* allowed to be loud, because what it says is *this is about to go*.
|
|
678
|
+
*/
|
|
679
|
+
background-color: var(--_solid);
|
|
680
|
+
block-size: var(--_progress-size);
|
|
681
|
+
inset-block-end: 0;
|
|
682
|
+
inset-inline-start: 0;
|
|
683
|
+
position: absolute;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* On a solid message the fill is already the tone, so the bar is drawn in
|
|
688
|
+
* the one ink the contrast test holds against it, held back so it reads as a
|
|
689
|
+
* measure rather than as a second border.
|
|
690
|
+
*/
|
|
691
|
+
[data-ui='toast']
|
|
692
|
+
> [data-part='list']
|
|
693
|
+
> [data-part='item'][data-variant='solid']
|
|
694
|
+
> [data-part='progress'] {
|
|
695
|
+
background-color: var(--_on-solid);
|
|
696
|
+
opacity: 0.7;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
@keyframes slotted-toast-countdown {
|
|
700
|
+
from {
|
|
701
|
+
inline-size: 100%;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
to {
|
|
705
|
+
inline-size: 0%;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/* ── The collapsed stack ──────────────────────────────────────────────
|
|
710
|
+
*
|
|
711
|
+
* Four messages at full height fill a phone. `stacking="stack"` puts them
|
|
712
|
+
* one behind another at the edge `placement` names, each stepped back and
|
|
713
|
+
* drawn slightly smaller, and expands the pile when the pointer is over the
|
|
714
|
+
* region or focus is inside it — the same two conditions that pause the
|
|
715
|
+
* timer, because both mean the reader is reading.
|
|
716
|
+
*
|
|
717
|
+
* Every message is positioned in both states and moves only by `translate`,
|
|
718
|
+
* which is what lets one become the other rather than snap. The numbers it
|
|
719
|
+
* moves by are measured — `--slotted-toast-height` per message,
|
|
720
|
+
* `--slotted-toast-stack-offset` for the messages in front of it,
|
|
721
|
+
* `--slotted-toast-front-height` for the one at the edge — because the pile
|
|
722
|
+
* has to be readable when it opens, and a pile of messages that are not all
|
|
723
|
+
* the same height cannot be opened from arithmetic alone.
|
|
724
|
+
*
|
|
725
|
+
* Depth is said by the step back and by the ink, and it was said by a scale
|
|
726
|
+
* per index until R7: a message is a box of words, and drawing it at
|
|
727
|
+
* ninety-six per cent resamples every letter in it while the pile opens and
|
|
728
|
+
* closes. What replaced it is the one property that says *further away*
|
|
729
|
+
* without touching the type — each message behind the front one is held
|
|
730
|
+
* back in opacity, and the front one, which is the one being read, is at
|
|
731
|
+
* full strength.
|
|
732
|
+
*/
|
|
733
|
+
[data-ui='toast'][data-stacking='stack'] > [data-part='list'] {
|
|
734
|
+
display: block;
|
|
735
|
+
position: relative;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
[data-ui='toast'][data-stacking='stack'] > [data-part='list'] > [data-part='item'] {
|
|
739
|
+
block-size: var(--slotted-toast-front-height, auto);
|
|
740
|
+
inset-inline: 0;
|
|
741
|
+
position: absolute;
|
|
742
|
+
/* Newest in front. The count is what lets the rule read the same at any
|
|
743
|
+
depth without a rule per index. */
|
|
744
|
+
z-index: calc(var(--slotted-toast-count, 1) - var(--slotted-toast-index, 0));
|
|
745
|
+
transition:
|
|
746
|
+
block-size var(--slotted-duration-medium) var(--slotted-easing-standard),
|
|
747
|
+
opacity var(--slotted-duration-medium) var(--slotted-easing-standard),
|
|
748
|
+
translate var(--slotted-duration-medium) var(--slotted-easing-standard);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* How far back a message is, said in ink. It is written on the open message
|
|
753
|
+
* rather than on every message, because the closed style is `opacity: 0` —
|
|
754
|
+
* the state a message arrives from and leaves into — and a rule that dimmed
|
|
755
|
+
* it there would be a message that never faded in. The third message and
|
|
756
|
+
* everything behind it share one strength: below that the pile is a stack of
|
|
757
|
+
* ghosts, and only the front two edges are showing anyway.
|
|
758
|
+
*/
|
|
759
|
+
[data-ui='toast'][data-stacking='stack'] > [data-part='list'] > [data-part='item'][data-open] {
|
|
760
|
+
opacity: calc(1 - min(var(--slotted-toast-index, 0), 2) * var(--_stack-fade));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
[data-ui='toast'][data-stacking='stack'][data-placement^='bottom']
|
|
764
|
+
> [data-part='list']
|
|
765
|
+
> [data-part='item'] {
|
|
766
|
+
inset-block-end: 0;
|
|
767
|
+
translate: 0 calc(-1 * var(--slotted-toast-index, 0) * var(--_stack-step));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
[data-ui='toast'][data-stacking='stack'][data-placement^='top']
|
|
771
|
+
> [data-part='list']
|
|
772
|
+
> [data-part='item'] {
|
|
773
|
+
inset-block-start: 0;
|
|
774
|
+
translate: 0 calc(var(--slotted-toast-index, 0) * var(--_stack-step));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Opened. Every message returns to its own height and its own place in the
|
|
779
|
+
* column, and the gap between two of them is the region's own gap — the
|
|
780
|
+
* offset script writes already carries it.
|
|
781
|
+
*/
|
|
782
|
+
[data-ui='toast'][data-stacking='stack'][data-expanded]
|
|
783
|
+
> [data-part='list']
|
|
784
|
+
> [data-part='item'] {
|
|
785
|
+
block-size: var(--slotted-toast-height, auto);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* The messages of a pile are positioned, so the list is as tall as none of
|
|
790
|
+
* them, and the rule that gives an expanded list the pointer covered no gap
|
|
791
|
+
* at all. Opened, it is as tall as the open pile — the measured sum of every
|
|
792
|
+
* message and the gaps between them — and the gaps are the region's. The
|
|
793
|
+
* messages hang from the list's edge at `placement`, so the list grows away
|
|
794
|
+
* from that edge and nothing it holds moves.
|
|
795
|
+
*/
|
|
796
|
+
[data-ui='toast'][data-stacking='stack'][data-expanded] > [data-part='list'] {
|
|
797
|
+
block-size: var(--slotted-toast-stack-height, auto);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
[data-ui='toast'][data-stacking='stack'][data-expanded]
|
|
801
|
+
> [data-part='list']
|
|
802
|
+
> [data-part='item'][data-open] {
|
|
803
|
+
opacity: 1;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
[data-ui='toast'][data-stacking='stack'][data-expanded][data-placement^='bottom']
|
|
807
|
+
> [data-part='list']
|
|
808
|
+
> [data-part='item'] {
|
|
809
|
+
translate: 0 calc(-1 * var(--slotted-toast-stack-offset, 0px));
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
[data-ui='toast'][data-stacking='stack'][data-expanded][data-placement^='top']
|
|
813
|
+
> [data-part='list']
|
|
814
|
+
> [data-part='item'] {
|
|
815
|
+
translate: 0 var(--slotted-toast-stack-offset, 0px);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* While the pile is closed, everything behind the front message shows only
|
|
820
|
+
* its own edge: its words are held back rather than clipped mid-sentence.
|
|
821
|
+
* `min()` is what turns an index into that rule without a rule per depth —
|
|
822
|
+
* the front message resolves to one and every other to zero.
|
|
823
|
+
*/
|
|
824
|
+
[data-ui='toast'][data-stacking='stack'] > [data-part='list'] > [data-part='item'] > * {
|
|
825
|
+
transition: opacity var(--slotted-duration-medium) var(--slotted-easing-standard);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
[data-ui='toast'][data-stacking='stack']:not([data-expanded]) [data-part='item'] > * {
|
|
829
|
+
opacity: calc(1 - min(1, var(--slotted-toast-index, 0)));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/* ── Swiping it away ──────────────────────────────────────────────────
|
|
833
|
+
*
|
|
834
|
+
* The gesture the platform trained the reader to expect, and the answer to a
|
|
835
|
+
* dismiss control that is a small target on a box that is moving. The
|
|
836
|
+
* pointer's distance is published by `packages/core/src/toast` and the
|
|
837
|
+
* message follows it exactly, with no transition in the way: a drag that
|
|
838
|
+
* lags the finger reads as a stuck interface.
|
|
839
|
+
*/
|
|
840
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'][data-dragging] {
|
|
841
|
+
cursor: grabbing;
|
|
842
|
+
transition: none;
|
|
843
|
+
translate: var(--slotted-toast-swipe-x, 0px) var(--slotted-toast-swipe-y, 0px);
|
|
844
|
+
/* The browser's own scroll gesture and this one are the same finger. */
|
|
845
|
+
touch-action: none;
|
|
846
|
+
user-select: none;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
[data-ui='toast'][data-swipe='true'] > [data-part='list'] > [data-part='item'] {
|
|
850
|
+
touch-action: none;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/* ── A narrow region ──────────────────────────────────────────────────
|
|
854
|
+
*
|
|
855
|
+
* The query is on the region's own inline size, which is what a message
|
|
856
|
+
* actually has to fit inside — the region is already the screen less two
|
|
857
|
+
* offsets, so a phone and a narrow panel reach this the same way. The
|
|
858
|
+
* breakpoint is a literal because CSS forbids a custom property in a
|
|
859
|
+
* container condition.
|
|
860
|
+
*/
|
|
861
|
+
@container slotted-toast (max-width: 20rem) {
|
|
862
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] {
|
|
863
|
+
--_inset-block: var(--slotted-toast-padding-block, var(--slotted-space-4, 0.5rem));
|
|
864
|
+
--_inset-inline: var(--slotted-toast-padding-inline, var(--slotted-space-5, 0.75rem));
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Two buttons wrapping into two ragged lines is worse than two buttons
|
|
869
|
+
* stacked. Each one keeps its own width: how wide a control is belongs to
|
|
870
|
+
* the control, and `button` already ships `fullWidth` for a consumer who
|
|
871
|
+
* wants otherwise.
|
|
872
|
+
*/
|
|
873
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='actions'] {
|
|
874
|
+
align-items: flex-start;
|
|
875
|
+
flex-direction: column;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/* ── Forced colours ───────────────────────────────────────────────────
|
|
880
|
+
*
|
|
881
|
+
* The operating system replaces every author colour, so five tones become
|
|
882
|
+
* one box and three variants become one painting. What is left to decide is
|
|
883
|
+
* whether the box still has an edge and whether the mark is still there —
|
|
884
|
+
* and the theme can answer neither, because `solid` draws its border in its
|
|
885
|
+
* own fill and a transparent border is not a colour the system remaps.
|
|
886
|
+
*/
|
|
887
|
+
@media (forced-colors: active) {
|
|
888
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] {
|
|
889
|
+
border-color: CanvasText;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='icon'] {
|
|
893
|
+
color: CanvasText;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* The control's glyph is stroked in `currentcolor`, so giving the control
|
|
898
|
+
* a system colour repaints it, and the mark's slot the same.
|
|
899
|
+
*/
|
|
900
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close'] {
|
|
901
|
+
color: CanvasText;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:enabled:hover,
|
|
905
|
+
[data-ui='toast']
|
|
906
|
+
> [data-part='list']
|
|
907
|
+
> [data-part='item']
|
|
908
|
+
> [data-part='close']:enabled:active {
|
|
909
|
+
background-color: Highlight;
|
|
910
|
+
color: HighlightText;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* A bar of system ink under every message, shortening, is a moving line
|
|
915
|
+
* with no colour to say what it belongs to. The message still expires.
|
|
916
|
+
*/
|
|
917
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='progress'] {
|
|
918
|
+
background-color: CanvasText;
|
|
919
|
+
opacity: 1;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/* ── Reduced motion ───────────────────────────────────────────────────
|
|
924
|
+
*
|
|
925
|
+
* Every duration in this sheet comes from the motion scale, which the theme
|
|
926
|
+
* collapses under this query, so the arrival, the exit and the opening of
|
|
927
|
+
* the pile all become instant without a rule here.
|
|
928
|
+
*
|
|
929
|
+
* The countdown does not, because its length is the message's own and no
|
|
930
|
+
* theme can reach it. It is removed instead: a reader who asked the system
|
|
931
|
+
* for stillness is not given a bar to watch for five seconds, and the bar
|
|
932
|
+
* becomes a rule under the message. Nothing is lost — the bar was never the
|
|
933
|
+
* only warning, and the message expires either way. Pausing it was not
|
|
934
|
+
* enough: an animation that fills forwards holds whatever width it had
|
|
935
|
+
* reached over the declared one, and the `timing` scene froze at 80 per cent.
|
|
936
|
+
*/
|
|
937
|
+
@media (prefers-reduced-motion: reduce) {
|
|
938
|
+
[data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='progress'] {
|
|
939
|
+
animation-name: none;
|
|
940
|
+
inline-size: 100%;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|