@slotted/styles 0.0.0-stage → 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/package.json +276 -3
- package/src/accordion/accordion.css +96 -0
- package/src/accordion/accordion.tokens.json +14 -0
- package/src/action-bar/action-bar.css +455 -0
- package/src/action-bar/action-bar.tokens.json +50 -0
- package/src/action-sheet/action-sheet.css +462 -0
- package/src/action-sheet/action-sheet.tokens.json +59 -0
- package/src/alert/alert.css +764 -0
- package/src/alert/alert.tokens.json +77 -0
- package/src/app-shell/app-shell.css +1899 -0
- package/src/app-shell/app-shell.tokens.json +88 -0
- package/src/aspect-ratio/aspect-ratio.css +131 -0
- package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
- package/src/attachment/attachment.css +906 -0
- package/src/attachment/attachment.tokens.json +85 -0
- package/src/avatar/avatar.css +598 -0
- package/src/avatar/avatar.tokens.json +64 -0
- package/src/avatar-group/avatar-group.css +268 -0
- package/src/avatar-group/avatar-group.tokens.json +16 -0
- package/src/badge/badge.css +462 -0
- package/src/badge/badge.tokens.json +58 -0
- package/src/breadcrumb/breadcrumb.css +441 -0
- package/src/breadcrumb/breadcrumb.tokens.json +50 -0
- package/src/button/button.css +754 -0
- package/src/button/button.tokens.json +72 -0
- package/src/button-group/button-group.css +455 -0
- package/src/button-group/button-group.tokens.json +11 -0
- package/src/calendar/calendar.css +846 -0
- package/src/calendar/calendar.tokens.json +67 -0
- package/src/card/card.css +602 -0
- package/src/card/card.tokens.json +68 -0
- package/src/carousel/carousel.css +860 -0
- package/src/carousel/carousel.tokens.json +62 -0
- package/src/cascader/cascader.css +1320 -0
- package/src/cascader/cascader.tokens.json +103 -0
- package/src/chart/chart.css +1473 -0
- package/src/chart/chart.tokens.json +107 -0
- package/src/checkbox/checkbox.css +1011 -0
- package/src/checkbox/checkbox.tokens.json +102 -0
- package/src/checkbox-group/checkbox-group.css +70 -0
- package/src/checkbox-group/checkbox-group.tokens.json +6 -0
- package/src/code-block/code-block.css +555 -0
- package/src/code-block/code-block.tokens.json +69 -0
- package/src/collapsible/collapsible.css +546 -0
- package/src/collapsible/collapsible.tokens.json +55 -0
- package/src/color-picker/color-picker.css +607 -0
- package/src/color-picker/color-picker.tokens.json +58 -0
- package/src/column-manager/column-manager.css +452 -0
- package/src/column-manager/column-manager.tokens.json +43 -0
- package/src/combobox/combobox.css +1048 -0
- package/src/combobox/combobox.tokens.json +112 -0
- package/src/command-palette/command-palette.css +1000 -0
- package/src/command-palette/command-palette.tokens.json +75 -0
- package/src/container/container.css +198 -0
- package/src/container/container.tokens.json +18 -0
- package/src/context-menu/context-menu.css +104 -0
- package/src/context-menu/context-menu.tokens.json +11 -0
- package/src/currency-field/currency-field.css +787 -0
- package/src/currency-field/currency-field.tokens.json +80 -0
- package/src/dashboard-grid/dashboard-grid.css +748 -0
- package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
- package/src/data-grid/data-grid.css +2484 -0
- package/src/data-grid/data-grid.tokens.json +100 -0
- package/src/data-list/data-list.css +455 -0
- package/src/data-list/data-list.tokens.json +35 -0
- package/src/date-field/date-field.css +560 -0
- package/src/date-field/date-field.tokens.json +80 -0
- package/src/date-picker/date-picker.css +420 -0
- package/src/date-picker/date-picker.tokens.json +49 -0
- package/src/date-range-picker/date-range-picker.css +983 -0
- package/src/date-range-picker/date-range-picker.tokens.json +109 -0
- package/src/datetime-field/datetime-field.css +850 -0
- package/src/datetime-field/datetime-field.tokens.json +91 -0
- package/src/datetime-picker/datetime-picker.css +843 -0
- package/src/datetime-picker/datetime-picker.tokens.json +78 -0
- package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
- package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
- package/src/description-list/description-list.css +675 -0
- package/src/description-list/description-list.tokens.json +66 -0
- package/src/detail-panel/detail-panel.css +329 -0
- package/src/detail-panel/detail-panel.tokens.json +33 -0
- package/src/dialog/dialog.css +968 -0
- package/src/dialog/dialog.tokens.json +66 -0
- package/src/divider/divider.css +131 -0
- package/src/divider/divider.tokens.json +18 -0
- package/src/drawer/drawer.css +1303 -0
- package/src/drawer/drawer.tokens.json +75 -0
- package/src/entity-picker/entity-picker.css +1367 -0
- package/src/entity-picker/entity-picker.tokens.json +118 -0
- package/src/event-calendar/event-calendar.css +2148 -0
- package/src/event-calendar/event-calendar.tokens.json +87 -0
- package/src/field/field.css +707 -0
- package/src/field/field.tokens.json +58 -0
- package/src/field-group/field-group.css +226 -0
- package/src/field-group/field-group.tokens.json +33 -0
- package/src/filter-bar/filter-bar.css +1306 -0
- package/src/filter-bar/filter-bar.tokens.json +78 -0
- package/src/form/form.css +218 -0
- package/src/form/form.tokens.json +32 -0
- package/src/fullscreen/fullscreen.css +376 -0
- package/src/fullscreen/fullscreen.tokens.json +50 -0
- package/src/grid/grid.css +173 -0
- package/src/grid/grid.tokens.json +20 -0
- package/src/highlight/highlight.css +135 -0
- package/src/highlight/highlight.tokens.json +16 -0
- package/src/hover-card/hover-card.css +93 -0
- package/src/hover-card/hover-card.tokens.json +17 -0
- package/src/icon/icon.css +136 -0
- package/src/icon/icon.tokens.json +14 -0
- package/src/image/image.css +449 -0
- package/src/image/image.tokens.json +27 -0
- package/src/import-export-center/import-export-center.css +1175 -0
- package/src/import-export-center/import-export-center.tokens.json +84 -0
- package/src/inline/inline.css +149 -0
- package/src/inline/inline.tokens.json +14 -0
- package/src/inline-message/inline-message.css +396 -0
- package/src/inline-message/inline-message.tokens.json +19 -0
- package/src/kbd/kbd.css +490 -0
- package/src/kbd/kbd.tokens.json +40 -0
- package/src/link/link.css +237 -0
- package/src/link/link.tokens.json +25 -0
- package/src/list/list.css +240 -0
- package/src/list/list.tokens.json +42 -0
- package/src/list-item/list-item.css +937 -0
- package/src/list-item/list-item.tokens.json +114 -0
- package/src/listbox/listbox.css +674 -0
- package/src/listbox/listbox.tokens.json +72 -0
- package/src/loading-overlay/loading-overlay.css +730 -0
- package/src/loading-overlay/loading-overlay.tokens.json +48 -0
- package/src/markdown/markdown.css +168 -0
- package/src/markdown/markdown.tokens.json +18 -0
- package/src/menu/menu.css +817 -0
- package/src/menu/menu.tokens.json +78 -0
- package/src/menubar/menubar.css +170 -0
- package/src/menubar/menubar.tokens.json +32 -0
- package/src/message/message.css +1453 -0
- package/src/message/message.tokens.json +85 -0
- package/src/message-scroller/message-scroller.css +371 -0
- package/src/message-scroller/message-scroller.tokens.json +47 -0
- package/src/multi-select/multi-select.css +1427 -0
- package/src/multi-select/multi-select.tokens.json +114 -0
- package/src/navigation-menu/navigation-menu.css +1029 -0
- package/src/navigation-menu/navigation-menu.tokens.json +70 -0
- package/src/notification-center/notification-center.css +856 -0
- package/src/notification-center/notification-center.tokens.json +84 -0
- package/src/number-field/number-field.css +794 -0
- package/src/number-field/number-field.tokens.json +80 -0
- package/src/otp-input/otp-input.css +525 -0
- package/src/otp-input/otp-input.tokens.json +69 -0
- package/src/page/page.css +179 -0
- package/src/page/page.tokens.json +20 -0
- package/src/page-header/page-header.css +1181 -0
- package/src/page-header/page-header.tokens.json +71 -0
- package/src/pagination/pagination.css +500 -0
- package/src/pagination/pagination.tokens.json +56 -0
- package/src/popover/popover.css +526 -0
- package/src/popover/popover.tokens.json +60 -0
- package/src/progress-indicator/progress-indicator.css +1584 -0
- package/src/progress-indicator/progress-indicator.tokens.json +50 -0
- package/src/prompt-composer/prompt-composer.css +680 -0
- package/src/prompt-composer/prompt-composer.tokens.json +99 -0
- package/src/prose/prose.css +733 -0
- package/src/prose/prose.tokens.json +79 -0
- package/src/radio-group/radio-group.css +778 -0
- package/src/radio-group/radio-group.tokens.json +103 -0
- package/src/rating/rating.css +1071 -0
- package/src/rating/rating.tokens.json +88 -0
- package/src/result-state/result-state.css +980 -0
- package/src/result-state/result-state.tokens.json +63 -0
- package/src/runtime-properties.mjs +321 -0
- package/src/saved-views/saved-views.css +394 -0
- package/src/saved-views/saved-views.tokens.json +41 -0
- package/src/scroll-area/scroll-area.css +481 -0
- package/src/scroll-area/scroll-area.tokens.json +15 -0
- package/src/section/section.css +177 -0
- package/src/section/section.tokens.json +39 -0
- package/src/segmented-control/segmented-control.css +564 -0
- package/src/segmented-control/segmented-control.tokens.json +67 -0
- package/src/select/select.css +1593 -0
- package/src/select/select.tokens.json +110 -0
- package/src/side-navigation/side-navigation.css +1096 -0
- package/src/side-navigation/side-navigation.tokens.json +87 -0
- package/src/skeleton/skeleton.css +431 -0
- package/src/skeleton/skeleton.tokens.json +29 -0
- package/src/slider/slider.css +792 -0
- package/src/slider/slider.tokens.json +75 -0
- package/src/sparkline/sparkline.css +282 -0
- package/src/sparkline/sparkline.tokens.json +45 -0
- package/src/spinner/spinner.css +224 -0
- package/src/spinner/spinner.tokens.json +8 -0
- package/src/split-button/split-button.css +253 -0
- package/src/split-button/split-button.tokens.json +12 -0
- package/src/splitter/splitter.css +336 -0
- package/src/splitter/splitter.tokens.json +24 -0
- package/src/stack/stack.css +128 -0
- package/src/stack/stack.tokens.json +14 -0
- package/src/stat-card/stat-card.css +641 -0
- package/src/stat-card/stat-card.tokens.json +95 -0
- package/src/status-indicator/status-indicator.css +481 -0
- package/src/status-indicator/status-indicator.tokens.json +38 -0
- package/src/stepper/stepper.css +629 -0
- package/src/stepper/stepper.tokens.json +72 -0
- package/src/switch/switch.css +703 -0
- package/src/switch/switch.tokens.json +75 -0
- package/src/table/table.css +1482 -0
- package/src/table/table.tokens.json +69 -0
- package/src/table-of-contents/table-of-contents.css +533 -0
- package/src/table-of-contents/table-of-contents.tokens.json +53 -0
- package/src/tabs/tabs.css +998 -0
- package/src/tabs/tabs.tokens.json +82 -0
- package/src/tag/tag.css +768 -0
- package/src/tag/tag.tokens.json +85 -0
- package/src/tags-input/tags-input.css +757 -0
- package/src/tags-input/tags-input.tokens.json +84 -0
- package/src/text-field/text-field.css +900 -0
- package/src/text-field/text-field.tokens.json +87 -0
- package/src/textarea/textarea.css +365 -0
- package/src/textarea/textarea.tokens.json +59 -0
- package/src/time-field/time-field.css +719 -0
- package/src/time-field/time-field.tokens.json +86 -0
- package/src/time-picker/time-picker.css +665 -0
- package/src/time-picker/time-picker.tokens.json +66 -0
- package/src/time-range-picker/time-range-picker.css +1287 -0
- package/src/time-range-picker/time-range-picker.tokens.json +134 -0
- package/src/timeline/timeline.css +1596 -0
- package/src/timeline/timeline.tokens.json +96 -0
- package/src/toast/toast.css +943 -0
- package/src/toast/toast.tokens.json +90 -0
- package/src/toggle/toggle.css +290 -0
- package/src/toggle/toggle.tokens.json +54 -0
- package/src/toggle-group/toggle-group.css +463 -0
- package/src/toggle-group/toggle-group.tokens.json +54 -0
- package/src/toolbar/toolbar.css +434 -0
- package/src/toolbar/toolbar.tokens.json +49 -0
- package/src/tooltip/tooltip.css +368 -0
- package/src/tooltip/tooltip.tokens.json +39 -0
- package/src/top-navigation/top-navigation.css +1287 -0
- package/src/top-navigation/top-navigation.tokens.json +91 -0
- package/src/tour/tour.css +680 -0
- package/src/tour/tour.tokens.json +67 -0
- package/src/transfer/transfer.css +571 -0
- package/src/transfer/transfer.tokens.json +49 -0
- package/src/tree/tree.css +1000 -0
- package/src/tree/tree.tokens.json +80 -0
- package/src/typography/typography.css +211 -0
- package/src/typography/typography.tokens.json +31 -0
- package/src/upload/upload.css +1706 -0
- package/src/upload/upload.tokens.json +123 -0
- package/src/visually-hidden/visually-hidden.css +52 -0
- package/src/visually-hidden/visually-hidden.tokens.json +1 -0
- package/src/wizard/wizard.css +740 -0
- package/src/wizard/wizard.tokens.json +63 -0
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* A window over the page, and the scrim that says the page is out of reach.
|
|
4
|
+
*
|
|
5
|
+
* Three boxes, and the third one is not decoration:
|
|
6
|
+
*
|
|
7
|
+
* ┌─ root ───────────────────────────────────────────────┐
|
|
8
|
+
* │ ┌─ backdrop ─────────────────────────────────────────┐│
|
|
9
|
+
* │ │ ┌─ viewport ───────────────────────────────────────┐│
|
|
10
|
+
* │ │ │ ┌─ content ────────────┐ ││
|
|
11
|
+
* │ │ │ │ header │ ││
|
|
12
|
+
* │ │ │ ├──────────────────────┤ ││
|
|
13
|
+
* │ │ │ │ body (scrolls)│ ││
|
|
14
|
+
* │ │ │ ├──────────────────────┤ ││
|
|
15
|
+
* │ │ │ │ footer │ ││
|
|
16
|
+
* │ │ │ └──────────────────────┘ ││
|
|
17
|
+
* │ │ └──────────────────────────────────────────────────┘│
|
|
18
|
+
* │ └────────────────────────────────────────────────────┘│
|
|
19
|
+
* └──────────────────────────────────────────────────────┘
|
|
20
|
+
*
|
|
21
|
+
* `root` declares `container-type: inline-size`, which applies `contain:
|
|
22
|
+
* layout`, which makes it the containing block for every positioned
|
|
23
|
+
* descendant. A scrim positioned inside a root that scrolls is therefore part
|
|
24
|
+
* of what scrolls, and under `scroll="viewport"` it slides off the top of the
|
|
25
|
+
* layer and the page reappears behind the panel. `backdrop` and `viewport`
|
|
26
|
+
* are two cells of one one-cell grid instead: they stack with no positioning
|
|
27
|
+
* at all, and only the second of them ever scrolls.
|
|
28
|
+
*
|
|
29
|
+
* `content` is a three-row grid whose middle row is bounded. That one
|
|
30
|
+
* declaration is what keeps the actions on screen when the body is long —
|
|
31
|
+
* the failure Radix and React Aria leave to each consumer's own CSS, because
|
|
32
|
+
* neither ships a layout.
|
|
33
|
+
*
|
|
34
|
+
* Every part is reached with a child combinator. A dialog opened from inside
|
|
35
|
+
* a dialog is the ordinary case, and a descendant rule would paint the inner
|
|
36
|
+
* panel's header with the outer panel's tone.
|
|
37
|
+
*/
|
|
38
|
+
[data-ui='dialog'] {
|
|
39
|
+
/**
|
|
40
|
+
* The panel's measure, per size step. Five named steps rather than the
|
|
41
|
+
* pixel string Material, Carbon and Ant all take: a theme can retune a
|
|
42
|
+
* step and cannot retune `width: '600px'`, and a family that accepted one
|
|
43
|
+
* would be carrying a second scale of its own.
|
|
44
|
+
*
|
|
45
|
+
* `--slotted-dialog-measure` is the way out for the panel the five steps do
|
|
46
|
+
* not hold — a two-column form at 46rem, a media viewer at the width of its
|
|
47
|
+
* own image. It is a family token rather than a prop, so it is set per
|
|
48
|
+
* instance with a style attribute, per region with a rule, or once for an
|
|
49
|
+
* application in the theme; and because every step reads it, an explicit
|
|
50
|
+
* measure wins over the step whichever step is asked for.
|
|
51
|
+
*/
|
|
52
|
+
--_measure: var(--slotted-dialog-measure, 32rem);
|
|
53
|
+
/** The air between the panel and the edge of the layer. */
|
|
54
|
+
--_layer-padding: var(--slotted-space-8, 1.5rem);
|
|
55
|
+
/** The panel's own inset, which every row reads. */
|
|
56
|
+
--_padding-inline: var(--slotted-space-8, 1.5rem);
|
|
57
|
+
/**
|
|
58
|
+
* Three insets, not one number used three times, because the three rows
|
|
59
|
+
* hold three different things.
|
|
60
|
+
*
|
|
61
|
+
* The **body** is prose, and prose is what the generous inset is for: it is
|
|
62
|
+
* the one row whose air is read as air.
|
|
63
|
+
*
|
|
64
|
+
* The **header** is a title block whose own line height already carries
|
|
65
|
+
* some. A step down from the body keeps the title from floating and closes
|
|
66
|
+
* the gap between the description and the first line below the rule, which
|
|
67
|
+
* was two full insets stacked — forty pixels of nothing between a sentence
|
|
68
|
+
* and the paragraph it introduces.
|
|
69
|
+
*
|
|
70
|
+
* The **footer** is a row of controls, and a control carries its own height
|
|
71
|
+
* and its own optical padding. The same inset that reads as air around a
|
|
72
|
+
* paragraph reads as a slab around a button: twenty above and below a
|
|
73
|
+
* thirty-four pixel control is a seventy-four pixel bar for one line.
|
|
74
|
+
*/
|
|
75
|
+
--_padding-block: var(--slotted-space-7, 1.25rem);
|
|
76
|
+
--_header-padding-block: var(--slotted-space-6, 1rem);
|
|
77
|
+
--_footer-padding-block: var(--slotted-space-5, 0.75rem);
|
|
78
|
+
--_gap: var(--slotted-space-5, 0.75rem);
|
|
79
|
+
--_rule-size: var(--slotted-border-width-thin, 1px);
|
|
80
|
+
/**
|
|
81
|
+
* The title is on the type scale rather than on the control type scale.
|
|
82
|
+
* A control's label is 12 to 14 pixels because it lives inside a button; a
|
|
83
|
+
* dialog's title is a heading, and the outline it belongs to is set like
|
|
84
|
+
* every other heading in the library. Density still moves the geometry,
|
|
85
|
+
* which is what "density changes size without changing meaning" asks for.
|
|
86
|
+
*/
|
|
87
|
+
--_title-font-size: var(--slotted-font-size-xl, 1.125rem);
|
|
88
|
+
/**
|
|
89
|
+
* 1.5 rather than the heading step's 1.45, because the title's line is the
|
|
90
|
+
* one length every framed row above the footer adds up from. At 1.45 the
|
|
91
|
+
* sixteen-pixel title of `sm` drew a 23.2 pixel line and put the header at
|
|
92
|
+
* 114.19 and the panel at 167.19; at 1.5 both title steps land on a whole
|
|
93
|
+
* pixel, 24 and 27 (dossier `2026-09-29-alert-dialog-reui-dossier.md`).
|
|
94
|
+
*/
|
|
95
|
+
--_title-line-height: var(--slotted-line-height-md, 1.5);
|
|
96
|
+
--_description-font-size: var(--slotted-font-size-md, 0.875rem);
|
|
97
|
+
--_body-font-size: var(--slotted-font-size-md, 0.875rem);
|
|
98
|
+
--_icon-size: var(--slotted-control-icon-size-lg, 18px);
|
|
99
|
+
--_inner-size: var(--slotted-control-inner-size-lg, 28px);
|
|
100
|
+
/**
|
|
101
|
+
* The close control's box: an inner control, not a button (R2), so the
|
|
102
|
+
* glyph plus a step of air on each side, floored at the WCAG 2.2 minimum
|
|
103
|
+
* so the target holds at every density.
|
|
104
|
+
*/
|
|
105
|
+
--_close-size: max(
|
|
106
|
+
var(--slotted-target-min, 24px),
|
|
107
|
+
min(calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
|
|
108
|
+
);
|
|
109
|
+
/**
|
|
110
|
+
* The mark of an alert dialog is the title's own size (R5), and eight
|
|
111
|
+
* pixels from it. It was a step and a half above the title before, which
|
|
112
|
+
* put a twenty-four-pixel glyph beside an eighteen-pixel headline and read
|
|
113
|
+
* as a picture standing next to the words rather than as part of them.
|
|
114
|
+
*/
|
|
115
|
+
--_status-size: var(--_title-font-size);
|
|
116
|
+
--_mark-gap: var(--slotted-space-4, 0.5rem);
|
|
117
|
+
--_tone-color: var(--slotted-tone-danger-text, CanvasText);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The one z-index in the family, and it is a sum. `dismissable-layer`
|
|
121
|
+
* writes `--slotted-layer-depth` on this element as it opens, so a dialog
|
|
122
|
+
* opened over a dialog is one step above it on the single stack the
|
|
123
|
+
* library keeps rather than a number this stylesheet guessed.
|
|
124
|
+
*/
|
|
125
|
+
position: fixed;
|
|
126
|
+
inset: 0;
|
|
127
|
+
z-index: calc(var(--slotted-z-index-overlay, 1000) + var(--slotted-layer-depth, 0));
|
|
128
|
+
|
|
129
|
+
display: grid;
|
|
130
|
+
grid-template: minmax(0, 1fr) / minmax(0, 1fr);
|
|
131
|
+
|
|
132
|
+
container-type: inline-size;
|
|
133
|
+
container-name: slotted-dialog;
|
|
134
|
+
|
|
135
|
+
color: var(--slotted-dialog-color, var(--slotted-text, CanvasText));
|
|
136
|
+
font-family: var(--slotted-control-font-family, var(--slotted-font-family-sans));
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `allow-discrete` is the whole of it. Without this line the rule below
|
|
140
|
+
* hides the layer at the first frame of the exit and the two hundred
|
|
141
|
+
* milliseconds `presence` is holding the node mounted for show nothing;
|
|
142
|
+
* with it, the flip to `hidden` happens at the end of the duration instead.
|
|
143
|
+
* `mount="active"` never reaches the closed state — the layer is gone — so
|
|
144
|
+
* this is what `eager` and `lazy` need to animate at all.
|
|
145
|
+
*/
|
|
146
|
+
content-visibility: visible;
|
|
147
|
+
transition: content-visibility var(--slotted-duration-medium) allow-discrete;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Mounted and closed. `eager` and `lazy` leave the layer in the document, and
|
|
152
|
+
* a layer left visible would cover the page it is not on. The rule keys on
|
|
153
|
+
* `inert`, which the binding writes while the layer is closed or exiting,
|
|
154
|
+
* and not on the absence of `data-open`: the entering frame has no
|
|
155
|
+
* `data-open` either, and a hidden layer in that frame refuses the focus
|
|
156
|
+
* scope's initial focus.
|
|
157
|
+
*/
|
|
158
|
+
[data-ui='dialog'][inert] {
|
|
159
|
+
content-visibility: hidden;
|
|
160
|
+
pointer-events: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A non-modal layer leaves the page reachable, and that includes the pointer
|
|
165
|
+
* and the wheel. The layer is fixed over the whole viewport, so it lets the
|
|
166
|
+
* pointer through and paints no scrim: the scrim says the page is out of
|
|
167
|
+
* reach, which is what a non-modal layer is not. The panel alone takes the
|
|
168
|
+
* pointer, and a press on the page is the outside press the dismissal policy
|
|
169
|
+
* answers.
|
|
170
|
+
*/
|
|
171
|
+
[data-ui='dialog']:not([data-modal]) {
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
[data-ui='dialog']:not([data-modal]) > [data-part='backdrop'] {
|
|
176
|
+
visibility: hidden;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
[data-ui='dialog']:not([data-modal]) > [data-part='viewport'] > [data-part='content'] {
|
|
180
|
+
pointer-events: auto;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
[data-ui='dialog'][data-size='sm'] {
|
|
184
|
+
--_measure: var(--slotted-dialog-measure, 24rem);
|
|
185
|
+
--_padding-inline: var(--slotted-space-7, 1.25rem);
|
|
186
|
+
--_padding-block: var(--slotted-space-6, 1rem);
|
|
187
|
+
--_header-padding-block: var(--slotted-space-5, 0.75rem);
|
|
188
|
+
--_footer-padding-block: var(--slotted-space-4, 0.5rem);
|
|
189
|
+
--_title-font-size: var(--slotted-font-size-lg, 1rem);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
[data-ui='dialog'][data-size='lg'] {
|
|
193
|
+
--_measure: var(--slotted-dialog-measure, 42rem);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
[data-ui='dialog'][data-size='xl'] {
|
|
197
|
+
--_measure: var(--slotted-dialog-measure, 56rem);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* `full` is the step a dialog that is a document takes: the panel spends the
|
|
202
|
+
* whole layer minus its padding, and the measure stops capping it.
|
|
203
|
+
*/
|
|
204
|
+
[data-ui='dialog'][data-size='full'] {
|
|
205
|
+
--_measure: var(--slotted-dialog-measure, none);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* ── The scrim ────────────────────────────────────────────────────────── */
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* `--slotted-scrim` is a foundation token the theme restates per scheme and
|
|
212
|
+
* remaps to `Canvas` under forced colours, where it becomes an opaque ground.
|
|
213
|
+
* That is why the panel below carries a border of its own: every shadow token
|
|
214
|
+
* is `none` in that mode, and a panel separated only by elevation would sit
|
|
215
|
+
* on an opaque ground with nothing to mark its edge.
|
|
216
|
+
*/
|
|
217
|
+
[data-ui='dialog'] > [data-part='backdrop'] {
|
|
218
|
+
grid-area: 1 / 1;
|
|
219
|
+
background: var(--slotted-dialog-scrim, var(--slotted-scrim, Canvas));
|
|
220
|
+
opacity: 0;
|
|
221
|
+
transition: opacity var(--slotted-duration-short) var(--slotted-easing-exit);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The arrival is the long step on the standard curve, and both halves of
|
|
226
|
+
* that are a correction (DG4). On the entrance curve the scrim was at
|
|
227
|
+
* 57.6 per cent of its ink by the second frame of the transition and at
|
|
228
|
+
* 83 per cent by the fourth — a curve written for something that travels
|
|
229
|
+
* and decelerates into place, spent on something that only changes ink —
|
|
230
|
+
* and it was reported as a scrim that simply appeared. The standard curve
|
|
231
|
+
* over the long step spends the frames where they can be seen: measured on
|
|
232
|
+
* the same page, 10.8 per cent at 25 milliseconds and 55 per cent at 100.
|
|
233
|
+
*
|
|
234
|
+
* The panel below arrives over the same duration and the same curve, so the
|
|
235
|
+
* two halves of one layer arrive together.
|
|
236
|
+
*/
|
|
237
|
+
[data-ui='dialog'][data-open] > [data-part='backdrop'] {
|
|
238
|
+
opacity: 1;
|
|
239
|
+
transition: opacity var(--slotted-duration-long) var(--slotted-easing-standard);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ── The box the panel sits in ────────────────────────────────────────── */
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* `safe center` rather than `center`. A panel taller than the layer, centred
|
|
246
|
+
* by the unsafe keyword, overflows equally at both ends — and the end that
|
|
247
|
+
* carries the title is the one that leaves the screen. `safe` aligns to the
|
|
248
|
+
* start edge as soon as the item cannot fit, which is the only reading that
|
|
249
|
+
* keeps the title reachable at 200% zoom and in a short window.
|
|
250
|
+
*/
|
|
251
|
+
[data-ui='dialog'] > [data-part='viewport'] {
|
|
252
|
+
grid-area: 1 / 1;
|
|
253
|
+
display: grid;
|
|
254
|
+
grid-template-columns: minmax(0, 1fr);
|
|
255
|
+
align-content: safe center;
|
|
256
|
+
justify-items: center;
|
|
257
|
+
padding: var(--_layer-padding);
|
|
258
|
+
/**
|
|
259
|
+
* The last resort, in both scroll modes. Under `scroll="content"` the panel
|
|
260
|
+
* is bounded to the layer and its body is what scrolls — but a panel with
|
|
261
|
+
* no body, or one whose header and footer alone are taller than the layer,
|
|
262
|
+
* has no row that can shrink, and `auto` rows do not. The first capture
|
|
263
|
+
* showed exactly that: a title of three lines and a description of three
|
|
264
|
+
* more pushed the actions past the bottom edge of a short layer, and the
|
|
265
|
+
* only thing between the reader and an unreachable button was this line.
|
|
266
|
+
*/
|
|
267
|
+
overflow: auto;
|
|
268
|
+
overscroll-behavior: contain;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
[data-ui='dialog'][data-placement='top'] > [data-part='viewport'] {
|
|
272
|
+
align-content: safe start;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Under `scroll="viewport"` the panel is unbounded and this box is what
|
|
277
|
+
* scrolls it, which is the rule below on `content`. The scrim does not move
|
|
278
|
+
* with it, because it is the other cell of the grid above rather than a child
|
|
279
|
+
* of this box.
|
|
280
|
+
*/
|
|
281
|
+
/* ── The panel ────────────────────────────────────────────────────────── */
|
|
282
|
+
|
|
283
|
+
[data-ui='dialog'] > [data-part='viewport'] > [data-part='content'] {
|
|
284
|
+
display: grid;
|
|
285
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
286
|
+
inline-size: 100%;
|
|
287
|
+
max-inline-size: var(--_measure);
|
|
288
|
+
/* Bounded, so the middle row is the one that scrolls and the footer stays. */
|
|
289
|
+
max-block-size: 100%;
|
|
290
|
+
|
|
291
|
+
background: var(--slotted-dialog-surface, var(--slotted-surface-raised, Canvas));
|
|
292
|
+
/**
|
|
293
|
+
* The subtle step, and the reason is what is actually doing the separating.
|
|
294
|
+
*
|
|
295
|
+
* The panel is told apart from the scrim by its **fill**, not by its edge:
|
|
296
|
+
* in the dark scheme the panel is `#1c1c20` over a backdrop that composites
|
|
297
|
+
* to about `#070708`, and in the light scheme it is white over about
|
|
298
|
+
* `#93979f`. The border only finishes the edge, so it takes the step for a
|
|
299
|
+
* line inside a surface rather than the step for a line between two.
|
|
300
|
+
*
|
|
301
|
+
* It was the strong step for one iteration, on the reading that a shadow of
|
|
302
|
+
* black on black leaves nothing in the dark scheme. That is true of the
|
|
303
|
+
* shadow and not of the panel: the strong step is `#71717a` against a
|
|
304
|
+
* `#1c1c20` panel, a jump of eighty-five per channel, and it read as a
|
|
305
|
+
* drawn stroke around every dialog.
|
|
306
|
+
*
|
|
307
|
+
* Forced colours is where an edge does have to carry the separation, since
|
|
308
|
+
* `--slotted-scrim` becomes an opaque `Canvas` there and every shadow token
|
|
309
|
+
* becomes `none`. The rule at the end of this file is that case, and it
|
|
310
|
+
* thickens the border to say so.
|
|
311
|
+
*/
|
|
312
|
+
border: var(--_rule-size) solid
|
|
313
|
+
var(--slotted-dialog-border-color, var(--slotted-border-subtle, ButtonBorder));
|
|
314
|
+
border-radius: var(--slotted-dialog-radius, var(--slotted-radius-lg, 8px));
|
|
315
|
+
box-shadow: var(--slotted-shadow-lg);
|
|
316
|
+
/* So the radius clips the row that scrolls under it. */
|
|
317
|
+
overflow: hidden;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The panel arrives by changing its ink and by nothing else.
|
|
321
|
+
*
|
|
322
|
+
* It grew into place for one iteration, from `0.98`, and R7 refuses that:
|
|
323
|
+
* a box with a title, a body and a row of buttons in it resamples every
|
|
324
|
+
* letter it holds while it is being scaled. Measured on the way in, the
|
|
325
|
+
* title's own box was up to 6.82 pixels narrower than at rest and off the
|
|
326
|
+
* pixel grid in thirteen frames of twenty-eight;
|
|
327
|
+
* `packages/styles/src/text-scale.test.mjs` holds that rule.
|
|
328
|
+
*
|
|
329
|
+
* Then it travelled four pixels instead, and the same defect was reported
|
|
330
|
+
* again (DG4). A box the browser is moving is a composited layer, and a
|
|
331
|
+
* layer moved by a fraction of a pixel has its text resampled for every
|
|
332
|
+
* frame it is between two of them: measured, eleven of the twelve frames
|
|
333
|
+
* of the movement landed on a fractional position — 32.305, 32.973,
|
|
334
|
+
* 33.326 — and the reader sees the words lose their edges and get them
|
|
335
|
+
* back. A centred panel has nothing for the movement to be *from* either,
|
|
336
|
+
* which is what the first draft of this rule said before the travel was
|
|
337
|
+
* written into it.
|
|
338
|
+
*
|
|
339
|
+
* So the entrance is `opacity` alone, at the same duration and on the same
|
|
340
|
+
* curve as the scrim behind it. Nothing about the geometry changes while
|
|
341
|
+
* the panel arrives, and there is no frame in which the type is drawn at a
|
|
342
|
+
* position it does not keep.
|
|
343
|
+
*/
|
|
344
|
+
opacity: 0;
|
|
345
|
+
transition: opacity var(--slotted-duration-short) var(--slotted-easing-exit);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Arriving is longer than leaving, and on a different curve. A dialog
|
|
350
|
+
* appearing is a state change the eye has to follow, because the page it
|
|
351
|
+
* covers is still visible behind it; a reader who has decided to close is
|
|
352
|
+
* waiting, so the exit is the short duration.
|
|
353
|
+
*
|
|
354
|
+
* The panel placed at the block start arrived by sliding twelve pixels from
|
|
355
|
+
* the edge it sits on, and it is on the same rule now for the same reason:
|
|
356
|
+
* the type inside it is what the slide was resampling (DG4).
|
|
357
|
+
*/
|
|
358
|
+
[data-ui='dialog'][data-open] > [data-part='viewport'] > [data-part='content'] {
|
|
359
|
+
opacity: 1;
|
|
360
|
+
transition: opacity var(--slotted-duration-long) var(--slotted-easing-standard);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Reduced motion has nothing left to drop here: neither the panel nor the
|
|
365
|
+
* scrim moves, and a fade is what the preference keeps. The theme collapses
|
|
366
|
+
* every duration token under this query, so the arrival becomes instant
|
|
367
|
+
* without a rule of its own — `motion.test.mjs` holds that for every family.
|
|
368
|
+
* The rule the family did carry was the one that stopped the panel
|
|
369
|
+
* travelling, and there is no travel to stop (DG4).
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
[data-ui='dialog'][data-scroll='viewport'] > [data-part='viewport'] > [data-part='content'] {
|
|
373
|
+
max-block-size: none;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* ── The header ───────────────────────────────────────────────────────── */
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Four columns: the mark, the words, the full-screen toggle and the close
|
|
380
|
+
* button. Each mark and each control keeps a column of its own, so a title
|
|
381
|
+
* that wraps to two lines grows the row instead of pushing any of them off
|
|
382
|
+
* the line. The toggle's track is empty and therefore nought wide unless the
|
|
383
|
+
* layer renders it, which is why the column gap is nought.
|
|
384
|
+
*
|
|
385
|
+
* The header draws no rule of its own. Every rule in the panel belongs to the
|
|
386
|
+
* row *below* it, which is what makes the count right for every combination a
|
|
387
|
+
* consumer can write: a header, a body and a footer draw two rules; a header
|
|
388
|
+
* and a footer alone draw one; and a panel that is only a body draws none.
|
|
389
|
+
* The first capture of an alert dialog — a header and a footer, with no body
|
|
390
|
+
* between them — showed the header's bottom rule and the footer's top rule
|
|
391
|
+
* stacked into one line of two pixels.
|
|
392
|
+
*
|
|
393
|
+
* The rules are drawn whether the body scrolls or not. `scroll-state()`
|
|
394
|
+
* container queries would let the stylesheet know, and they are limited
|
|
395
|
+
* availability — the same ground on which `collapsible` declined
|
|
396
|
+
* `interpolate-size`. The cost is a rule over a footer that sometimes has
|
|
397
|
+
* nothing above it to separate.
|
|
398
|
+
*/
|
|
399
|
+
[data-ui='dialog'] > [data-part='viewport'] > [data-part='content'] > [data-part='header'] {
|
|
400
|
+
display: grid;
|
|
401
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
402
|
+
align-items: start;
|
|
403
|
+
/* The distance from the mark to the words belongs to the mark, and the
|
|
404
|
+
distance from the words to the close button to the button: a column gap
|
|
405
|
+
is drawn beside an empty track too, so a dialog with no mark would start
|
|
406
|
+
its title one gap in from the inset. */
|
|
407
|
+
column-gap: 0;
|
|
408
|
+
row-gap: var(--slotted-space-2, 0.25rem);
|
|
409
|
+
padding-block: var(--_header-padding-block);
|
|
410
|
+
padding-inline: var(--_padding-inline);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Everything in the header lands in the words column unless the family placed
|
|
415
|
+
* it somewhere else. The four rules below place the mark, the title, the
|
|
416
|
+
* description and the close button; this is what happens to the fifth thing —
|
|
417
|
+
* a badge beside the title, a breadcrumb, a line the consumer wrote.
|
|
418
|
+
*
|
|
419
|
+
* Without it those children auto-placed into whichever cell was free, which is
|
|
420
|
+
* column one whenever no icon was rendered: a consumer's own element landed
|
|
421
|
+
* where the mark would have gone, and moved as soon as a tone was set. Where
|
|
422
|
+
* it goes is now a rule rather than an accident of the grid.
|
|
423
|
+
*/
|
|
424
|
+
[data-ui='dialog']
|
|
425
|
+
> [data-part='viewport']
|
|
426
|
+
> [data-part='content']
|
|
427
|
+
> [data-part='header']
|
|
428
|
+
> *:not(
|
|
429
|
+
[data-part='icon'],
|
|
430
|
+
[data-part='title'],
|
|
431
|
+
[data-part='description'],
|
|
432
|
+
[data-part='fullscreen'],
|
|
433
|
+
[data-part='close']
|
|
434
|
+
) {
|
|
435
|
+
grid-column: 2;
|
|
436
|
+
/**
|
|
437
|
+
* `start`, not the grid's own `stretch`. A badge stretched across the words
|
|
438
|
+
* column paints its ground the full width and centres its label in it,
|
|
439
|
+
* which is what the first capture showed. The five placed parts are excluded
|
|
440
|
+
* by name rather than by `:not([data-part])`, because what a consumer puts
|
|
441
|
+
* here is usually another family and that carries a `data-part` of its own.
|
|
442
|
+
*/
|
|
443
|
+
justify-self: start;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* ── The mark an alert dialog carries ─────────────────────────────────── */
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Real geometry: the binding draws the tone's own `Glyph` inside an `icon`
|
|
450
|
+
* box, and the slot gives it the tone's text colour and the mark's size.
|
|
451
|
+
* WCAG 2.2 SC 1.4.1 asks that colour not be the only carrier, and a
|
|
452
|
+
* destructive confirmation is the place that matters most: under forced
|
|
453
|
+
* colours every tone becomes one system colour and the silhouette is all
|
|
454
|
+
* that is left.
|
|
455
|
+
*/
|
|
456
|
+
[data-ui='dialog']
|
|
457
|
+
> [data-part='viewport']
|
|
458
|
+
> [data-part='content']
|
|
459
|
+
> [data-part='header']
|
|
460
|
+
> [data-part='icon'] {
|
|
461
|
+
--slotted-icon-size: var(--_status-size);
|
|
462
|
+
|
|
463
|
+
grid-column: 1;
|
|
464
|
+
grid-row: 1 / span 2;
|
|
465
|
+
align-self: start;
|
|
466
|
+
display: inline-grid;
|
|
467
|
+
place-items: center;
|
|
468
|
+
color: var(--_tone-color);
|
|
469
|
+
block-size: var(--_status-size);
|
|
470
|
+
inline-size: var(--_status-size);
|
|
471
|
+
flex: none;
|
|
472
|
+
/* The mark sits on the title's first line rather than on its box: half
|
|
473
|
+
the difference between the title's line and the mark, in the title's own
|
|
474
|
+
type, because the header's line is not the title's. */
|
|
475
|
+
margin-block-start: calc(
|
|
476
|
+
(var(--_title-font-size) * var(--_title-line-height) - var(--_status-size)) / 2
|
|
477
|
+
);
|
|
478
|
+
margin-inline-end: var(--_mark-gap);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
[data-ui='dialog'][data-tone='accent'] {
|
|
482
|
+
--_tone-color: var(--slotted-tone-accent-text, CanvasText);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
[data-ui='dialog'][data-tone='secondary'] {
|
|
486
|
+
--_tone-color: var(--slotted-tone-secondary-text, CanvasText);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
[data-ui='dialog'][data-tone='success'] {
|
|
490
|
+
--_tone-color: var(--slotted-tone-success-text, CanvasText);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
[data-ui='dialog'][data-tone='warning'] {
|
|
494
|
+
--_tone-color: var(--slotted-tone-warning-text, CanvasText);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
[data-ui='dialog'][data-tone='danger'] {
|
|
498
|
+
--_tone-color: var(--slotted-tone-danger-text, CanvasText);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* ── The words ────────────────────────────────────────────────────────── */
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* `overflow-wrap: anywhere` and a zero minimum are the pair that answer a
|
|
505
|
+
* hostile string. A ninety-character unbroken token in a title widens the
|
|
506
|
+
* panel past the layer, and the panel is centred, so both of its edges leave
|
|
507
|
+
* the screen at once.
|
|
508
|
+
*/
|
|
509
|
+
[data-ui='dialog']
|
|
510
|
+
> [data-part='viewport']
|
|
511
|
+
> [data-part='content']
|
|
512
|
+
> [data-part='header']
|
|
513
|
+
> [data-part='title'] {
|
|
514
|
+
grid-column: 2;
|
|
515
|
+
grid-row: 1;
|
|
516
|
+
margin: 0;
|
|
517
|
+
min-inline-size: 0;
|
|
518
|
+
overflow-wrap: anywhere;
|
|
519
|
+
font-family: var(
|
|
520
|
+
--slotted-dialog-title-font-family,
|
|
521
|
+
var(
|
|
522
|
+
--slotted-font-family-heading,
|
|
523
|
+
var(--slotted-font-family-sans, ui-sans-serif, system-ui, sans-serif)
|
|
524
|
+
)
|
|
525
|
+
);
|
|
526
|
+
font-size: var(--_title-font-size);
|
|
527
|
+
font-weight: var(--slotted-font-weight-semibold, 600);
|
|
528
|
+
line-height: var(--_title-line-height);
|
|
529
|
+
color: var(--slotted-dialog-title-color, var(--slotted-text, CanvasText));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
[data-ui='dialog']
|
|
533
|
+
> [data-part='viewport']
|
|
534
|
+
> [data-part='content']
|
|
535
|
+
> [data-part='header']
|
|
536
|
+
> [data-part='description'] {
|
|
537
|
+
grid-column: 2;
|
|
538
|
+
grid-row: 2;
|
|
539
|
+
margin: 0;
|
|
540
|
+
min-inline-size: 0;
|
|
541
|
+
overflow-wrap: anywhere;
|
|
542
|
+
font-size: var(--_description-font-size);
|
|
543
|
+
line-height: var(--slotted-line-height-md, 1.5);
|
|
544
|
+
color: var(--slotted-dialog-description-color, var(--slotted-text-muted, GrayText));
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* The name of the thing the dialog acts on. A confirmation says which
|
|
549
|
+
* project or which member it removes, and a consumer marks that name with
|
|
550
|
+
* `strong`; left to the user agent it was bold in the muted colour, heavier
|
|
551
|
+
* than the title and no easier to find. It takes the title's colour and
|
|
552
|
+
* weight instead, which is what both product confirmations in the survey do
|
|
553
|
+
* with a span of their own.
|
|
554
|
+
*/
|
|
555
|
+
[data-ui='dialog']
|
|
556
|
+
> [data-part='viewport']
|
|
557
|
+
> [data-part='content']
|
|
558
|
+
> [data-part='header']
|
|
559
|
+
> [data-part='description']
|
|
560
|
+
:is(strong, b) {
|
|
561
|
+
font-weight: var(--slotted-font-weight-semibold, 600);
|
|
562
|
+
color: var(--slotted-dialog-title-color, var(--slotted-text, CanvasText));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/* ── The close button ─────────────────────────────────────────────────── */
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* An inner control, not a button (R2): the box is the glyph plus a step of
|
|
569
|
+
* air on each side, floored at the WCAG 2.2 minimum, and the glyph is
|
|
570
|
+
* centred in it. It was the control height before, a thirty-four pixel
|
|
571
|
+
* square beside an eighteen pixel mark. The glyph is the binding's `Glyph`
|
|
572
|
+
* in an `icon` box, sized here through the icon token so the box and the
|
|
573
|
+
* formula above read one length.
|
|
574
|
+
*/
|
|
575
|
+
[data-ui='dialog']
|
|
576
|
+
> [data-part='viewport']
|
|
577
|
+
> [data-part='content']
|
|
578
|
+
> [data-part='header']
|
|
579
|
+
> [data-part='close'] {
|
|
580
|
+
--slotted-icon-size: var(--_icon-size);
|
|
581
|
+
|
|
582
|
+
grid-column: 4;
|
|
583
|
+
grid-row: 1 / span 2;
|
|
584
|
+
align-self: start;
|
|
585
|
+
display: inline-grid;
|
|
586
|
+
place-items: center;
|
|
587
|
+
block-size: var(--_close-size);
|
|
588
|
+
inline-size: var(--_close-size);
|
|
589
|
+
/* The box overhangs the panel's edge by its own air, so the mark aligns
|
|
590
|
+
with the title. */
|
|
591
|
+
margin-inline-end: calc(-1 * (var(--_close-size) - var(--_icon-size)) / 2);
|
|
592
|
+
margin-inline-start: var(--_gap);
|
|
593
|
+
padding: 0;
|
|
594
|
+
appearance: none;
|
|
595
|
+
background-color: transparent;
|
|
596
|
+
border: 0;
|
|
597
|
+
border-radius: var(--slotted-control-radius, var(--slotted-radius-sm, 4px));
|
|
598
|
+
color: var(--slotted-dialog-close-color, var(--slotted-text-muted, GrayText));
|
|
599
|
+
cursor: pointer;
|
|
600
|
+
transition:
|
|
601
|
+
background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
602
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
|
|
603
|
+
color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
604
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
[data-ui='dialog']
|
|
608
|
+
> [data-part='viewport']
|
|
609
|
+
> [data-part='content']
|
|
610
|
+
> [data-part='header']
|
|
611
|
+
> [data-part='close']:not(:disabled):hover {
|
|
612
|
+
/* The ground moves under the pointer and nothing else does (R2). */
|
|
613
|
+
background-color: var(
|
|
614
|
+
--slotted-dialog-close-hover-background,
|
|
615
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
[data-ui='dialog']
|
|
620
|
+
> [data-part='viewport']
|
|
621
|
+
> [data-part='content']
|
|
622
|
+
> [data-part='header']
|
|
623
|
+
> [data-part='close']:not(:disabled):active {
|
|
624
|
+
background-color: var(
|
|
625
|
+
--slotted-dialog-close-active-background,
|
|
626
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
[data-ui='dialog']
|
|
631
|
+
> [data-part='viewport']
|
|
632
|
+
> [data-part='content']
|
|
633
|
+
> [data-part='header']
|
|
634
|
+
> [data-part='close']:focus-visible {
|
|
635
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
636
|
+
outline-offset: calc(-1 * var(--slotted-focus-ring-offset, 2px));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/* ── The full-screen toggle ───────────────────────────────────────────── */
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* The reader's own control for taking the layer, and the same inner control
|
|
643
|
+
* the close is (R2): the box is the glyph plus a step of air on each side,
|
|
644
|
+
* floored at the WCAG 2.2 minimum, and the glyph is centred in it. The
|
|
645
|
+
* geometry is not chosen here — it is `--_close-size`, the one formula this
|
|
646
|
+
* family already states, so the two controls in the corner are one row of
|
|
647
|
+
* one size.
|
|
648
|
+
*
|
|
649
|
+
* It sits in the column before the close, which is where a window's controls
|
|
650
|
+
* put it, and it carries no end overhang: the overhang belongs to whichever
|
|
651
|
+
* control stands against the panel's inset, and that is the close. Its
|
|
652
|
+
* glyph is the binding's, chosen from the state: the mark says what the
|
|
653
|
+
* press does next, so it is the opposite of the state the panel is in.
|
|
654
|
+
*/
|
|
655
|
+
[data-ui='dialog']
|
|
656
|
+
> [data-part='viewport']
|
|
657
|
+
> [data-part='content']
|
|
658
|
+
> [data-part='header']
|
|
659
|
+
> [data-part='fullscreen'] {
|
|
660
|
+
--slotted-icon-size: var(--_icon-size);
|
|
661
|
+
|
|
662
|
+
grid-column: 3;
|
|
663
|
+
grid-row: 1 / span 2;
|
|
664
|
+
align-self: start;
|
|
665
|
+
display: inline-grid;
|
|
666
|
+
place-items: center;
|
|
667
|
+
block-size: var(--_close-size);
|
|
668
|
+
inline-size: var(--_close-size);
|
|
669
|
+
margin-inline-start: var(--_gap);
|
|
670
|
+
padding: 0;
|
|
671
|
+
appearance: none;
|
|
672
|
+
background-color: transparent;
|
|
673
|
+
border: 0;
|
|
674
|
+
border-radius: var(--slotted-control-radius, var(--slotted-radius-sm, 4px));
|
|
675
|
+
color: var(--slotted-dialog-close-color, var(--slotted-text-muted, GrayText));
|
|
676
|
+
cursor: pointer;
|
|
677
|
+
transition:
|
|
678
|
+
background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
679
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
|
|
680
|
+
color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
681
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* The two controls in the corner are neighbours, so the air between them is
|
|
686
|
+
* their own: each box already carries half the difference between it and its
|
|
687
|
+
* glyph on every side, and a second gap between them would set them further
|
|
688
|
+
* apart than either is from the words.
|
|
689
|
+
*/
|
|
690
|
+
[data-ui='dialog']
|
|
691
|
+
> [data-part='viewport']
|
|
692
|
+
> [data-part='content']
|
|
693
|
+
> [data-part='header']
|
|
694
|
+
> [data-part='fullscreen']
|
|
695
|
+
+ [data-part='close'] {
|
|
696
|
+
margin-inline-start: 0;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
[data-ui='dialog']
|
|
700
|
+
> [data-part='viewport']
|
|
701
|
+
> [data-part='content']
|
|
702
|
+
> [data-part='header']
|
|
703
|
+
> [data-part='fullscreen']:not(:disabled):hover {
|
|
704
|
+
/* The ground moves under the pointer and nothing else does (R2). */
|
|
705
|
+
background-color: var(
|
|
706
|
+
--slotted-dialog-close-hover-background,
|
|
707
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
[data-ui='dialog']
|
|
712
|
+
> [data-part='viewport']
|
|
713
|
+
> [data-part='content']
|
|
714
|
+
> [data-part='header']
|
|
715
|
+
> [data-part='fullscreen']:not(:disabled):active {
|
|
716
|
+
background-color: var(
|
|
717
|
+
--slotted-dialog-close-active-background,
|
|
718
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
[data-ui='dialog']
|
|
723
|
+
> [data-part='viewport']
|
|
724
|
+
> [data-part='content']
|
|
725
|
+
> [data-part='header']
|
|
726
|
+
> [data-part='fullscreen']:focus-visible {
|
|
727
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
728
|
+
outline-offset: calc(-1 * var(--slotted-focus-ring-offset, 2px));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* On a finger the target is the comfortable size in both axes and the box
|
|
733
|
+
* is the one a mouse sees: a transparent `::before` centred on each control
|
|
734
|
+
* reaches 44 pixels, and the ground and the ring stay square. The two
|
|
735
|
+
* controls touch, so neither reaches across the edge they share.
|
|
736
|
+
*/
|
|
737
|
+
@media (pointer: coarse) {
|
|
738
|
+
[data-ui='dialog']
|
|
739
|
+
> [data-part='viewport']
|
|
740
|
+
> [data-part='content']
|
|
741
|
+
> [data-part='header']
|
|
742
|
+
> :is([data-part='close'], [data-part='fullscreen']) {
|
|
743
|
+
position: relative;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
[data-ui='dialog']
|
|
747
|
+
> [data-part='viewport']
|
|
748
|
+
> [data-part='content']
|
|
749
|
+
> [data-part='header']
|
|
750
|
+
> :is([data-part='close'], [data-part='fullscreen'])::before {
|
|
751
|
+
content: '';
|
|
752
|
+
inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
|
|
753
|
+
position: absolute;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
[data-ui='dialog']
|
|
757
|
+
> [data-part='viewport']
|
|
758
|
+
> [data-part='content']
|
|
759
|
+
> [data-part='header']
|
|
760
|
+
> [data-part='fullscreen']:has(+ [data-part='close'])::before {
|
|
761
|
+
inset-inline-end: 0;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
[data-ui='dialog']
|
|
765
|
+
> [data-part='viewport']
|
|
766
|
+
> [data-part='content']
|
|
767
|
+
> [data-part='header']
|
|
768
|
+
> [data-part='fullscreen']
|
|
769
|
+
+ [data-part='close']::before {
|
|
770
|
+
inset-inline-start: 0;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/* ── The row that scrolls ─────────────────────────────────────────────── */
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* `overscroll-behavior: contain` is what keeps a wheel that reaches the end
|
|
778
|
+
* of this box from scrolling the page behind the layer. The scroll lock in
|
|
779
|
+
* `dismissable-layer` covers the modal case; this covers the non-modal one,
|
|
780
|
+
* where the page behind is not locked at all.
|
|
781
|
+
*/
|
|
782
|
+
[data-ui='dialog']
|
|
783
|
+
> [data-part='viewport']
|
|
784
|
+
> [data-part='content']
|
|
785
|
+
> [data-part='body']:not(:first-child) {
|
|
786
|
+
border-block-start: var(--_rule-size) solid
|
|
787
|
+
var(--slotted-dialog-rule-color, var(--slotted-border-subtle, ButtonBorder));
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
[data-ui='dialog'] > [data-part='viewport'] > [data-part='content'] > [data-part='body'] {
|
|
791
|
+
min-block-size: 0;
|
|
792
|
+
overflow-x: hidden;
|
|
793
|
+
overflow-y: auto;
|
|
794
|
+
overflow-inline: hidden;
|
|
795
|
+
overflow-block: auto;
|
|
796
|
+
overscroll-behavior: contain;
|
|
797
|
+
padding-block: var(--_padding-block);
|
|
798
|
+
padding-inline: var(--_padding-inline);
|
|
799
|
+
font-size: var(--_body-font-size);
|
|
800
|
+
line-height: var(--slotted-line-height-md, 1.5);
|
|
801
|
+
color: var(--slotted-dialog-body-color, var(--slotted-text, CanvasText));
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
[data-ui='dialog'][data-scroll='viewport']
|
|
805
|
+
> [data-part='viewport']
|
|
806
|
+
> [data-part='content']
|
|
807
|
+
> [data-part='body'] {
|
|
808
|
+
overflow: visible;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Content that is not there yet. No surveyed implementation has a state for
|
|
813
|
+
* it, so a consumer renders a spinner conditionally and the panel jumps to
|
|
814
|
+
* size when the data lands. The row keeps a floor here instead, and the
|
|
815
|
+
* header, the close button and Escape all stay operable: a loading dialog a
|
|
816
|
+
* reader cannot leave is the worse failure of the two.
|
|
817
|
+
*/
|
|
818
|
+
[data-ui='dialog'][data-loading]
|
|
819
|
+
> [data-part='viewport']
|
|
820
|
+
> [data-part='content']
|
|
821
|
+
> [data-part='body'] {
|
|
822
|
+
min-block-size: var(--slotted-space-12, 4rem);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/* ── The actions ──────────────────────────────────────────────────────── */
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* `flex-wrap` rather than a grid: two actions that do not fit on one line
|
|
829
|
+
* stack, and the primary one stays last in both readings. The row is the
|
|
830
|
+
* reason `content` is bounded — an unbounded panel pushes it past the fold,
|
|
831
|
+
* which is the whole failure the three-row grid exists to prevent.
|
|
832
|
+
*/
|
|
833
|
+
[data-ui='dialog'] > [data-part='viewport'] > [data-part='content'] > [data-part='footer'] {
|
|
834
|
+
display: flex;
|
|
835
|
+
flex-wrap: wrap;
|
|
836
|
+
align-items: center;
|
|
837
|
+
justify-content: flex-end;
|
|
838
|
+
gap: var(--_gap);
|
|
839
|
+
padding-block: var(--_footer-padding-block);
|
|
840
|
+
padding-inline: var(--_padding-inline);
|
|
841
|
+
background: var(--slotted-dialog-footer-surface, var(--slotted-surface-sunken, Canvas));
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
[data-ui='dialog']
|
|
845
|
+
> [data-part='viewport']
|
|
846
|
+
> [data-part='content']
|
|
847
|
+
> [data-part='footer']:not(:first-child) {
|
|
848
|
+
border-block-start: var(--_rule-size) solid
|
|
849
|
+
var(--slotted-dialog-rule-color, var(--slotted-border-subtle, ButtonBorder));
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/* ── A panel the consumer paints ──────────────────────────────────────── */
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* The layer without the picture.
|
|
856
|
+
*
|
|
857
|
+
* `render` substitutes the element the panel is drawn on and not the drawing,
|
|
858
|
+
* so a consumer who wanted the mechanism and their own box — a media viewer,
|
|
859
|
+
* an image at its own aspect ratio, a `card` — had no way to say so. Here the
|
|
860
|
+
* family paints nothing: no surface, no border, no radius, no shadow, no rows
|
|
861
|
+
* and no padding.
|
|
862
|
+
*
|
|
863
|
+
* Everything that is not paint stays: the portal, the scrim, `presence`, the
|
|
864
|
+
* focus containment and restoration, the dismissal policy, the single z-index
|
|
865
|
+
* stack and the motion. It is the one place this library meets a headless one
|
|
866
|
+
* — Radix and Base UI ship no panel at all, so this is what their Content
|
|
867
|
+
* already is — and it is an axis rather than a second family because
|
|
868
|
+
* everything but the paint is the same.
|
|
869
|
+
*
|
|
870
|
+
* The rows go with the paint, so a plain panel does not keep its footer on
|
|
871
|
+
* screen for a long body. That is the cost, and a consumer who wants both
|
|
872
|
+
* writes their own three-row grid, which is the whole of what they are taking
|
|
873
|
+
* over.
|
|
874
|
+
*/
|
|
875
|
+
[data-ui='dialog'][data-surface='plain'] > [data-part='viewport'] > [data-part='content'] {
|
|
876
|
+
display: block;
|
|
877
|
+
background: none;
|
|
878
|
+
border: 0;
|
|
879
|
+
border-radius: 0;
|
|
880
|
+
box-shadow: none;
|
|
881
|
+
overflow: visible;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
[data-ui='dialog'][data-surface='plain']
|
|
885
|
+
> [data-part='viewport']
|
|
886
|
+
> [data-part='content']
|
|
887
|
+
> :is([data-part='header'], [data-part='body'], [data-part='footer']) {
|
|
888
|
+
background: none;
|
|
889
|
+
border: 0;
|
|
890
|
+
padding: 0;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/* ── Full screen ──────────────────────────────────────────────────────── */
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* The panel spends the whole layer: no margins, no radius, no measure.
|
|
897
|
+
*
|
|
898
|
+
* `align-content: stretch` on the viewport is the half of it that is easy to
|
|
899
|
+
* miss. The first capture had the padding gone and the panel still floating
|
|
900
|
+
* in the middle of the layer with the scrim above and below it, because a
|
|
901
|
+
* percentage block size resolves against a grid area that `safe center` had
|
|
902
|
+
* already sized to the content. The track has to stretch before the panel can
|
|
903
|
+
* fill it.
|
|
904
|
+
*
|
|
905
|
+
* A
|
|
906
|
+
* container query rather than a viewport one, because the family answers its
|
|
907
|
+
* own inline size — a dialog inside a consumer-named portal container is as
|
|
908
|
+
* narrow as that container and not as narrow as the window. The breakpoint is
|
|
909
|
+
* a literal because `@container (max-inline-size: var(--x))` is invalid CSS.
|
|
910
|
+
*/
|
|
911
|
+
@container slotted-dialog (max-inline-size: 30rem) {
|
|
912
|
+
[data-ui='dialog'][data-full-screen='narrow'] > [data-part='viewport'] {
|
|
913
|
+
align-content: stretch;
|
|
914
|
+
padding: 0;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
[data-ui='dialog'][data-full-screen='narrow'] > [data-part='viewport'] > [data-part='content'] {
|
|
918
|
+
max-inline-size: none;
|
|
919
|
+
block-size: 100%;
|
|
920
|
+
border-radius: 0;
|
|
921
|
+
border: 0;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* The axis and the reader's own state produce one picture, so they are one
|
|
927
|
+
* rule. `data-full-screen='always'` is what a consumer chose in advance;
|
|
928
|
+
* `data-fullscreen` is what the reader pressed. Nothing here transitions:
|
|
929
|
+
* R7 admits `transform` and `opacity` alone, and the layer's entry already
|
|
930
|
+
* owns both — a panel that animated its own box would animate `inset`,
|
|
931
|
+
* `inline-size` and `block-size`, which is the rule R7 exists to state.
|
|
932
|
+
*/
|
|
933
|
+
[data-ui='dialog'][data-full-screen='always'] > [data-part='viewport'],
|
|
934
|
+
[data-ui='dialog'][data-fullscreen] > [data-part='viewport'] {
|
|
935
|
+
align-content: stretch;
|
|
936
|
+
padding: 0;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
[data-ui='dialog'][data-full-screen='always'] > [data-part='viewport'] > [data-part='content'],
|
|
940
|
+
[data-ui='dialog'][data-fullscreen] > [data-part='viewport'] > [data-part='content'] {
|
|
941
|
+
max-inline-size: none;
|
|
942
|
+
block-size: 100%;
|
|
943
|
+
border-radius: 0;
|
|
944
|
+
/**
|
|
945
|
+
* No border, not only no radius. A panel that covers the layer hides the
|
|
946
|
+
* scrim entirely, so the edge separates the panel from nothing — and the
|
|
947
|
+
* first full-screen capture had a hairline across the very top of the
|
|
948
|
+
* layer, which on a phone is a stray line under the browser chrome.
|
|
949
|
+
*/
|
|
950
|
+
border: 0;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/* ── Forced colours ───────────────────────────────────────────────────── */
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* The theme remaps every token this family reads, so the only rule here is
|
|
957
|
+
* the one the theme cannot reach: `--slotted-scrim` becomes `Canvas`, an
|
|
958
|
+
* opaque ground, and the panel on it needs an edge the shadow no longer
|
|
959
|
+
* draws. The border above already states one; this thickens it to the weight
|
|
960
|
+
* a system border is read at.
|
|
961
|
+
*/
|
|
962
|
+
@media (forced-colors: active) {
|
|
963
|
+
[data-ui='dialog'] > [data-part='viewport'] > [data-part='content'] {
|
|
964
|
+
border-color: CanvasText;
|
|
965
|
+
border-width: var(--slotted-border-width-thick, 2px);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|