@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,1303 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* A panel that comes in from an edge, and either covers the page or stands
|
|
4
|
+
* beside it.
|
|
5
|
+
*
|
|
6
|
+
* Two boxes, and the second one is the whole family:
|
|
7
|
+
*
|
|
8
|
+
* ┌─ root ──────────────────────────────────────────────┐
|
|
9
|
+
* │ ┌─ backdrop ────────────────────────────────────────┐│
|
|
10
|
+
* │ │ ┌─ panel ────────────────────┐││
|
|
11
|
+
* │ │ │ ⁞ header │││
|
|
12
|
+
* │ │ │ ⁞ ─────────────────────── │││
|
|
13
|
+
* │ │ │ ⁞ body (scrolls) │││
|
|
14
|
+
* │ │ │ ⁞ ─────────────────────── │││
|
|
15
|
+
* │ │ │ ⁞ footer │││
|
|
16
|
+
* │ │ └─┴──────────────────────────┘││
|
|
17
|
+
* │ └───────────────────────┬───────────────────────────┘│
|
|
18
|
+
* └─────────────────────────┼────────────────────────────┘
|
|
19
|
+
* resizer
|
|
20
|
+
*
|
|
21
|
+
* `dialog` needs a third box between its layer and its panel because its root
|
|
22
|
+
* declares `container-type: inline-size`, which applies `contain: layout`,
|
|
23
|
+
* which makes a scrim positioned inside a scrolling root part of what
|
|
24
|
+
* scrolls. This root declares no container and never scrolls: the panel is
|
|
25
|
+
* pinned to an edge and bounded to the layer by construction, so `backdrop`
|
|
26
|
+
* and `panel` are two cells of one one-cell grid and nothing is positioned.
|
|
27
|
+
*
|
|
28
|
+
* The resizer is a four-pixel gutter whose pointer reach is
|
|
29
|
+
* `--slotted-target-min`, the WCAG 2.2 floor, drawn by a transparent
|
|
30
|
+
* pseudo-element centred on it; it is a row of the panel's grid on a block
|
|
31
|
+
* side and an overlay on its inner edge on an inline side. The section that draws it says why the two
|
|
32
|
+
* differ; in one sentence, every rule in this panel runs across the inline
|
|
33
|
+
* axis, and a column would cut all of them.
|
|
34
|
+
*
|
|
35
|
+
* Every part is reached with a child combinator. A drawer opened from inside
|
|
36
|
+
* a drawer is the ordinary case, and a descendant rule would paint the inner
|
|
37
|
+
* panel with the outer panel's geometry.
|
|
38
|
+
*/
|
|
39
|
+
[data-ui='drawer'] {
|
|
40
|
+
/**
|
|
41
|
+
* The panel's resting measure, per size step. Five named steps rather than
|
|
42
|
+
* the pixel string Ant and MUI both take: a theme retunes a step and cannot
|
|
43
|
+
* retune `width: 400px`, and a family that accepted one would be carrying a
|
|
44
|
+
* second scale of its own.
|
|
45
|
+
*/
|
|
46
|
+
--_step: var(--slotted-drawer-extent-md, 24rem);
|
|
47
|
+
/**
|
|
48
|
+
* What the panel actually measures. `--slotted-drawer-extent` is the one
|
|
49
|
+
* name for it, and it is read from two places: an application setting one
|
|
50
|
+
* measure for every drawer writes it on `[data-ui='drawer']`, and the
|
|
51
|
+
* binding writes the length the reader dragged to as an inline custom
|
|
52
|
+
* property on this element. Both are lengths, so the cascade decides
|
|
53
|
+
* between them the way it decides everything else.
|
|
54
|
+
*/
|
|
55
|
+
--_extent: var(--slotted-drawer-extent, var(--_step));
|
|
56
|
+
/** The panel's own inset, which every row reads. */
|
|
57
|
+
--_padding-inline: var(--slotted-space-7, 1.25rem);
|
|
58
|
+
--_padding-block: var(--slotted-space-6, 1rem);
|
|
59
|
+
--_gap: var(--slotted-space-5, 0.75rem);
|
|
60
|
+
--_rule-size: var(--slotted-border-width-thin, 1px);
|
|
61
|
+
/**
|
|
62
|
+
* The title is on the type scale rather than on the control type scale: a
|
|
63
|
+
* control's label is 12 to 14 pixels because it lives inside a button, and
|
|
64
|
+
* a drawer's title is a heading set like every other heading in the library.
|
|
65
|
+
*/
|
|
66
|
+
--_title-font-size: var(--slotted-font-size-lg, 1rem);
|
|
67
|
+
--_description-font-size: var(--slotted-font-size-md, 0.875rem);
|
|
68
|
+
--_body-font-size: var(--slotted-font-size-md, 0.875rem);
|
|
69
|
+
--_icon-size: var(--slotted-control-icon-size-md, 16px);
|
|
70
|
+
--_inner-size: var(--slotted-control-inner-size-md, 24px);
|
|
71
|
+
/**
|
|
72
|
+
* The close control's box: an inner control, not a button (R2), so the
|
|
73
|
+
* glyph plus a step of air on each side, floored at the WCAG 2.2 minimum
|
|
74
|
+
* so the target holds at every density.
|
|
75
|
+
*/
|
|
76
|
+
--_close-size: max(
|
|
77
|
+
var(--slotted-target-min, 24px),
|
|
78
|
+
min(calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
|
|
79
|
+
);
|
|
80
|
+
/**
|
|
81
|
+
* The separator is a gutter, not a handle with a grip on it: four pixels
|
|
82
|
+
* of nothing at rest, a two-pixel line under the hand, and a reach the
|
|
83
|
+
* pointer finds without the eye being told where it is.
|
|
84
|
+
*
|
|
85
|
+
* `--_resizer-reach` is the WCAG 2.2 floor and it is centred on the
|
|
86
|
+
* gutter, so it takes ten pixels from each side rather than twenty from
|
|
87
|
+
* the panel — which is what keeps it off the header of a sheet.
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* One rem, and not the four pixels it was.
|
|
91
|
+
*
|
|
92
|
+
* Four was right for a gutter that drew nothing: the picture was the edge,
|
|
93
|
+
* and the target came from the reach alone. It is wrong for a gutter that
|
|
94
|
+
* carries a grip — the grip does not fit — and it was wrong for the focus
|
|
95
|
+
* ring too: a two-pixel ring inset into a four-pixel box leaves nothing
|
|
96
|
+
* between its own edges, so a focused separator read as a solid accent
|
|
97
|
+
* stripe rather than as a ring around something. The capture is what
|
|
98
|
+
* showed it.
|
|
99
|
+
*
|
|
100
|
+
* It is `splitter`'s track, because the two families draw one picture and a
|
|
101
|
+
* picture is a geometry as much as a colour. The panel's rows already read
|
|
102
|
+
* this through `max(--_padding-inline, --_resizer-thickness)`, so widening
|
|
103
|
+
* it moves nothing else.
|
|
104
|
+
*/
|
|
105
|
+
--_resizer-thickness: var(--slotted-space-6, 1rem);
|
|
106
|
+
--_resizer-reach: var(--slotted-target-min, 24px);
|
|
107
|
+
--_resizer-line: var(--slotted-border-width-thick, 2px);
|
|
108
|
+
--_radius: var(--slotted-radius-lg, 8px);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Two timings, because two distances.
|
|
112
|
+
*
|
|
113
|
+
* `--_slide` is the panel crossing the whole layer from outside it, which
|
|
114
|
+
* is the longest travel any family in the library animates — `dialog`'s
|
|
115
|
+
* panel moves eight pixels and this one moves the width of itself, so the
|
|
116
|
+
* duration it borrowed from that family read as a snap. It takes the top of
|
|
117
|
+
* the motion scale arriving and one step down leaving, because a reader who
|
|
118
|
+
* has decided to close is waiting.
|
|
119
|
+
*
|
|
120
|
+
* `--_move` is any measure that changes — an inline drawer opening, and a
|
|
121
|
+
* separator released onto a snap point. One step below the slide.
|
|
122
|
+
*
|
|
123
|
+
* It used to carry the scrim as well, on the argument that the scrim should
|
|
124
|
+
* have settled by the time the panel lands. It was reported as a scrim that
|
|
125
|
+
* simply appeared, and the correction is `dialog`'s, which had the same
|
|
126
|
+
* complaint and answered it with measurement: on the entrance curve the ink
|
|
127
|
+
* was at 57.6 per cent by the second frame and 83 by the fourth, while the
|
|
128
|
+
* standard curve over the long step spends the frames where they can be
|
|
129
|
+
* seen — 10.8 per cent at 25 milliseconds and 55 at 100. The scrim's own
|
|
130
|
+
* pair is on the backdrop rules below, read from that family rather than
|
|
131
|
+
* invented again here.
|
|
132
|
+
*
|
|
133
|
+
* Both are custom properties rather than pairs of rules because of the
|
|
134
|
+
* cascade: the side rules that set the exit offset carry the same
|
|
135
|
+
* specificity as a `[data-open]` rule would, and the last one written would
|
|
136
|
+
* decide. A property is resolved on the element instead, and the three
|
|
137
|
+
* states below read as what they are.
|
|
138
|
+
*/
|
|
139
|
+
--_slide: var(--slotted-duration-long) var(--slotted-easing-enter);
|
|
140
|
+
--_move: var(--slotted-duration-medium) var(--slotted-easing-enter);
|
|
141
|
+
/**
|
|
142
|
+
* `allow-discrete` is the whole of it. Without it the rule that hides a
|
|
143
|
+
* closed drawer takes effect at the first frame of the exit and the
|
|
144
|
+
* milliseconds `presence` is holding the node mounted for show nothing;
|
|
145
|
+
* with it, the flip to `hidden` happens at the end of the duration instead.
|
|
146
|
+
* `mount="active"` never reaches the closed state — the layer is gone — so
|
|
147
|
+
* this is what `eager`, `lazy` and every inline drawer need in order to
|
|
148
|
+
* animate at all.
|
|
149
|
+
*/
|
|
150
|
+
/**
|
|
151
|
+
* The same step the exit takes, because this only has to outlast the
|
|
152
|
+
* movement and `presence` reads the longest transition on this element to
|
|
153
|
+
* know how long to hold the node. A step longer left the panel sitting off
|
|
154
|
+
* screen, inert and invisible, for a hundred and fifty milliseconds after
|
|
155
|
+
* it had finished leaving — which is a hundred and fifty milliseconds
|
|
156
|
+
* before `closed` fires.
|
|
157
|
+
*/
|
|
158
|
+
--_reveal: content-visibility var(--slotted-duration-medium) allow-discrete;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The repository has no global reset, so a family that gives a box an
|
|
162
|
+
* explicit measure states this itself. Without it the panel's own edge is
|
|
163
|
+
* a pixel *outside* the extent a consumer asked for — and under `inline`
|
|
164
|
+
* the root clips at that extent, so the edge that separates the drawer
|
|
165
|
+
* from the page beside it disappeared entirely in the first capture.
|
|
166
|
+
*/
|
|
167
|
+
box-sizing: border-box;
|
|
168
|
+
display: grid;
|
|
169
|
+
grid-template: minmax(0, 1fr) / minmax(0, 1fr);
|
|
170
|
+
|
|
171
|
+
color: var(--slotted-drawer-color, var(--slotted-text, CanvasText));
|
|
172
|
+
font-family: var(--slotted-control-font-family, var(--slotted-font-family-sans));
|
|
173
|
+
|
|
174
|
+
content-visibility: visible;
|
|
175
|
+
transition: var(--_reveal);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Leaving is shorter than arriving: a reader who has decided is waiting. */
|
|
179
|
+
[data-ui='drawer']:not([data-open]) {
|
|
180
|
+
--_slide: var(--slotted-duration-medium) var(--slotted-easing-exit);
|
|
181
|
+
--_move: var(--slotted-duration-medium) var(--slotted-easing-exit);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* While the pointer is down nothing eases. An extent that transitioned toward
|
|
186
|
+
* every pointer position would lag the hand by the whole duration, and a
|
|
187
|
+
* bottom sheet the reader is pulling has to follow it.
|
|
188
|
+
*/
|
|
189
|
+
[data-ui='drawer'][data-dragging] {
|
|
190
|
+
--_move: var(--slotted-duration-instant) var(--slotted-easing-standard);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
[data-ui='drawer'][data-size='sm'] {
|
|
194
|
+
--_step: var(--slotted-drawer-extent-sm, 18rem);
|
|
195
|
+
--_padding-inline: var(--slotted-space-6, 1rem);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
[data-ui='drawer'][data-size='lg'] {
|
|
199
|
+
--_step: var(--slotted-drawer-extent-lg, 30rem);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
[data-ui='drawer'][data-size='xl'] {
|
|
203
|
+
--_step: var(--slotted-drawer-extent-xl, 40rem);
|
|
204
|
+
--_padding-inline: var(--slotted-space-8, 1.5rem);
|
|
205
|
+
--_title-font-size: var(--slotted-font-size-xl, 1.125rem);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The step a drawer that is a page takes: the whole layer, and no measure. */
|
|
209
|
+
[data-ui='drawer'][data-size='full'] {
|
|
210
|
+
--_step: 100%;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* ── The layer, and the box that is not one ───────────────────────────── */
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The one z-index in the family, and it is a sum. `dismissable-layer` writes
|
|
217
|
+
* `--slotted-layer-depth` on this element as it opens, so a drawer opened
|
|
218
|
+
* over a dialog is one step above it on the single stack the library keeps
|
|
219
|
+
* rather than a number this stylesheet guessed.
|
|
220
|
+
*/
|
|
221
|
+
[data-ui='drawer'][data-mode='overlay'] {
|
|
222
|
+
position: fixed;
|
|
223
|
+
inset: 0;
|
|
224
|
+
z-index: calc(var(--slotted-z-index-overlay, 1000) + var(--slotted-layer-depth, 0));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Under `inline` there is no layer: the root is a box in the consumer's own
|
|
229
|
+
* layout and its own measure is what opens and closes. `overflow: clip` is
|
|
230
|
+
* what lets the panel keep its measure while the box around it collapses — a
|
|
231
|
+
* panel that reflowed as it closed would rewrap its text on the way out.
|
|
232
|
+
*
|
|
233
|
+
* The sibling content reflows because the consumer's grid says so, which is
|
|
234
|
+
* what replaces Angular Material's `push` and MUI's `persistent`. Neither is
|
|
235
|
+
* a mode here, because a family writing a margin on an element it does not
|
|
236
|
+
* own is a family deciding someone else's layout.
|
|
237
|
+
*/
|
|
238
|
+
[data-ui='drawer'][data-mode='inline'] {
|
|
239
|
+
overflow: clip;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
[data-ui='drawer'][data-mode='inline'][data-side='start'],
|
|
243
|
+
[data-ui='drawer'][data-mode='inline'][data-side='end'] {
|
|
244
|
+
inline-size: 0;
|
|
245
|
+
transition:
|
|
246
|
+
inline-size var(--_move),
|
|
247
|
+
var(--_reveal);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
[data-ui='drawer'][data-mode='inline'][data-side='top'],
|
|
251
|
+
[data-ui='drawer'][data-mode='inline'][data-side='bottom'] {
|
|
252
|
+
block-size: 0;
|
|
253
|
+
transition:
|
|
254
|
+
block-size var(--_move),
|
|
255
|
+
var(--_reveal);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The step is what the drawer asks for; the column is what it gets. An
|
|
260
|
+
* `sm` drawer is eighteen rems, and in a two-hundred-and-forty-pixel layout
|
|
261
|
+
* that is the whole of the room and thirty-two pixels more — the content
|
|
262
|
+
* beside it was left with a column narrower than one of its own words.
|
|
263
|
+
* `max-inline-size` is the same clamp the overlay panel already carries as
|
|
264
|
+
* `min(…, 100%)`; it resolves wherever the consumer's column has a size to
|
|
265
|
+
* resolve against, and is inert where the column is sized by the drawer
|
|
266
|
+
* itself.
|
|
267
|
+
*
|
|
268
|
+
* It is written on `[data-open]` alone. Closing, the root's own measure runs
|
|
269
|
+
* to nought, and a cap against a box on its way to nought is what would
|
|
270
|
+
* rewrap the panel on the way out.
|
|
271
|
+
*/
|
|
272
|
+
[data-ui='drawer'][data-mode='inline'][data-open][data-side='start'],
|
|
273
|
+
[data-ui='drawer'][data-mode='inline'][data-open][data-side='end'] {
|
|
274
|
+
inline-size: var(--_extent);
|
|
275
|
+
max-inline-size: 100%;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
[data-ui='drawer'][data-mode='inline'][data-open][data-side='top'],
|
|
279
|
+
[data-ui='drawer'][data-mode='inline'][data-open][data-side='bottom'] {
|
|
280
|
+
block-size: var(--_extent);
|
|
281
|
+
max-block-size: 100%;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Mounted and closed. `eager` and `lazy` leave the layer in the document and
|
|
286
|
+
* an inline drawer never leaves it at all, so a panel the eye reads as closed
|
|
287
|
+
* would otherwise keep its controls in the tab sequence — the defect MUI's
|
|
288
|
+
* `persistent` variant ships. The rule keys on `inert`, which the binding
|
|
289
|
+
* writes while the panel is closed or exiting, and not on the absence of
|
|
290
|
+
* `data-open`: the entering frame has no `data-open` either, and a hidden
|
|
291
|
+
* panel in that frame refuses the focus scope's initial focus.
|
|
292
|
+
*/
|
|
293
|
+
[data-ui='drawer'][inert] {
|
|
294
|
+
content-visibility: hidden;
|
|
295
|
+
pointer-events: none;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* ── The scrim ────────────────────────────────────────────────────────── */
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* `--slotted-scrim` is a foundation token the theme restates per scheme and
|
|
302
|
+
* remaps to `Canvas` under forced colours, where it becomes an opaque ground.
|
|
303
|
+
* That is why the panel below carries an edge of its own: every shadow token
|
|
304
|
+
* is `none` in that mode, and a panel separated only by elevation would sit on
|
|
305
|
+
* an opaque ground with nothing marking where it starts.
|
|
306
|
+
*/
|
|
307
|
+
[data-ui='drawer'] > [data-part='backdrop'] {
|
|
308
|
+
grid-area: 1 / 1;
|
|
309
|
+
background: var(--slotted-drawer-scrim, var(--slotted-scrim, Canvas));
|
|
310
|
+
opacity: 0;
|
|
311
|
+
transition: opacity var(--slotted-duration-short) var(--slotted-easing-exit);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The arrival is the long step on the standard curve, and it is `dialog`'s
|
|
316
|
+
* pair rather than a second answer to the same question — the two halves of
|
|
317
|
+
* one layer arrive together, and `packages/styles/src/scrim-motion.test.mjs`
|
|
318
|
+
* reads one family's declaration and holds the other to it.
|
|
319
|
+
*
|
|
320
|
+
* Leaving is the short step on the exit curve: a reader who has decided to
|
|
321
|
+
* close is waiting, and the page behind is back before the panel has finished
|
|
322
|
+
* going.
|
|
323
|
+
*/
|
|
324
|
+
[data-ui='drawer'][data-open] > [data-part='backdrop'] {
|
|
325
|
+
opacity: 1;
|
|
326
|
+
transition: opacity var(--slotted-duration-long) var(--slotted-easing-standard);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* ── The panel ────────────────────────────────────────────────────────── */
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Three rows and, when a resizer is rendered, one track beside them. The
|
|
333
|
+
* middle row is bounded, which is what keeps the actions on screen when the
|
|
334
|
+
* body is long — the failure Vaul, MUI and Angular Material all leave to each
|
|
335
|
+
* consumer's own CSS, because none of the three ships a layout.
|
|
336
|
+
*/
|
|
337
|
+
[data-ui='drawer'] > [data-part='panel'] {
|
|
338
|
+
grid-area: 1 / 1;
|
|
339
|
+
box-sizing: border-box;
|
|
340
|
+
/**
|
|
341
|
+
* The measure eases, which is the whole of what a snap point feels like: a
|
|
342
|
+
* release with no transition puts the panel on its stop between two frames
|
|
343
|
+
* and reads as a jump. `--_move` is `instant` while the pointer is down, so
|
|
344
|
+
* the panel still follows the hand, and returns to the enter step the
|
|
345
|
+
* moment the hand leaves.
|
|
346
|
+
*/
|
|
347
|
+
transition:
|
|
348
|
+
inline-size var(--_move),
|
|
349
|
+
block-size var(--_move);
|
|
350
|
+
/* The containing block for the separator, on the two sides it lies over. */
|
|
351
|
+
position: relative;
|
|
352
|
+
display: grid;
|
|
353
|
+
grid-template-columns: minmax(0, 1fr);
|
|
354
|
+
min-inline-size: 0;
|
|
355
|
+
min-block-size: 0;
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The inset each row keeps on each edge. A side widens the one its
|
|
359
|
+
* separator lies over, so the words never start under the reader's hand,
|
|
360
|
+
* and `max()` means a size step whose inset is already larger keeps it.
|
|
361
|
+
*/
|
|
362
|
+
--_row-padding-inline-start: var(--_padding-inline);
|
|
363
|
+
--_row-padding-inline-end: var(--_padding-inline);
|
|
364
|
+
|
|
365
|
+
background: var(--slotted-drawer-surface, var(--slotted-surface-raised, Canvas));
|
|
366
|
+
/**
|
|
367
|
+
* `--slotted-border-subtle` and not `--slotted-border-strong`.
|
|
368
|
+
*
|
|
369
|
+
* The strong step is the line between two surfaces and the foundation holds
|
|
370
|
+
* it to 3:1 against each of them. Read as one, the panel's edge measured
|
|
371
|
+
* 3.51 against the panel in the dark scheme — a light grey stripe down a
|
|
372
|
+
* near-black surface, brighter than anything else the drawer paints, and
|
|
373
|
+
* the first thing the eye lands on when the panel arrives (DR4).
|
|
374
|
+
*
|
|
375
|
+
* The edge is not that line. What separates the panel from the page is the
|
|
376
|
+
* scrim in front of the page and the elevation the panel is lifted by; the
|
|
377
|
+
* edge is a detail inside the panel's own surface, which is the subtle step
|
|
378
|
+
* — 1.14 against the panel in `dark`, 1.24 in `light`, and the same black
|
|
379
|
+
* as every other line in `high-contrast`, where the query below thickens it
|
|
380
|
+
* to a system border anyway.
|
|
381
|
+
*
|
|
382
|
+
* The boundary that is left is measured rather than assumed: the panel
|
|
383
|
+
* stands 2.48 against the scrim in `dark` and 3.32 in `light`, which is the
|
|
384
|
+
* separation between two surfaces the foundation asks the strong step to
|
|
385
|
+
* carry — and here it is carried by the surfaces themselves. The elevation
|
|
386
|
+
* adds nothing in `dark`, where every shadow token is black on black over a
|
|
387
|
+
* scrim that is already black; it is the scrim, not the shadow, that darkens
|
|
388
|
+
* what lies behind. A theme that wants a heavier edge states
|
|
389
|
+
* `--slotted-drawer-border-color`.
|
|
390
|
+
*/
|
|
391
|
+
border-color: var(--slotted-drawer-border-color, var(--slotted-border-subtle, ButtonBorder));
|
|
392
|
+
border-style: solid;
|
|
393
|
+
border-width: 0;
|
|
394
|
+
/* So the leading corners clip the row that scrolls under them. */
|
|
395
|
+
overflow: hidden;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* The exit, and the only physical value in the family.
|
|
400
|
+
*
|
|
401
|
+
* Grid alignment is flow-relative, so the panel lands on the right edge in
|
|
402
|
+
* both directions with no rule of its own. `translate` is not: it takes a
|
|
403
|
+
* physical axis, and a start-side panel leaves toward the left of a
|
|
404
|
+
* left-to-right document and toward the right of a mirrored one.
|
|
405
|
+
*/
|
|
406
|
+
[data-ui='drawer'][data-mode='overlay'] > [data-part='panel'] {
|
|
407
|
+
box-shadow: var(--slotted-shadow-lg);
|
|
408
|
+
translate: 0 0;
|
|
409
|
+
/* The measure is restated here, or this rule would replace the base one:
|
|
410
|
+
`transition` is one property and the more specific declaration wins it
|
|
411
|
+
whole. */
|
|
412
|
+
transition:
|
|
413
|
+
translate var(--_slide),
|
|
414
|
+
inline-size var(--_move),
|
|
415
|
+
block-size var(--_move);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
[data-ui='drawer'][data-mode='overlay']:not([data-open]) > [data-part='panel'] {
|
|
419
|
+
translate: var(--_shift-x, 0) var(--_shift-y, 0);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* While the pointer is down the measure is not eased at all — and *not
|
|
424
|
+
* eased* rather than eased for nought milliseconds, which is still a
|
|
425
|
+
* transition the engine starts and still an animation `getAnimations()`
|
|
426
|
+
* reports. The value under the hand is the reader's own: it is written once
|
|
427
|
+
* per `pointermove` and it animates nothing.
|
|
428
|
+
*
|
|
429
|
+
* R7 forbids a transition on a layout property, and this obeys it rather
|
|
430
|
+
* than excepting itself from it. What R7 governs is an *entrance* — a box
|
|
431
|
+
* that arrives by growing costs a layout on every frame of an animation
|
|
432
|
+
* nobody asked for, and `transform` and `opacity` say the same thing for
|
|
433
|
+
* free. A drag is the other case: the width is the value the reader is
|
|
434
|
+
* holding, there is no keyframe between two of their positions, and a
|
|
435
|
+
* transition here would put the panel behind the hand by the whole duration.
|
|
436
|
+
* The slide stays, because a panel released mid-gesture still has an exit.
|
|
437
|
+
*/
|
|
438
|
+
[data-ui='drawer'][data-dragging] > [data-part='panel'] {
|
|
439
|
+
transition: none;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
[data-ui='drawer'][data-dragging][data-mode='overlay'] > [data-part='panel'] {
|
|
443
|
+
transition: translate var(--_slide);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
[data-ui='drawer'][data-dragging][data-mode='inline'][data-side='start'],
|
|
447
|
+
[data-ui='drawer'][data-dragging][data-mode='inline'][data-side='end'],
|
|
448
|
+
[data-ui='drawer'][data-dragging][data-mode='inline'][data-side='top'],
|
|
449
|
+
[data-ui='drawer'][data-dragging][data-mode='inline'][data-side='bottom'] {
|
|
450
|
+
transition: var(--_reveal);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Reduced motion drops the slide and keeps the scrim's fade: the panel is
|
|
455
|
+
* simply there, behind a scrim that still says something opened. The
|
|
456
|
+
* closed panel sits at rest because the layer hides it whole, above.
|
|
457
|
+
*/
|
|
458
|
+
@media (prefers-reduced-motion: reduce) {
|
|
459
|
+
[data-ui='drawer'][data-mode='overlay'] > [data-part='panel'] {
|
|
460
|
+
translate: 0 0;
|
|
461
|
+
transition:
|
|
462
|
+
inline-size var(--_move),
|
|
463
|
+
block-size var(--_move);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/* ── Where the panel sits, and where it goes when it leaves ───────────── */
|
|
468
|
+
|
|
469
|
+
[data-ui='drawer'][data-side='start'] > [data-part='panel'],
|
|
470
|
+
[data-ui='drawer'][data-side='end'] > [data-part='panel'] {
|
|
471
|
+
align-self: stretch;
|
|
472
|
+
inline-size: min(var(--_extent), 100%);
|
|
473
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
[data-ui='drawer'][data-side='top'] > [data-part='panel'],
|
|
477
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] {
|
|
478
|
+
justify-self: stretch;
|
|
479
|
+
block-size: min(var(--_extent), 100%);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
[data-ui='drawer'][data-side='start'] > [data-part='panel'] {
|
|
483
|
+
justify-self: start;
|
|
484
|
+
--_shift-x: -100%;
|
|
485
|
+
/* The resizer lies over this edge, so the rows keep clear of it. */
|
|
486
|
+
--_row-padding-inline-end: max(var(--_padding-inline), var(--_resizer-thickness));
|
|
487
|
+
border-inline-end-width: var(--_rule-size);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
[data-ui='drawer'][data-side='end'] > [data-part='panel'] {
|
|
491
|
+
justify-self: end;
|
|
492
|
+
--_shift-x: 100%;
|
|
493
|
+
--_row-padding-inline-start: max(var(--_padding-inline), var(--_resizer-thickness));
|
|
494
|
+
border-inline-start-width: var(--_rule-size);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* A sheet keeps its leading corners and a side drawer keeps none. An
|
|
499
|
+
* edge-anchored panel that spans the whole block extent reads as a wall rather
|
|
500
|
+
* than as a card, which is what Fluent, Ant, MUI and Angular Material all
|
|
501
|
+
* draw; a sheet coming up from the bottom is the one case where the two
|
|
502
|
+
* corners the reader can see are what say it is a sheet.
|
|
503
|
+
*
|
|
504
|
+
* On a block side the resizer is a row of its own — see the section below for
|
|
505
|
+
* why it is a row here and an overlay on an inline side — so the four rows are
|
|
506
|
+
* placed explicitly rather than left to fall in DOM order. A panel with no
|
|
507
|
+
* resizer would otherwise put its body in an `auto` track and lose the one
|
|
508
|
+
* declaration that keeps the footer on screen.
|
|
509
|
+
*/
|
|
510
|
+
[data-ui='drawer'][data-side='top'] > [data-part='panel'] {
|
|
511
|
+
align-self: start;
|
|
512
|
+
grid-template-rows: auto minmax(0, 1fr) auto auto;
|
|
513
|
+
--_shift-y: -100%;
|
|
514
|
+
border-block-end-width: var(--_rule-size);
|
|
515
|
+
border-end-start-radius: var(--slotted-drawer-radius, var(--_radius));
|
|
516
|
+
border-end-end-radius: var(--slotted-drawer-radius, var(--_radius));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] {
|
|
520
|
+
align-self: end;
|
|
521
|
+
grid-template-rows: auto auto minmax(0, 1fr) auto;
|
|
522
|
+
--_shift-y: 100%;
|
|
523
|
+
border-block-start-width: var(--_rule-size);
|
|
524
|
+
border-start-start-radius: var(--slotted-drawer-radius, var(--_radius));
|
|
525
|
+
border-start-end-radius: var(--slotted-drawer-radius, var(--_radius));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* A sheet's separator takes the first row, so the three that follow move down
|
|
530
|
+
* one. `top` needs no such rule: its separator is the fourth row and the
|
|
531
|
+
* three above it are already where the base puts them.
|
|
532
|
+
*/
|
|
533
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] > [data-part='header'] {
|
|
534
|
+
grid-row: 2;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] > [data-part='body'] {
|
|
538
|
+
grid-row: 3;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] > [data-part='footer'] {
|
|
542
|
+
grid-row: 4;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/* ── The sheet ────────────────────────────────────────────────────────── */
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* A sheet on a wide layer is a card over the page, not a strip across it:
|
|
549
|
+
* forty rems, Material 3's 640 dp, centred, with the scrim on both sides. The
|
|
550
|
+
* edge then shows on three sides, so the panel draws it on three; on a phone
|
|
551
|
+
* the two side edges sit on the screen's own and read as nothing.
|
|
552
|
+
*/
|
|
553
|
+
[data-ui='drawer'][data-mode='overlay'][data-side='top'] > [data-part='panel'],
|
|
554
|
+
[data-ui='drawer'][data-mode='overlay'][data-side='bottom'] > [data-part='panel'] {
|
|
555
|
+
justify-self: center;
|
|
556
|
+
inline-size: min(
|
|
557
|
+
100%,
|
|
558
|
+
var(--slotted-drawer-sheet-inline-size, var(--slotted-drawer-extent-xl, 40rem))
|
|
559
|
+
);
|
|
560
|
+
border-inline-width: var(--_rule-size);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* `size="content"`: the panel measures what it holds, which is what every
|
|
565
|
+
* native sheet does and no named step can. `auto` on a block side and
|
|
566
|
+
* `max-content` on an inline one, capped four rems short of the layer so a
|
|
567
|
+
* strip of scrim is always left for the press outside.
|
|
568
|
+
*
|
|
569
|
+
* The cap reads `--slotted-drawer-extent` first. Once the reader has moved the
|
|
570
|
+
* sheet the binding writes that length, and a sheet dragged to the whole layer
|
|
571
|
+
* is the whole layer; unset, the cap is the strip.
|
|
572
|
+
*
|
|
573
|
+
* `interpolate-size` is what lets a height of `auto` transition — content that
|
|
574
|
+
* arrives while the sheet is open, and a release back onto the content stop. A
|
|
575
|
+
* browser without it jumps, and nothing else changes.
|
|
576
|
+
*/
|
|
577
|
+
[data-ui='drawer'][data-size='content'] {
|
|
578
|
+
--_step: auto;
|
|
579
|
+
--_content-cap: min(
|
|
580
|
+
var(--slotted-drawer-extent, calc(100% - var(--slotted-space-12, 4rem))),
|
|
581
|
+
100%
|
|
582
|
+
);
|
|
583
|
+
interpolate-size: allow-keywords;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
[data-ui='drawer'][data-size='content'][data-side='start'],
|
|
587
|
+
[data-ui='drawer'][data-size='content'][data-side='end'] {
|
|
588
|
+
--_step: max-content;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
[data-ui='drawer'][data-size='content'][data-side='start'] > [data-part='panel'],
|
|
592
|
+
[data-ui='drawer'][data-size='content'][data-side='end'] > [data-part='panel'] {
|
|
593
|
+
inline-size: var(--_extent);
|
|
594
|
+
max-inline-size: var(--_content-cap);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
[data-ui='drawer'][data-size='content'][data-side='top'] > [data-part='panel'],
|
|
598
|
+
[data-ui='drawer'][data-size='content'][data-side='bottom'] > [data-part='panel'] {
|
|
599
|
+
block-size: var(--_extent);
|
|
600
|
+
max-block-size: var(--_content-cap);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* The virtual keyboard. `observeKeyboardInset` in `@slotted/core/measure` measures what the
|
|
605
|
+
* keyboard covers and the binding writes it here while the layer is open; the
|
|
606
|
+
* cell the panel sits in shrinks by it, so a sheet lands above the keys and a
|
|
607
|
+
* side panel's footer does too, and every `100%` cap shrinks with the cell.
|
|
608
|
+
*/
|
|
609
|
+
[data-ui='drawer'][data-mode='overlay'] {
|
|
610
|
+
padding-block-end: var(--slotted-keyboard-inset, 0px);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* The scrim a sheet leaves off below the stop the consumer named
|
|
615
|
+
* (`scrimFrom`): the map behind a sheet at its lowest stop is the page, and a
|
|
616
|
+
* press on it reaches it. The opacity keeps the transition the scrim already
|
|
617
|
+
* has, so crossing the stop fades rather than blinks.
|
|
618
|
+
*/
|
|
619
|
+
[data-ui='drawer'][data-undimmed] > [data-part='backdrop'] {
|
|
620
|
+
opacity: 0;
|
|
621
|
+
pointer-events: none;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/* The layer is fixed over the whole viewport, so the scrim alone letting the
|
|
625
|
+
press through leaves the root to catch it; the panel takes it back. */
|
|
626
|
+
[data-ui='drawer'][data-undimmed] {
|
|
627
|
+
pointer-events: none;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
[data-ui='drawer'][data-undimmed] > [data-part='panel'] {
|
|
631
|
+
pointer-events: auto;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* A sheet with no footer: the body is the last row and the home indicator
|
|
636
|
+
* sits over its end. The inset is padding inside the scrollport, so the last
|
|
637
|
+
* line scrolls clear of the indicator rather than stopping under it.
|
|
638
|
+
*/
|
|
639
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] > [data-part='body']:last-child {
|
|
640
|
+
padding-block-end: calc(var(--_padding-block) + env(safe-area-inset-bottom, 0px));
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** `start` is the left edge in one direction and the right edge in the other. */
|
|
644
|
+
[data-ui='drawer'][data-side='start']:dir(rtl) > [data-part='panel'] {
|
|
645
|
+
--_shift-x: 100%;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
[data-ui='drawer'][data-side='end']:dir(rtl) > [data-part='panel'] {
|
|
649
|
+
--_shift-x: -100%;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* `full` is the panel covering the layer, so its inner edge separates it from
|
|
654
|
+
* nothing and the hairline it would draw sits on the edge of the screen —
|
|
655
|
+
* which on a phone reads as a stray line under the browser chrome. `dialog`
|
|
656
|
+
* removed the same line from its own full-screen step for the same reason.
|
|
657
|
+
*
|
|
658
|
+
* A step the layer is too narrow to honour is clamped by `min(…, 100%)` and
|
|
659
|
+
* still draws its edge. CSS cannot compare a resolved length to its
|
|
660
|
+
* container, and the four container queries that would answer it — one per
|
|
661
|
+
* step, because a container condition cannot hold a custom property — buy one
|
|
662
|
+
* hairline at the extreme edge of the viewport.
|
|
663
|
+
*/
|
|
664
|
+
[data-ui='drawer'][data-size='full'] > [data-part='panel'] {
|
|
665
|
+
border-width: 0;
|
|
666
|
+
border-radius: 0;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Under `inline` the panel keeps the measure the root is collapsing away
|
|
671
|
+
* from, which is what `overflow: clip` on the root is for. `min(…, 100%)`
|
|
672
|
+
* would resolve against a box that is on its way to nought.
|
|
673
|
+
*/
|
|
674
|
+
[data-ui='drawer'][data-mode='inline'][data-side='start'] > [data-part='panel'],
|
|
675
|
+
[data-ui='drawer'][data-mode='inline'][data-side='end'] > [data-part='panel'] {
|
|
676
|
+
inline-size: var(--_extent);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
[data-ui='drawer'][data-mode='inline'][data-side='top'] > [data-part='panel'],
|
|
680
|
+
[data-ui='drawer'][data-mode='inline'][data-side='bottom'] > [data-part='panel'] {
|
|
681
|
+
block-size: var(--_extent);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Open, the root is a settled measure and the panel takes it rather than the
|
|
686
|
+
* step it asked for: a root the column clamped would otherwise clip a panel
|
|
687
|
+
* that is still drawing itself at the full step, and a panel cut in half is
|
|
688
|
+
* a worse picture than one that wrapped. The cap is on `[data-open]` for the
|
|
689
|
+
* reason the root's is.
|
|
690
|
+
*/
|
|
691
|
+
[data-ui='drawer'][data-mode='inline'][data-open][data-side='start'] > [data-part='panel'],
|
|
692
|
+
[data-ui='drawer'][data-mode='inline'][data-open][data-side='end'] > [data-part='panel'] {
|
|
693
|
+
max-inline-size: 100%;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
[data-ui='drawer'][data-mode='inline'][data-open][data-side='top'] > [data-part='panel'],
|
|
697
|
+
[data-ui='drawer'][data-mode='inline'][data-open][data-side='bottom'] > [data-part='panel'] {
|
|
698
|
+
max-block-size: 100%;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* ── The separator the reader moves ───────────────────────────────────── */
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* A focusable `separator`, which is the widget form of the role, from the
|
|
705
|
+
* WAI-ARIA Window Splitter pattern. Ant's resizer and Vaul's snap dragging
|
|
706
|
+
* are both pointer-only; this one is a target a keyboard can reach, which is
|
|
707
|
+
* what `docs/component-features.md` §2.8 asks of every resize.
|
|
708
|
+
*
|
|
709
|
+
* **It draws something at rest, and that is a correction.** It used to be
|
|
710
|
+
* four pixels of nothing until a hand arrived, and then a two-pixel line in
|
|
711
|
+
* the accent border — no grip, no dots, no mark — on the argument that a
|
|
712
|
+
* separator drawing anything at rest is a decoration on an edge nobody is
|
|
713
|
+
* touching. Both halves of that were wrong. A resizable edge nobody can see
|
|
714
|
+
* is a capability nobody finds, and it was reported as exactly that; and the
|
|
715
|
+
* accent is the one colour in the library that means "the keyboard is here",
|
|
716
|
+
* so spending it on hover left nothing to say at focus.
|
|
717
|
+
*
|
|
718
|
+
* The picture is `splitter`'s, which is the family that settled it: two
|
|
719
|
+
* hairlines in the strong border step with a grip between them, running the
|
|
720
|
+
* whole gutter under the hand, and the accent reserved for the focus ring.
|
|
721
|
+
* SAP's own splitter is where both families read it. The two are written
|
|
722
|
+
* twice rather than shared, because `packages/styles/src/identity.test.mjs`
|
|
723
|
+
* forbids one family reaching into another's rules — about thirty lines, and
|
|
724
|
+
* the cost of two families that can move apart.
|
|
725
|
+
*
|
|
726
|
+
* **A sheet is held differently, so it draws differently.** On a block side
|
|
727
|
+
* the lines are not painted at all and the grip becomes a pill above the
|
|
728
|
+
* header, which is where a hand looks for a sheet. The old gutter put the
|
|
729
|
+
* gesture on the bottom edge of the screen, which is nowhere a thumb goes
|
|
730
|
+
* looking.
|
|
731
|
+
*
|
|
732
|
+
* **The pointer reach is not the picture.** `::before` is a transparent box
|
|
733
|
+
* of `--slotted-target-min`, centred on the gutter, so the target holds at
|
|
734
|
+
* the WCAG 2.2 floor while the picture stays thin. Centred rather than run
|
|
735
|
+
* inward: inward it would take twenty pixels of the panel and, on a sheet,
|
|
736
|
+
* sit over the header.
|
|
737
|
+
*
|
|
738
|
+
* **It is a row on a block side and an overlay on an inline side**, and the
|
|
739
|
+
* asymmetry has one cause: every rule in this panel runs across the inline
|
|
740
|
+
* axis. A resizer row above the header or below the footer crosses none of
|
|
741
|
+
* them, while a resizer *column* would cut all of them — the first capture
|
|
742
|
+
* showed exactly that, a rule under the header stopping short of the panel's
|
|
743
|
+
* own edge, which reads as an unfinished line rather than as a track. A line
|
|
744
|
+
* cannot cross an opaque box, so on an inline side the separator lies over
|
|
745
|
+
* the panel instead and the rows keep clear of it with
|
|
746
|
+
* `--_row-padding-inline-*`.
|
|
747
|
+
*
|
|
748
|
+
* The cost is written rather than hidden: on `side="start"` the separator
|
|
749
|
+
* sits at the panel's inline-end edge, which is where a left-to-right
|
|
750
|
+
* document also puts the body's scrollbar. The wheel still scrolls and the
|
|
751
|
+
* scrollbar's inner half is still reachable, and every desktop application
|
|
752
|
+
* with a resizable sidebar has the same overlap.
|
|
753
|
+
*
|
|
754
|
+
* `touch-action: none` so a drag is not read as a scroll on the way past.
|
|
755
|
+
*/
|
|
756
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='resizer'] {
|
|
757
|
+
display: flex;
|
|
758
|
+
align-items: center;
|
|
759
|
+
justify-content: center;
|
|
760
|
+
gap: min(var(--slotted-space-9, 2rem), 12%);
|
|
761
|
+
background: transparent;
|
|
762
|
+
touch-action: none;
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* The two steps, and the whole of the colour decision — the same one
|
|
766
|
+
* `splitter` makes, because the two are one picture in two families.
|
|
767
|
+
*
|
|
768
|
+
* The strong border step at rest, not the subtle one and not the accent.
|
|
769
|
+
* The accent is the one colour in the library that means "the keyboard is
|
|
770
|
+
* here", and this family used to spend it on hover as well: a two-pixel
|
|
771
|
+
* accent line appearing under the hand on an edge that drew nothing at all
|
|
772
|
+
* before it. Both halves of that were wrong. An edge nobody can see is a
|
|
773
|
+
* capability nobody finds, and an accent spent on hover has nothing left to
|
|
774
|
+
* say at focus.
|
|
775
|
+
*/
|
|
776
|
+
--_rest: var(--slotted-drawer-resizer-line-color, var(--slotted-border-strong, ButtonBorder));
|
|
777
|
+
--_active: var(
|
|
778
|
+
--slotted-drawer-resizer-line-active-color,
|
|
779
|
+
var(--slotted-text-muted, ButtonText)
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/* Under the hand the gap closes and the marks read as one. */
|
|
784
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='resizer']:not([data-disabled]):hover,
|
|
785
|
+
[data-ui='drawer']
|
|
786
|
+
> [data-part='panel']
|
|
787
|
+
> [data-part='resizer']:not([data-disabled]):focus-visible,
|
|
788
|
+
[data-ui='drawer'][data-dragging] > [data-part='panel'] > [data-part='resizer'] {
|
|
789
|
+
gap: var(--slotted-space-4, 0.5rem);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
[data-ui='drawer'][data-side='start'] > [data-part='panel'] > [data-part='resizer'],
|
|
793
|
+
[data-ui='drawer'][data-side='end'] > [data-part='panel'] > [data-part='resizer'] {
|
|
794
|
+
flex-direction: column;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* The reach. Transparent, centred on the gutter and above the panel's own
|
|
799
|
+
* rows, which is what makes a four-pixel picture a twenty-four-pixel target.
|
|
800
|
+
*/
|
|
801
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='resizer']::before {
|
|
802
|
+
content: '';
|
|
803
|
+
position: absolute;
|
|
804
|
+
--_reach-overhang: calc((var(--_resizer-reach) - var(--_resizer-thickness)) / -2);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
[data-ui='drawer'][data-side='start'] > [data-part='panel'] > [data-part='resizer']::before,
|
|
808
|
+
[data-ui='drawer'][data-side='end'] > [data-part='panel'] > [data-part='resizer']::before {
|
|
809
|
+
inset-block: 0;
|
|
810
|
+
inset-inline: var(--_reach-overhang);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
[data-ui='drawer'][data-side='top'] > [data-part='panel'] > [data-part='resizer']::before,
|
|
814
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] > [data-part='resizer']::before {
|
|
815
|
+
inset-inline: 0;
|
|
816
|
+
inset-block: var(--_reach-overhang);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
[data-ui='drawer'][data-side='start'] > [data-part='panel'] > [data-part='resizer'],
|
|
820
|
+
[data-ui='drawer'][data-side='end'] > [data-part='panel'] > [data-part='resizer'] {
|
|
821
|
+
position: absolute;
|
|
822
|
+
inset-block: 0;
|
|
823
|
+
inline-size: var(--_resizer-thickness);
|
|
824
|
+
cursor: col-resize;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
[data-ui='drawer'][data-side='start'] > [data-part='panel'] > [data-part='resizer'] {
|
|
828
|
+
inset-inline-end: 0;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
[data-ui='drawer'][data-side='end'] > [data-part='panel'] > [data-part='resizer'] {
|
|
832
|
+
inset-inline-start: 0;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
[data-ui='drawer'][data-side='top'] > [data-part='panel'] > [data-part='resizer'],
|
|
836
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] > [data-part='resizer'] {
|
|
837
|
+
position: relative;
|
|
838
|
+
block-size: var(--_resizer-thickness);
|
|
839
|
+
cursor: row-resize;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] > [data-part='resizer'] {
|
|
843
|
+
grid-row: 1;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
[data-ui='drawer'][data-side='top'] > [data-part='panel'] > [data-part='resizer'] {
|
|
847
|
+
grid-row: 4;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* The lines, on an inline side only. A hairline each side of the grip, fading
|
|
852
|
+
* to transparent away from it, running the whole gutter once a hand is on
|
|
853
|
+
* them. `splitter`'s picture, and its arithmetic too: a resting length of
|
|
854
|
+
* `min(4rem, 20%)` leaves slack at every gutter length, and it is that slack
|
|
855
|
+
* the hand claims.
|
|
856
|
+
*
|
|
857
|
+
* A sheet paints none of them — see the grabber below.
|
|
858
|
+
*/
|
|
859
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='resizer'] > [data-part='line'] {
|
|
860
|
+
flex: 0 1 min(var(--slotted-space-12, 4rem), 20%);
|
|
861
|
+
min-block-size: 0;
|
|
862
|
+
min-inline-size: 0;
|
|
863
|
+
inline-size: var(--slotted-border-width-thin, 1px);
|
|
864
|
+
border-radius: var(--slotted-radius-sm, 2px);
|
|
865
|
+
transition:
|
|
866
|
+
flex-basis var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
867
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
|
|
868
|
+
background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
869
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
[data-ui='drawer'][data-side='start']
|
|
873
|
+
> [data-part='panel']
|
|
874
|
+
> [data-part='resizer']
|
|
875
|
+
> [data-part='line']:first-child,
|
|
876
|
+
[data-ui='drawer'][data-side='end']
|
|
877
|
+
> [data-part='panel']
|
|
878
|
+
> [data-part='resizer']
|
|
879
|
+
> [data-part='line']:first-child {
|
|
880
|
+
background: linear-gradient(to bottom, transparent, var(--_rest));
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
[data-ui='drawer'][data-side='start']
|
|
884
|
+
> [data-part='panel']
|
|
885
|
+
> [data-part='resizer']
|
|
886
|
+
> [data-part='line']:last-child,
|
|
887
|
+
[data-ui='drawer'][data-side='end']
|
|
888
|
+
> [data-part='panel']
|
|
889
|
+
> [data-part='resizer']
|
|
890
|
+
> [data-part='line']:last-child {
|
|
891
|
+
background: linear-gradient(to top, transparent, var(--_rest));
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
[data-ui='drawer']
|
|
895
|
+
> [data-part='panel']
|
|
896
|
+
> [data-part='resizer']:not([data-disabled]):hover
|
|
897
|
+
> [data-part='line'],
|
|
898
|
+
[data-ui='drawer']
|
|
899
|
+
> [data-part='panel']
|
|
900
|
+
> [data-part='resizer']:not([data-disabled]):focus-visible
|
|
901
|
+
> [data-part='line'],
|
|
902
|
+
[data-ui='drawer'][data-dragging]
|
|
903
|
+
> [data-part='panel']
|
|
904
|
+
> [data-part='resizer']
|
|
905
|
+
> [data-part='line'] {
|
|
906
|
+
flex-basis: 100%;
|
|
907
|
+
background: var(--_active);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* The grip, and the two pictures it takes.
|
|
912
|
+
*
|
|
913
|
+
* On an inline side it is `splitter`'s dot matrix: a mark on an edge, small
|
|
914
|
+
* enough to read as a texture rather than as a control.
|
|
915
|
+
*
|
|
916
|
+
* On a block side it is a pill, and the whole separator becomes it — the
|
|
917
|
+
* grabber a hand looks for at the top of a sheet. The lines are not painted
|
|
918
|
+
* there because a sheet is not held at its corners; it is held in the middle,
|
|
919
|
+
* which is where every mobile sheet in the field puts its mark.
|
|
920
|
+
*/
|
|
921
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='resizer'] > [data-part='grip'] {
|
|
922
|
+
flex: none;
|
|
923
|
+
transition:
|
|
924
|
+
background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
925
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
|
|
926
|
+
color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
927
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
[data-ui='drawer'][data-side='start']
|
|
931
|
+
> [data-part='panel']
|
|
932
|
+
> [data-part='resizer']
|
|
933
|
+
> [data-part='grip'],
|
|
934
|
+
[data-ui='drawer'][data-side='end']
|
|
935
|
+
> [data-part='panel']
|
|
936
|
+
> [data-part='resizer']
|
|
937
|
+
> [data-part='grip'] {
|
|
938
|
+
color: var(--_rest);
|
|
939
|
+
background-image: radial-gradient(circle at center, currentColor 40%, transparent 42%);
|
|
940
|
+
background-size: var(--slotted-space-2, 0.25rem) var(--slotted-space-2, 0.25rem);
|
|
941
|
+
inline-size: calc(2 * var(--slotted-space-2, 0.25rem));
|
|
942
|
+
block-size: calc(3 * var(--slotted-space-2, 0.25rem));
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/* The grabber. Four by thirty-six, full radius, centred above the header. */
|
|
946
|
+
[data-ui='drawer'][data-side='top']
|
|
947
|
+
> [data-part='panel']
|
|
948
|
+
> [data-part='resizer']
|
|
949
|
+
> [data-part='grip'],
|
|
950
|
+
[data-ui='drawer'][data-side='bottom']
|
|
951
|
+
> [data-part='panel']
|
|
952
|
+
> [data-part='resizer']
|
|
953
|
+
> [data-part='grip'] {
|
|
954
|
+
background-color: var(--_rest);
|
|
955
|
+
border-radius: var(--slotted-radius-full, 9999px);
|
|
956
|
+
block-size: var(--slotted-space-2, 0.25rem);
|
|
957
|
+
inline-size: var(--slotted-space-11, 3rem);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/* A sheet's lines are not painted: the pill is the whole picture. */
|
|
961
|
+
[data-ui='drawer'][data-side='top']
|
|
962
|
+
> [data-part='panel']
|
|
963
|
+
> [data-part='resizer']
|
|
964
|
+
> [data-part='line'],
|
|
965
|
+
[data-ui='drawer'][data-side='bottom']
|
|
966
|
+
> [data-part='panel']
|
|
967
|
+
> [data-part='resizer']
|
|
968
|
+
> [data-part='line'] {
|
|
969
|
+
display: none;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* The dot matrix recolours its ink and nothing else: its dots are
|
|
974
|
+
* `currentColor` over a transparent box, and a `background-color` here filled
|
|
975
|
+
* the box — the grip became a solid block under the hand, under hover and
|
|
976
|
+
* under focus. `splitter`, which draws the same picture, only ever changed
|
|
977
|
+
* `color`. The pill on a block side is a filled box, so it recolours its fill.
|
|
978
|
+
*/
|
|
979
|
+
[data-ui='drawer']
|
|
980
|
+
> [data-part='panel']
|
|
981
|
+
> [data-part='resizer']:not([data-disabled]):hover
|
|
982
|
+
> [data-part='grip'],
|
|
983
|
+
[data-ui='drawer']
|
|
984
|
+
> [data-part='panel']
|
|
985
|
+
> [data-part='resizer']:not([data-disabled]):focus-visible
|
|
986
|
+
> [data-part='grip'],
|
|
987
|
+
[data-ui='drawer'][data-dragging]
|
|
988
|
+
> [data-part='panel']
|
|
989
|
+
> [data-part='resizer']
|
|
990
|
+
> [data-part='grip'] {
|
|
991
|
+
color: var(--_active);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
[data-ui='drawer'][data-side='top']
|
|
995
|
+
> [data-part='panel']
|
|
996
|
+
> [data-part='resizer']:not([data-disabled]):hover
|
|
997
|
+
> [data-part='grip'],
|
|
998
|
+
[data-ui='drawer'][data-side='bottom']
|
|
999
|
+
> [data-part='panel']
|
|
1000
|
+
> [data-part='resizer']:not([data-disabled]):hover
|
|
1001
|
+
> [data-part='grip'],
|
|
1002
|
+
[data-ui='drawer'][data-side='top']
|
|
1003
|
+
> [data-part='panel']
|
|
1004
|
+
> [data-part='resizer']:not([data-disabled]):focus-visible
|
|
1005
|
+
> [data-part='grip'],
|
|
1006
|
+
[data-ui='drawer'][data-side='bottom']
|
|
1007
|
+
> [data-part='panel']
|
|
1008
|
+
> [data-part='resizer']:not([data-disabled]):focus-visible
|
|
1009
|
+
> [data-part='grip'],
|
|
1010
|
+
[data-ui='drawer'][data-dragging][data-side='top']
|
|
1011
|
+
> [data-part='panel']
|
|
1012
|
+
> [data-part='resizer']
|
|
1013
|
+
> [data-part='grip'],
|
|
1014
|
+
[data-ui='drawer'][data-dragging][data-side='bottom']
|
|
1015
|
+
> [data-part='panel']
|
|
1016
|
+
> [data-part='resizer']
|
|
1017
|
+
> [data-part='grip'] {
|
|
1018
|
+
background-color: var(--_active);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Hover and press are held back for `disabled`, which this separator does not
|
|
1023
|
+
* have today. The guard is the shared rule from
|
|
1024
|
+
* `packages/styles/src/states.test.mjs`, and it is written here so that a
|
|
1025
|
+
* disabled resize, if one ever arrives, cannot ship without it.
|
|
1026
|
+
*/
|
|
1027
|
+
/**
|
|
1028
|
+
* The ring stays on the gutter and it fills it: a two-pixel ring inset into
|
|
1029
|
+
* a four-pixel box leaves nothing between the two edges, so a focused
|
|
1030
|
+
* separator is a four-pixel bar and a hovered one is the two-pixel line down
|
|
1031
|
+
* its middle. The two pictures differ, which is all the ring has to do here.
|
|
1032
|
+
*
|
|
1033
|
+
* It cannot be drawn on the reach instead. `packages/styles/src/fallbacks.test.mjs`
|
|
1034
|
+
* forbids a system colour inside `::before` or `::after`, and the ring's own
|
|
1035
|
+
* last resort is `Highlight`.
|
|
1036
|
+
*/
|
|
1037
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='resizer']:focus-visible {
|
|
1038
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
1039
|
+
outline-offset: calc(-1 * var(--slotted-focus-ring-offset, 2px));
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* The one place this family asks the compositor for anything, and only while
|
|
1044
|
+
* the pointer is down. `will-change` on a box that is not moving costs a
|
|
1045
|
+
* layer for nothing; on the box the hand is dragging it is the difference
|
|
1046
|
+
* between a resize the engine prepares for and one it discovers each frame.
|
|
1047
|
+
* It is written on the panel because the panel is what changes size.
|
|
1048
|
+
*/
|
|
1049
|
+
[data-ui='drawer'][data-dragging][data-side='start'] > [data-part='panel'],
|
|
1050
|
+
[data-ui='drawer'][data-dragging][data-side='end'] > [data-part='panel'] {
|
|
1051
|
+
will-change: inline-size;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
[data-ui='drawer'][data-dragging][data-side='top'] > [data-part='panel'],
|
|
1055
|
+
[data-ui='drawer'][data-dragging][data-side='bottom'] > [data-part='panel'] {
|
|
1056
|
+
will-change: block-size;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/* ── The header ───────────────────────────────────────────────────────── */
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Two columns: the words and the close button. The button keeps a column of
|
|
1063
|
+
* its own, so a title that wraps to two lines grows the row instead of
|
|
1064
|
+
* pushing it off the line.
|
|
1065
|
+
*
|
|
1066
|
+
* The header draws no rule of its own. Every rule in the panel belongs to the
|
|
1067
|
+
* row *below* it, which is what makes the count right for every combination a
|
|
1068
|
+
* consumer can write: a header, a body and a footer draw two rules; a header
|
|
1069
|
+
* and a footer alone draw one; and a panel that is only a body draws none.
|
|
1070
|
+
*/
|
|
1071
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='header'] {
|
|
1072
|
+
grid-row: 1;
|
|
1073
|
+
display: grid;
|
|
1074
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
1075
|
+
align-items: start;
|
|
1076
|
+
column-gap: var(--_gap);
|
|
1077
|
+
row-gap: var(--slotted-space-2, 0.25rem);
|
|
1078
|
+
padding-block: var(--_padding-block);
|
|
1079
|
+
padding-inline: var(--_row-padding-inline-start) var(--_row-padding-inline-end);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* `overflow-wrap: anywhere` and a zero minimum are the pair that answer a
|
|
1084
|
+
* hostile string. A ninety-character unbroken token in a title widens the
|
|
1085
|
+
* panel past its own measure, and a panel pinned to an edge then pushes its
|
|
1086
|
+
* inner edge across the layer.
|
|
1087
|
+
*/
|
|
1088
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='header'] > [data-part='title'] {
|
|
1089
|
+
grid-column: 1;
|
|
1090
|
+
grid-row: 1;
|
|
1091
|
+
margin: 0;
|
|
1092
|
+
min-inline-size: 0;
|
|
1093
|
+
overflow-wrap: anywhere;
|
|
1094
|
+
font-family: var(
|
|
1095
|
+
--slotted-drawer-title-font-family,
|
|
1096
|
+
var(
|
|
1097
|
+
--slotted-font-family-heading,
|
|
1098
|
+
var(--slotted-font-family-sans, ui-sans-serif, system-ui, sans-serif)
|
|
1099
|
+
)
|
|
1100
|
+
);
|
|
1101
|
+
font-size: var(--_title-font-size);
|
|
1102
|
+
font-weight: var(--slotted-font-weight-semibold, 600);
|
|
1103
|
+
line-height: var(--slotted-line-height-lg, 1.5);
|
|
1104
|
+
color: var(--slotted-drawer-title-color, var(--slotted-text, CanvasText));
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='header'] > [data-part='description'] {
|
|
1108
|
+
grid-column: 1;
|
|
1109
|
+
grid-row: 2;
|
|
1110
|
+
margin: 0;
|
|
1111
|
+
min-inline-size: 0;
|
|
1112
|
+
overflow-wrap: anywhere;
|
|
1113
|
+
font-size: var(--_description-font-size);
|
|
1114
|
+
line-height: var(--slotted-line-height-md, 1.5);
|
|
1115
|
+
color: var(--slotted-drawer-description-color, var(--slotted-text-muted, GrayText));
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
/* ── The close button ─────────────────────────────────────────────────── */
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* An inner control, not a button (R2): the box is the glyph plus a step of
|
|
1122
|
+
* air on each side, floored at the WCAG 2.2 minimum, and the glyph is
|
|
1123
|
+
* centred in it. It was the control height before, a thirty-four pixel
|
|
1124
|
+
* square beside a sixteen pixel mark. The glyph is the binding's `Glyph` in
|
|
1125
|
+
* an `icon` box, sized here through the icon token so the box and the
|
|
1126
|
+
* formula above read one length.
|
|
1127
|
+
*/
|
|
1128
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='header'] > [data-part='close'] {
|
|
1129
|
+
--slotted-icon-size: var(--_icon-size);
|
|
1130
|
+
|
|
1131
|
+
grid-column: 2;
|
|
1132
|
+
grid-row: 1 / span 2;
|
|
1133
|
+
align-self: start;
|
|
1134
|
+
display: inline-grid;
|
|
1135
|
+
place-items: center;
|
|
1136
|
+
block-size: var(--_close-size);
|
|
1137
|
+
inline-size: var(--_close-size);
|
|
1138
|
+
/* The box overhangs the panel's edge by its own air, so the mark aligns
|
|
1139
|
+
with the title. */
|
|
1140
|
+
margin-inline-end: calc(-1 * (var(--_close-size) - var(--_icon-size)) / 2);
|
|
1141
|
+
padding: 0;
|
|
1142
|
+
appearance: none;
|
|
1143
|
+
background-color: transparent;
|
|
1144
|
+
border: 0;
|
|
1145
|
+
border-radius: var(--slotted-control-radius, var(--slotted-radius-sm, 4px));
|
|
1146
|
+
color: var(--slotted-drawer-close-color, var(--slotted-text-muted, GrayText));
|
|
1147
|
+
cursor: pointer;
|
|
1148
|
+
transition:
|
|
1149
|
+
background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
1150
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
|
|
1151
|
+
color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
1152
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/* On a finger the target is the comfortable size in both axes and the box
|
|
1156
|
+
is the one a mouse sees: a transparent `::before` centred on the control
|
|
1157
|
+
reaches 44 pixels, and the ground and the ring stay square. */
|
|
1158
|
+
@media (pointer: coarse) {
|
|
1159
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='header'] > [data-part='close'] {
|
|
1160
|
+
position: relative;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='header'] > [data-part='close']::before {
|
|
1164
|
+
content: '';
|
|
1165
|
+
inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
|
|
1166
|
+
position: absolute;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
[data-ui='drawer']
|
|
1171
|
+
> [data-part='panel']
|
|
1172
|
+
> [data-part='header']
|
|
1173
|
+
> [data-part='close']:not(:disabled):hover {
|
|
1174
|
+
/* The ground moves under the pointer and nothing else does (R2). */
|
|
1175
|
+
background-color: var(
|
|
1176
|
+
--slotted-drawer-close-hover-background,
|
|
1177
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
[data-ui='drawer']
|
|
1182
|
+
> [data-part='panel']
|
|
1183
|
+
> [data-part='header']
|
|
1184
|
+
> [data-part='close']:not(:disabled):active {
|
|
1185
|
+
background-color: var(
|
|
1186
|
+
--slotted-drawer-close-active-background,
|
|
1187
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
[data-ui='drawer']
|
|
1192
|
+
> [data-part='panel']
|
|
1193
|
+
> [data-part='header']
|
|
1194
|
+
> [data-part='close']:focus-visible {
|
|
1195
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
1196
|
+
outline-offset: calc(-1 * var(--slotted-focus-ring-offset, 2px));
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/* ── The row that scrolls ─────────────────────────────────────────────── */
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* `overscroll-behavior: contain` is what keeps a wheel that reaches the end of
|
|
1203
|
+
* this box from scrolling the page behind it. The scroll lock in
|
|
1204
|
+
* `dismissable-layer` covers the modal case; this covers the other two, where
|
|
1205
|
+
* the page behind is not locked at all.
|
|
1206
|
+
*/
|
|
1207
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='body'] {
|
|
1208
|
+
grid-row: 2;
|
|
1209
|
+
min-block-size: 0;
|
|
1210
|
+
min-inline-size: 0;
|
|
1211
|
+
overflow-x: hidden;
|
|
1212
|
+
overflow-y: auto;
|
|
1213
|
+
overflow-inline: hidden;
|
|
1214
|
+
overflow-block: auto;
|
|
1215
|
+
overscroll-behavior: contain;
|
|
1216
|
+
padding-block: var(--_padding-block);
|
|
1217
|
+
padding-inline: var(--_row-padding-inline-start) var(--_row-padding-inline-end);
|
|
1218
|
+
font-size: var(--_body-font-size);
|
|
1219
|
+
line-height: var(--slotted-line-height-md, 1.5);
|
|
1220
|
+
color: var(--slotted-drawer-body-color, var(--slotted-text, CanvasText));
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/* Content that brings its own inset — a page, a list on the panel's edges. */
|
|
1224
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='body'][data-inset='none'] {
|
|
1225
|
+
padding: 0;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='body']:not(:first-child) {
|
|
1229
|
+
border-block-start: var(--_rule-size) solid
|
|
1230
|
+
var(--slotted-drawer-rule-color, var(--slotted-border-subtle, ButtonBorder));
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Content that is not there yet. Only Ant among the surveyed implementations
|
|
1235
|
+
* has a state for it, so elsewhere a consumer renders a spinner conditionally
|
|
1236
|
+
* and the panel jumps to size when the data lands. The row keeps a floor here
|
|
1237
|
+
* instead, and the header, the close button, Escape and the resizer all stay
|
|
1238
|
+
* operable: a loading drawer a reader cannot leave is the worse failure.
|
|
1239
|
+
*/
|
|
1240
|
+
[data-ui='drawer'][data-loading] > [data-part='panel'] > [data-part='body'] {
|
|
1241
|
+
min-block-size: var(--slotted-space-12, 4rem);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/* ── The actions ──────────────────────────────────────────────────────── */
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* `flex-wrap` rather than a grid: two actions that do not fit on one line
|
|
1248
|
+
* stack, and the primary one stays last in both readings. This row is the
|
|
1249
|
+
* reason the middle one is bounded — an unbounded panel pushes it past the
|
|
1250
|
+
* fold, which is the whole failure the three-row grid exists to prevent.
|
|
1251
|
+
*/
|
|
1252
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='footer'] {
|
|
1253
|
+
grid-row: 3;
|
|
1254
|
+
display: flex;
|
|
1255
|
+
flex-wrap: wrap;
|
|
1256
|
+
align-items: center;
|
|
1257
|
+
justify-content: flex-end;
|
|
1258
|
+
gap: var(--_gap);
|
|
1259
|
+
padding-block: var(--_padding-block);
|
|
1260
|
+
padding-inline: var(--_row-padding-inline-start) var(--_row-padding-inline-end);
|
|
1261
|
+
background: var(--slotted-drawer-footer-surface, var(--slotted-surface-sunken, Canvas));
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* The edge of the device, on a sheet.
|
|
1266
|
+
*
|
|
1267
|
+
* A bottom sheet's footer sits where the home indicator and the gesture bar
|
|
1268
|
+
* live, and a row of actions under them is a row the reader taps twice: once
|
|
1269
|
+
* on the system and once on the button. `env()` resolves to nought wherever
|
|
1270
|
+
* there is no inset, so this costs nothing on a desktop and there is no query
|
|
1271
|
+
* to keep in step.
|
|
1272
|
+
*
|
|
1273
|
+
* Only `bottom`, and only the footer. A sheet from the top has the status bar
|
|
1274
|
+
* above it and the header already clears it; the body scrolls, so an inset on
|
|
1275
|
+
* it would be padding inside a scrollport rather than under it.
|
|
1276
|
+
*/
|
|
1277
|
+
[data-ui='drawer'][data-side='bottom'] > [data-part='panel'] > [data-part='footer'] {
|
|
1278
|
+
padding-block-end: calc(
|
|
1279
|
+
var(--_footer-padding-block, var(--_padding-block)) + env(safe-area-inset-bottom, 0px)
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
[data-ui='drawer'] > [data-part='panel'] > [data-part='footer']:not(:first-child) {
|
|
1284
|
+
border-block-start: var(--_rule-size) solid
|
|
1285
|
+
var(--slotted-drawer-rule-color, var(--slotted-border-subtle, ButtonBorder));
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/* ── Forced colours ───────────────────────────────────────────────────── */
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* The theme remaps every token this family reads, so the only rule here is the
|
|
1292
|
+
* one the theme cannot reach: `--slotted-scrim` becomes `Canvas`, an opaque
|
|
1293
|
+
* ground, and the panel on it needs an edge the shadow no longer draws. The
|
|
1294
|
+
* border above already states one; this thickens it to the weight a system
|
|
1295
|
+
* border is read at, and gives the panel the three edges its side does not.
|
|
1296
|
+
*/
|
|
1297
|
+
@media (forced-colors: active) {
|
|
1298
|
+
[data-ui='drawer'] > [data-part='panel'] {
|
|
1299
|
+
border-width: var(--slotted-border-width-thick, 2px);
|
|
1300
|
+
border-color: CanvasText;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|