@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,817 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* A list of commands on a layer, opened from a control, from an item of
|
|
4
|
+
* another menu, or from a trigger on a menubar.
|
|
5
|
+
*
|
|
6
|
+
* The two boxes are `tooltip`'s and `popover`'s: the **layer** is what
|
|
7
|
+
* `packages/core/src/positioner` writes `position`, `left` and `top` onto and
|
|
8
|
+
* what slides and fades, because the module leaves `transform` alone; the
|
|
9
|
+
* **content** is the painted surface and the only thing that takes the
|
|
10
|
+
* pointer. The gap between the trigger and the surface is the layer's own
|
|
11
|
+
* padding rather than the positioner's offset, so the pointer never crosses
|
|
12
|
+
* empty page on its way from a parent item into a submenu — which is half of
|
|
13
|
+
* what makes the grace area work.
|
|
14
|
+
*
|
|
15
|
+
* ┌─ root ─ the layer ─────────────────────────────────────┐
|
|
16
|
+
* │ ┌─ content ─ role="menu" ─ scrolls ───────────────┐ │
|
|
17
|
+
* │ │ group ▸ group-label │ │
|
|
18
|
+
* │ │ item ▸ indicator or icon │ label │ shortcut │ │
|
|
19
|
+
* │ │ description │ │
|
|
20
|
+
* │ │ separator │ │
|
|
21
|
+
* │ │ empty │ │
|
|
22
|
+
* │ └─────────────────────────────────────────────────┘ │
|
|
23
|
+
* └────────────────────────────────────────────────────────┘
|
|
24
|
+
*
|
|
25
|
+
* ── Why the surface is a grid and the item is a subgrid ──────────────
|
|
26
|
+
*
|
|
27
|
+
* Every surveyed implementation lays an item out as a flex row with the
|
|
28
|
+
* shortcut pushed to the end, which lets a long translated label eat the
|
|
29
|
+
* shortcut's space and leaves two items' labels out of line whenever one has
|
|
30
|
+
* an icon and the other does not. Here the surface owns four columns — the
|
|
31
|
+
* mark, the label, the meta, the shortcut — and every item, group and group label
|
|
32
|
+
* passes through them with `grid-template-columns: subgrid`. Labels line up
|
|
33
|
+
* across the whole menu, the shortcut column is as wide as the widest
|
|
34
|
+
* shortcut and never narrower, and the label column is the only one that
|
|
35
|
+
* gives way.
|
|
36
|
+
*
|
|
37
|
+
* The mark column holds the indicator or the icon, and not both. A checkbox
|
|
38
|
+
* item's mark is its icon: an item that carried a check in one gutter and a
|
|
39
|
+
* glyph in a second would open every menu with two empty columns down its
|
|
40
|
+
* start edge, which is the shape a menu inherits from a desktop menu bar and
|
|
41
|
+
* not one a product needs. Radix, Base UI and Ark all draw the two in one
|
|
42
|
+
* place for the same reason.
|
|
43
|
+
*
|
|
44
|
+
* The columns carry no `column-gap`. A gap would be drawn between two tracks
|
|
45
|
+
* even when the first is empty, so a menu with no icons at all would open
|
|
46
|
+
* with a gutter down its start edge. Each optional cell carries its own
|
|
47
|
+
* margin instead, and a column nothing occupies collapses to nothing.
|
|
48
|
+
*
|
|
49
|
+
* ── Density ─────────────────────────────────────────────────────────
|
|
50
|
+
*
|
|
51
|
+
* An item is a control, so every measurement it makes reads control
|
|
52
|
+
* geometry: its height, its inset, its type, its glyph and its gap. A dense
|
|
53
|
+
* menu holds more commands and is the same component; only the surface's own
|
|
54
|
+
* inset and radius come off the space and radius scales, as `popover`'s do.
|
|
55
|
+
*/
|
|
56
|
+
[data-ui='menu'] {
|
|
57
|
+
--_gap: var(--slotted-menu-gap, var(--slotted-space-2, 0.25rem));
|
|
58
|
+
|
|
59
|
+
--_surface: var(--slotted-menu-surface, var(--slotted-surface-raised, #ffffff));
|
|
60
|
+
--_text: var(--slotted-menu-text, var(--slotted-text, #0f172a));
|
|
61
|
+
--_muted: var(--slotted-menu-text-muted, var(--slotted-text-muted, #5b6b7f));
|
|
62
|
+
--_border-color: var(--slotted-menu-border-color, var(--slotted-border-subtle, #e2e8f0));
|
|
63
|
+
--_border-width: var(--slotted-border-width-thin, 1px);
|
|
64
|
+
--_radius: var(--slotted-menu-radius, var(--slotted-radius-lg, 8px));
|
|
65
|
+
--_shadow: var(--slotted-menu-shadow, var(--slotted-shadow-md));
|
|
66
|
+
|
|
67
|
+
/* The highlight is one step of the neutral tone at rest and the next one
|
|
68
|
+
under the press, which is the same pair every control in this library
|
|
69
|
+
uses for a subtle surface. A tone on an item retargets both. */
|
|
70
|
+
--_highlight: var(--slotted-menu-highlight, var(--slotted-tone-secondary-subtle-hover));
|
|
71
|
+
--_highlight-active: var(
|
|
72
|
+
--slotted-menu-highlight-active,
|
|
73
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
74
|
+
);
|
|
75
|
+
--_accent: var(--_text);
|
|
76
|
+
|
|
77
|
+
--_inset-block: var(--slotted-menu-padding-block, var(--slotted-space-2, 0.25rem));
|
|
78
|
+
--_inset-inline: var(--slotted-menu-padding-inline, var(--slotted-space-2, 0.25rem));
|
|
79
|
+
/**
|
|
80
|
+
* The item's own inset is a control's inline padding less the surface's,
|
|
81
|
+
* so the first column starts where the text of the control that opened
|
|
82
|
+
* the menu starts: both sit one border and one control padding in from
|
|
83
|
+
* the shared start edge. With the full control padding the column stood
|
|
84
|
+
* four pixels further in (x 55 against the trigger's 51).
|
|
85
|
+
*/
|
|
86
|
+
--_item-padding: var(--slotted-control-padding-inline-md, 0.8125rem);
|
|
87
|
+
--_item-inset: calc(var(--_item-padding) - var(--_inset-inline));
|
|
88
|
+
--_item-height: var(--slotted-control-height-md, 34px);
|
|
89
|
+
--_item-radius: var(--slotted-menu-item-radius, var(--slotted-control-radius));
|
|
90
|
+
--_font-size: var(--slotted-control-font-size-md, 13px);
|
|
91
|
+
--_secondary-font-size: var(--slotted-control-font-size-sm, 12px);
|
|
92
|
+
--_icon-size: var(--slotted-control-icon-size-md, 16px);
|
|
93
|
+
--_column-gap: var(--slotted-control-gap-md, 0.5rem);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Line boxes on whole pixels. The scale's ratios give 19.5 at 13px and
|
|
97
|
+
* 17.4 at 12px, and a surface holding a group label and a description
|
|
98
|
+
* summed them to 270.28 pixels tall, so its bottom border sat on a
|
|
99
|
+
* fractional row. Rounding up keeps each line at least its ratio.
|
|
100
|
+
*/
|
|
101
|
+
--_leading: round(up, calc(var(--_font-size) * var(--slotted-line-height-md, 1.5)), 1px);
|
|
102
|
+
--_secondary-leading: round(
|
|
103
|
+
up,
|
|
104
|
+
calc(var(--_secondary-font-size) * var(--slotted-line-height-sm, 1.45)),
|
|
105
|
+
1px
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The cap. Measure belongs to `container`, so the family offers no axis for
|
|
110
|
+
* it and still has to stop a `max-content` surface from setting one command
|
|
111
|
+
* the width of the page. Wide enough for a label and a shortcut beside it,
|
|
112
|
+
* narrow enough to hang off a button.
|
|
113
|
+
*/
|
|
114
|
+
--_measure: var(--slotted-menu-measure, 16rem);
|
|
115
|
+
/**
|
|
116
|
+
* And the floor, which the family had and put on the wrong element (M3).
|
|
117
|
+
*
|
|
118
|
+
* A `max-content` surface is as wide as its longest command, so a menu of
|
|
119
|
+
* short words is a sliver: "Inbox, Archive, Drafts, Sent" measured 91px at
|
|
120
|
+
* `md`, 81px at `compact` and 77px at `dense`, against the 228px the *empty*
|
|
121
|
+
* menu stood at — because the empty row carried a `min-inline-size` of its
|
|
122
|
+
* own and the surface carried none. The surface therefore grew when the
|
|
123
|
+
* list was filtered away and shrank when the commands came back, which is
|
|
124
|
+
* the one thing a menu must not do under a pointer that is already moving
|
|
125
|
+
* toward it.
|
|
126
|
+
*
|
|
127
|
+
* The floor is the surface's now and the empty row inherits it, so the two
|
|
128
|
+
* cannot disagree. It is subordinate to the room the positioner reports:
|
|
129
|
+
* `min-inline-size` beats `max-inline-size`, so a floor stated flat would
|
|
130
|
+
* push a menu out of a column too narrow to hold it.
|
|
131
|
+
*/
|
|
132
|
+
--_min-measure: min(
|
|
133
|
+
var(--slotted-menu-min-measure, 12rem),
|
|
134
|
+
var(--slotted-positioner-available-width, 100vw)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
/* Only the block inset: the positioner writes `left` and never `right`, and
|
|
138
|
+
`inset-inline-start` would resolve to `right` in a right-to-left document
|
|
139
|
+
and win over the measured value. */
|
|
140
|
+
position: absolute;
|
|
141
|
+
inset-block-start: 0;
|
|
142
|
+
|
|
143
|
+
z-index: calc(var(--slotted-z-index-popover, 1100) + var(--slotted-layer-depth, 0));
|
|
144
|
+
isolation: isolate;
|
|
145
|
+
|
|
146
|
+
box-sizing: border-box;
|
|
147
|
+
inline-size: max-content;
|
|
148
|
+
max-inline-size: min(var(--_measure), var(--slotted-positioner-available-width, 100vw));
|
|
149
|
+
min-inline-size: var(--_min-measure);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The gap sits on the axis that faces the anchor and on no other. A layer
|
|
153
|
+
* padded on all four sides drops a submenu's first item below the item that
|
|
154
|
+
* opened it by the padding it does not need, and the reader reads a list
|
|
155
|
+
* that starts under its own parent rather than beside it. `data-side` is
|
|
156
|
+
* physical, because the engine measures in physical coordinates.
|
|
157
|
+
*/
|
|
158
|
+
padding-block: var(--_gap);
|
|
159
|
+
padding-inline: 0;
|
|
160
|
+
|
|
161
|
+
/* The layer is only a frame around the surface; the surface takes the
|
|
162
|
+
pointer, and the frame must not swallow a press meant for the page. */
|
|
163
|
+
pointer-events: none;
|
|
164
|
+
|
|
165
|
+
opacity: 0;
|
|
166
|
+
/**
|
|
167
|
+
* R7: the layer arrives along the axis it opens on — from one space step
|
|
168
|
+
* above its place when it opens downward, from one step below when it
|
|
169
|
+
* opens upward — and never along the inline axis. A `scale` from the
|
|
170
|
+
* corner the positioner named as origin moved the far edge by four per
|
|
171
|
+
* cent of the width, which read as an entrance from the left.
|
|
172
|
+
*/
|
|
173
|
+
--_enter-distance: var(--slotted-space-2, 0.25rem);
|
|
174
|
+
translate: 0 calc(-1 * var(--_enter-distance));
|
|
175
|
+
transition:
|
|
176
|
+
opacity var(--slotted-duration-short) var(--slotted-easing-exit),
|
|
177
|
+
translate var(--slotted-duration-short) var(--slotted-easing-exit);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
[data-ui='menu'][data-side='top'] {
|
|
181
|
+
translate: 0 var(--_enter-distance);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* A submenu, or a menu placed beside its anchor, arrives along the inline
|
|
185
|
+
axis it opens on: from one step inside the parent's edge (M1). */
|
|
186
|
+
[data-ui='menu'][data-side='right'] {
|
|
187
|
+
translate: calc(-1 * var(--_enter-distance)) 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
[data-ui='menu'][data-side='left'] {
|
|
191
|
+
translate: var(--_enter-distance) 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
[data-ui='menu']:is([data-side='left'], [data-side='right']) {
|
|
195
|
+
padding-block: 0;
|
|
196
|
+
padding-inline: var(--_gap);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The entrance is a keyframe animation and not a transition. A transition
|
|
201
|
+
* starts from the style the browser computed last, and the positioner
|
|
202
|
+
* writes `data-side` in the same style update as the family opens the
|
|
203
|
+
* layer, so a transition set out from the closed style of the side the
|
|
204
|
+
* layer had before it had one — the bottom's — whatever side it opens on.
|
|
205
|
+
* A keyframe states where it starts. The exit stays a transition: it
|
|
206
|
+
* leaves from wherever the layer is, on the closed rule's durations.
|
|
207
|
+
*/
|
|
208
|
+
[data-ui='menu'][data-open] {
|
|
209
|
+
opacity: 1;
|
|
210
|
+
translate: 0 0;
|
|
211
|
+
transition: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Only a layer the positioner placed arrives: it writes `data-positioned`
|
|
216
|
+
* with the side, and a layer written open in static markup — the harness,
|
|
217
|
+
* a docs snippet — is simply there, and is photographed where it stands.
|
|
218
|
+
*/
|
|
219
|
+
[data-ui='menu'][data-open][data-positioned] {
|
|
220
|
+
animation: slotted-menu-enter-down var(--slotted-duration-medium) var(--slotted-easing-enter);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
[data-ui='menu'][data-open][data-positioned][data-side='top'] {
|
|
224
|
+
animation-name: slotted-menu-enter-up;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
[data-ui='menu'][data-open][data-positioned][data-side='right'] {
|
|
228
|
+
animation-name: slotted-menu-enter-from-start;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
[data-ui='menu'][data-open][data-positioned][data-side='left'] {
|
|
232
|
+
animation-name: slotted-menu-enter-from-end;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@keyframes slotted-menu-enter-down {
|
|
236
|
+
from {
|
|
237
|
+
opacity: 0;
|
|
238
|
+
translate: 0 calc(-1 * var(--_enter-distance));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@keyframes slotted-menu-enter-up {
|
|
243
|
+
from {
|
|
244
|
+
opacity: 0;
|
|
245
|
+
translate: 0 var(--_enter-distance);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@keyframes slotted-menu-enter-from-start {
|
|
250
|
+
from {
|
|
251
|
+
opacity: 0;
|
|
252
|
+
translate: calc(-1 * var(--_enter-distance)) 0;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@keyframes slotted-menu-enter-from-end {
|
|
257
|
+
from {
|
|
258
|
+
opacity: 0;
|
|
259
|
+
translate: var(--_enter-distance) 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@keyframes slotted-menu-fade {
|
|
264
|
+
from {
|
|
265
|
+
opacity: 0;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Stillness keeps the fade and drops the movement (R7). */
|
|
270
|
+
@media (prefers-reduced-motion: reduce) {
|
|
271
|
+
[data-ui='menu'],
|
|
272
|
+
[data-ui='menu'][data-side='top'],
|
|
273
|
+
[data-ui='menu'][data-side='left'],
|
|
274
|
+
[data-ui='menu'][data-side='right'] {
|
|
275
|
+
translate: 0 0;
|
|
276
|
+
transition: opacity var(--slotted-duration-short) var(--slotted-easing-exit);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
[data-ui='menu'][data-open][data-positioned],
|
|
280
|
+
[data-ui='menu'][data-open][data-positioned][data-side='top'],
|
|
281
|
+
[data-ui='menu'][data-open][data-positioned][data-side='left'],
|
|
282
|
+
[data-ui='menu'][data-open][data-positioned][data-side='right'] {
|
|
283
|
+
animation-name: slotted-menu-fade;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* The anchor scrolled out of its own clipper. The menu is still open and its
|
|
288
|
+
commands still belong to a control the reader can no longer see. */
|
|
289
|
+
[data-ui='menu'][data-detached] {
|
|
290
|
+
visibility: hidden;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
[data-ui='menu'][data-size='sm'] {
|
|
294
|
+
--_item-padding: var(--slotted-control-padding-inline-sm, 0.625rem);
|
|
295
|
+
--_item-height: var(--slotted-control-height-sm, 30px);
|
|
296
|
+
--_font-size: var(--slotted-control-font-size-sm, 12px);
|
|
297
|
+
--_secondary-font-size: var(--slotted-font-size-xs, 0.75rem);
|
|
298
|
+
--_icon-size: var(--slotted-control-icon-size-sm, 14px);
|
|
299
|
+
--_column-gap: var(--slotted-control-gap-sm, 0.375rem);
|
|
300
|
+
--_measure: var(--slotted-menu-measure-sm, 14rem);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
[data-ui='menu'][data-size='lg'] {
|
|
304
|
+
--_item-padding: var(--slotted-control-padding-inline-lg, 1rem);
|
|
305
|
+
--_item-height: var(--slotted-control-height-lg, 38px);
|
|
306
|
+
--_font-size: var(--slotted-control-font-size-lg, 14px);
|
|
307
|
+
--_secondary-font-size: var(--slotted-control-font-size-sm, 12px);
|
|
308
|
+
--_icon-size: var(--slotted-control-icon-size-lg, 18px);
|
|
309
|
+
--_column-gap: var(--slotted-control-gap-lg, 0.5rem);
|
|
310
|
+
--_measure: var(--slotted-menu-measure-lg, 20rem);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* ── The surface ──────────────────────────────────────────────────────── */
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Four columns, and the height the positioner says is left. Radix, Base UI
|
|
317
|
+
* and React Aria each publish the remaining height as a custom property and
|
|
318
|
+
* none of them bounds itself by default, so a menu of thirty commands runs
|
|
319
|
+
* off the bottom of the screen. `overscroll-behavior` keeps the page still
|
|
320
|
+
* once the surface has scrolled to its end.
|
|
321
|
+
*/
|
|
322
|
+
[data-ui='menu'] > [data-part='content'] {
|
|
323
|
+
background: var(--_surface);
|
|
324
|
+
border: var(--_border-width) solid var(--_border-color);
|
|
325
|
+
border-radius: var(--_radius);
|
|
326
|
+
box-shadow: var(--_shadow);
|
|
327
|
+
box-sizing: border-box;
|
|
328
|
+
color: var(--_text);
|
|
329
|
+
display: grid;
|
|
330
|
+
font-family: var(--slotted-font-family-sans);
|
|
331
|
+
font-size: var(--_font-size);
|
|
332
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
333
|
+
max-block-size: calc(var(--slotted-positioner-available-height, 100vh) - 2 * var(--_gap));
|
|
334
|
+
outline: none;
|
|
335
|
+
overflow-y: auto;
|
|
336
|
+
overscroll-behavior: contain;
|
|
337
|
+
padding-block: var(--_inset-block);
|
|
338
|
+
padding-inline: var(--_inset-inline);
|
|
339
|
+
pointer-events: auto;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The surface itself never takes a focus ring. Focus lives on an item from
|
|
344
|
+
* the moment the menu opens, and a ring around the whole surface would say
|
|
345
|
+
* the surface is the thing being operated.
|
|
346
|
+
*/
|
|
347
|
+
[data-ui='menu'] > [data-part='content']:focus-visible {
|
|
348
|
+
outline: none;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* ── Groups ───────────────────────────────────────────────────────────── */
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* A group is a grid area spanning every column, and a grid of its own that
|
|
355
|
+
* inherits them, so its items keep the menu's columns instead of starting a
|
|
356
|
+
* second set. Without the subgrid a group would align its own labels and
|
|
357
|
+
* disagree with the items outside it.
|
|
358
|
+
*/
|
|
359
|
+
[data-ui='menu'] [data-part='group'] {
|
|
360
|
+
display: grid;
|
|
361
|
+
grid-column: 1 / -1;
|
|
362
|
+
grid-template-columns: subgrid;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
[data-ui='menu'] [data-part='group-label'] {
|
|
366
|
+
color: var(--_muted);
|
|
367
|
+
font-size: var(--_secondary-font-size);
|
|
368
|
+
font-weight: var(--slotted-font-weight-semibold, 600);
|
|
369
|
+
grid-column: 1 / -1;
|
|
370
|
+
line-height: var(--_secondary-leading);
|
|
371
|
+
padding-block: var(--slotted-space-2, 0.25rem);
|
|
372
|
+
padding-inline: var(--_item-inset);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* ── Header ───────────────────────────────────────────────────────────── */
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Who the menu belongs to: an avatar or a mark, a name, a line under it. It
|
|
379
|
+
* spans every column and keeps a grid of its own rather than the surface's
|
|
380
|
+
* subgrid, because a 32-pixel avatar in the mark column would widen that
|
|
381
|
+
* column for every item below it. The media takes the first track and both
|
|
382
|
+
* lines; the name and its line reuse `label` and `description`. It starts
|
|
383
|
+
* at the item inset, so the avatar's edge is the icons' edge.
|
|
384
|
+
*/
|
|
385
|
+
[data-ui='menu'] [data-part='header'] {
|
|
386
|
+
align-items: center;
|
|
387
|
+
column-gap: var(--slotted-space-5, 0.75rem);
|
|
388
|
+
display: grid;
|
|
389
|
+
grid-column: 1 / -1;
|
|
390
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
391
|
+
line-height: var(--_leading);
|
|
392
|
+
padding-block: var(--slotted-space-2, 0.25rem);
|
|
393
|
+
padding-inline: var(--_item-inset);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
[data-ui='menu'] [data-part='header'] > :not([data-part='label'], [data-part='description']) {
|
|
397
|
+
grid-column: 1;
|
|
398
|
+
grid-row: 1 / span 2;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
[data-ui='menu'] [data-part='header'] > [data-part='label'] {
|
|
402
|
+
font-weight: var(--slotted-font-weight-semibold, 600);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* ── Items ────────────────────────────────────────────────────────────── */
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* A native `button` carrying `role="menuitem"`: the platform gives the press,
|
|
409
|
+
* the Enter and Space handling and the pointer semantics, and the role
|
|
410
|
+
* replaces `button` so a reader hears an item. Base UI and Ark render a `div`
|
|
411
|
+
* and reimplement all three.
|
|
412
|
+
*
|
|
413
|
+
* `min-block-size` rather than a height, because an item with a description
|
|
414
|
+
* is two lines tall and still has to clear the target minimum.
|
|
415
|
+
*
|
|
416
|
+
* The hand, as on every button in the library and on every list row of
|
|
417
|
+
* `select`, `combobox`, `listbox` and `command-palette`. The arrow it wore
|
|
418
|
+
* before followed Radix, Base UI and Ark, which follow the operating
|
|
419
|
+
* system's own menus; this surface is a `div` on the page, and the argument
|
|
420
|
+
* `select` used to take the hand for its options holds here unchanged. An
|
|
421
|
+
* unavailable item keeps the hand and refuses the press, as `button` does.
|
|
422
|
+
*/
|
|
423
|
+
[data-ui='menu'] [data-part='item'] {
|
|
424
|
+
appearance: none;
|
|
425
|
+
background: transparent;
|
|
426
|
+
border: 0;
|
|
427
|
+
border-radius: var(--_item-radius);
|
|
428
|
+
box-sizing: border-box;
|
|
429
|
+
color: inherit;
|
|
430
|
+
cursor: pointer;
|
|
431
|
+
display: grid;
|
|
432
|
+
font: inherit;
|
|
433
|
+
grid-column: 1 / -1;
|
|
434
|
+
grid-template-columns: subgrid;
|
|
435
|
+
align-content: center;
|
|
436
|
+
/**
|
|
437
|
+
* The line box belongs to the item, and every cell inherits it. `1lh` on a
|
|
438
|
+
* mark resolves against the mark's *own* line-height, so a mark inside an
|
|
439
|
+
* item that left the line box at `normal` measured 15 pixels while the
|
|
440
|
+
* label beside it measured 19.5 — and start-aligned in the same row, the
|
|
441
|
+
* mark sat two pixels above the text it annotates. Stating it once here is
|
|
442
|
+
* what makes `1lh` mean the same thing in all four columns.
|
|
443
|
+
*/
|
|
444
|
+
line-height: var(--_leading);
|
|
445
|
+
min-block-size: var(--_item-height);
|
|
446
|
+
padding-block: var(--slotted-space-2, 0.25rem);
|
|
447
|
+
padding-inline: var(--_item-inset);
|
|
448
|
+
/* The one gap inside an item, between a label and the line under it. The
|
|
449
|
+
two lines set solid read as one wrapped sentence rather than as a
|
|
450
|
+
command and its explanation. */
|
|
451
|
+
row-gap: var(--slotted-space-1, 0.125rem);
|
|
452
|
+
text-align: start;
|
|
453
|
+
text-decoration: none;
|
|
454
|
+
transition:
|
|
455
|
+
background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
456
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
|
|
457
|
+
color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
458
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* The highlight is painted on focus rather than on hover, and that is the
|
|
463
|
+
* decision the family turns on: the pointer entering an item moves real focus
|
|
464
|
+
* to it, so exactly one item is highlighted whichever device is driving.
|
|
465
|
+
* Hovering one item while the keyboard has moved to another is the state
|
|
466
|
+
* `data-highlighted` exists to prevent in Radix and Ark; here the platform
|
|
467
|
+
* prevents it, because there is only ever one focused element.
|
|
468
|
+
*/
|
|
469
|
+
[data-ui='menu'] [data-part='item']:focus {
|
|
470
|
+
background-color: var(--_highlight);
|
|
471
|
+
color: var(--_accent);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The ring only when the keyboard put focus there. A pointer-driven highlight
|
|
476
|
+
* is already unambiguous, and a ring under the pointer would draw a box the
|
|
477
|
+
* reader did not ask for. Inset, so it never overflows the surface's own
|
|
478
|
+
* rounded edge.
|
|
479
|
+
*/
|
|
480
|
+
[data-ui='menu'] [data-part='item']:focus-visible {
|
|
481
|
+
outline: var(--slotted-focus-ring-width, 2px) solid
|
|
482
|
+
var(--slotted-focus-ring-color, currentColor);
|
|
483
|
+
outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
[data-ui='menu'] [data-part='item']:not([data-disabled]):active {
|
|
487
|
+
background-color: var(--_highlight-active);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* A parent item stays highlighted while its submenu is open, so the path from
|
|
492
|
+
* the bar to the surface under the pointer reads as one line.
|
|
493
|
+
*/
|
|
494
|
+
[data-ui='menu'] [data-part='item'][data-open] {
|
|
495
|
+
background-color: var(--_highlight);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Disabled items are focusable and cannot be activated, which the pattern
|
|
500
|
+
* requires. They therefore have to answer focus with something, and answer it
|
|
501
|
+
* more quietly than an item that would do anything: the ground is the same
|
|
502
|
+
* highlight, so the reader still sees where they are, and the text keeps the
|
|
503
|
+
* disabled colour, so the item still reads as unavailable. The theme's
|
|
504
|
+
* disabled surface stood where the highlight stands now and measured ΔL 0.005
|
|
505
|
+
* against the dark surface and 0 against the high-contrast one: the pointer
|
|
506
|
+
* landed on an item and nothing answered.
|
|
507
|
+
*/
|
|
508
|
+
[data-ui='menu'] [data-part='item'][data-disabled] {
|
|
509
|
+
color: var(--slotted-disabled-foreground, #64748b);
|
|
510
|
+
cursor: not-allowed;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
[data-ui='menu'] [data-part='item'][data-disabled]:focus {
|
|
514
|
+
background-color: var(--_highlight);
|
|
515
|
+
color: var(--slotted-disabled-foreground, #64748b);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* ── The cells ────────────────────────────────────────────────────────── */
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Column one, and the reason a checkbox item and a command item line their
|
|
522
|
+
* labels up: the mark's box exists whether or not the mark is drawn, so a
|
|
523
|
+
* menu of options does not shift by sixteen pixels as one is checked.
|
|
524
|
+
*/
|
|
525
|
+
[data-ui='menu'] :is([data-part='indicator'], [data-part='icon']),
|
|
526
|
+
[data-ui='menu'] [data-part='item'][aria-haspopup='menu'] > [data-ui='icon'] {
|
|
527
|
+
/**
|
|
528
|
+
* One line box tall, so a mark beside a label that wrapped onto three
|
|
529
|
+
* lines sits on the first of them rather than in the middle of the item —
|
|
530
|
+
* and, because the item states the line box every cell inherits, exactly
|
|
531
|
+
* as tall as the line it sits beside. The mark is then centred on the text
|
|
532
|
+
* by construction rather than by a nudge that has to be retuned at every
|
|
533
|
+
* size and density.
|
|
534
|
+
*/
|
|
535
|
+
block-size: 1lh;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* The mark is the Glyph element the binding renders in the box — `check` for
|
|
540
|
+
* a checkbox item, `dot` for a radio item — and it is shown while the item
|
|
541
|
+
* is checked.
|
|
542
|
+
*/
|
|
543
|
+
[data-ui='menu'] [data-part='indicator'] {
|
|
544
|
+
--slotted-icon-size: var(--_icon-size);
|
|
545
|
+
|
|
546
|
+
align-items: center;
|
|
547
|
+
display: flex;
|
|
548
|
+
grid-column: 1;
|
|
549
|
+
grid-row: 1;
|
|
550
|
+
inline-size: var(--_icon-size);
|
|
551
|
+
justify-content: center;
|
|
552
|
+
justify-self: center;
|
|
553
|
+
margin-inline-end: var(--_column-gap);
|
|
554
|
+
opacity: 0;
|
|
555
|
+
/**
|
|
556
|
+
* The mark arrives and leaves: a fade with a small growth from the centre
|
|
557
|
+
* of its column. 0.8 rather than the 0.95 a panel starts from, because at
|
|
558
|
+
* sixteen pixels 0.95 moves the glyph's edge by under half a pixel and the
|
|
559
|
+
* growth is not seen; 0.8 moves it by 1.6. Leaving takes the exit curve.
|
|
560
|
+
*/
|
|
561
|
+
scale: 0.8;
|
|
562
|
+
transition:
|
|
563
|
+
opacity var(--slotted-duration-short) var(--slotted-easing-exit),
|
|
564
|
+
scale var(--slotted-duration-short) var(--slotted-easing-exit);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
[data-ui='menu'] [data-part='item'][data-checked] > [data-part='indicator'] {
|
|
568
|
+
opacity: 1;
|
|
569
|
+
scale: 1;
|
|
570
|
+
transition:
|
|
571
|
+
opacity var(--slotted-duration-short) var(--slotted-easing-enter),
|
|
572
|
+
scale var(--slotted-duration-short) var(--slotted-easing-enter);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/* The dot is drawn smaller than the check, at seven tenths of the column's glyph. */
|
|
576
|
+
[data-ui='menu'] [data-part='item'][role='menuitemradio'] > [data-part='indicator'] {
|
|
577
|
+
--slotted-icon-size: calc(var(--_icon-size) * 0.7);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* The same column the indicator uses, and never both on one item. The
|
|
582
|
+
* consumer supplies the icon and the family supplies the column, so a menu
|
|
583
|
+
* whose second and fifth items have icons still reads as one list rather
|
|
584
|
+
* than as two.
|
|
585
|
+
*
|
|
586
|
+
* The mark takes the item's own colour rather than the muted one. Painted
|
|
587
|
+
* muted, a disabled item's icon was the same grey as an enabled item's —
|
|
588
|
+
* `--slotted-text-muted` and `--slotted-disabled-foreground` are four points
|
|
589
|
+
* apart in the light scheme — so the state the item was in could be read from
|
|
590
|
+
* its label and not from its mark.
|
|
591
|
+
*
|
|
592
|
+
* What fills it is content: the library's own `Glyph` in an icon box, or an
|
|
593
|
+
* element of the consumer's own, centred. The stylesheet paints nothing
|
|
594
|
+
* here.
|
|
595
|
+
*
|
|
596
|
+
* The box sizes what it holds: an icon box reads `--slotted-icon-size` from
|
|
597
|
+
* its container, and a bare glyph host such as `ng-icon` — which sizes
|
|
598
|
+
* itself at `1em` — reads the font-size the child rule below gives it.
|
|
599
|
+
* Nothing placed here is drawn at the item's type size. The font-size is on
|
|
600
|
+
* the child and not on the box, because the box's `1lh` reads the item's
|
|
601
|
+
* line box.
|
|
602
|
+
*/
|
|
603
|
+
[data-ui='menu'] [data-part='icon'] {
|
|
604
|
+
--slotted-icon-size: var(--_icon-size);
|
|
605
|
+
|
|
606
|
+
align-items: center;
|
|
607
|
+
display: flex;
|
|
608
|
+
grid-column: 1;
|
|
609
|
+
grid-row: 1;
|
|
610
|
+
inline-size: var(--_icon-size);
|
|
611
|
+
justify-content: center;
|
|
612
|
+
justify-self: center;
|
|
613
|
+
margin-inline-end: var(--_column-gap);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
[data-ui='menu'] [data-part='icon'] > * {
|
|
617
|
+
font-size: var(--_icon-size);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/* Column two, and the only column that gives way. */
|
|
621
|
+
[data-ui='menu'] [data-part='label'] {
|
|
622
|
+
grid-column: 2;
|
|
623
|
+
grid-row: 1;
|
|
624
|
+
min-inline-size: 0;
|
|
625
|
+
overflow-wrap: anywhere;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
[data-ui='menu'] [data-part='description'] {
|
|
629
|
+
color: var(--_muted);
|
|
630
|
+
font-size: var(--_secondary-font-size);
|
|
631
|
+
grid-column: 2;
|
|
632
|
+
grid-row: 2;
|
|
633
|
+
line-height: var(--_secondary-leading);
|
|
634
|
+
min-inline-size: 0;
|
|
635
|
+
overflow-wrap: anywhere;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
[data-ui='menu'] [data-part='item'][data-disabled] > [data-part='description'] {
|
|
639
|
+
color: inherit;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Column three: what the item holds or counts — "12", a status, the value a
|
|
644
|
+
* submenu is set to. Its own column, so the metas of a menu line up with
|
|
645
|
+
* each other rather than each stopping wherever its label ended, which is
|
|
646
|
+
* where a `margin-inline-start: auto` badge lands. End-aligned in the
|
|
647
|
+
* column, so counts stack on their last digit and a word ends where the
|
|
648
|
+
* numbers end; tabular figures, muted like the shortcut, never wrapped.
|
|
649
|
+
*/
|
|
650
|
+
[data-ui='menu'] [data-part='meta'] {
|
|
651
|
+
align-items: center;
|
|
652
|
+
color: var(--_muted);
|
|
653
|
+
column-gap: var(--slotted-space-2, 0.25rem);
|
|
654
|
+
display: flex;
|
|
655
|
+
font-variant-numeric: tabular-nums;
|
|
656
|
+
grid-column: 3;
|
|
657
|
+
grid-row: 1;
|
|
658
|
+
justify-self: end;
|
|
659
|
+
margin-inline-start: var(--slotted-space-6, 1rem);
|
|
660
|
+
white-space: nowrap;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Column four. The shortcut is set in the same face as the label and one step
|
|
665
|
+
* quieter, so it reads as an annotation rather than as a second command; a
|
|
666
|
+
* `kbd` placed here instead paints its own keys and this rule leaves it
|
|
667
|
+
* alone. Never narrower than its own content: a long label wraps in column
|
|
668
|
+
* two rather than pushing the binding off the surface.
|
|
669
|
+
*/
|
|
670
|
+
[data-ui='menu'] [data-part='shortcut'] {
|
|
671
|
+
color: var(--_muted);
|
|
672
|
+
grid-column: 4;
|
|
673
|
+
grid-row: 1;
|
|
674
|
+
justify-self: end;
|
|
675
|
+
margin-inline-start: var(--slotted-space-6, 1rem);
|
|
676
|
+
white-space: nowrap;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
[data-ui='menu']
|
|
680
|
+
[data-part='item'][data-disabled]
|
|
681
|
+
> :is([data-part='shortcut'], [data-part='meta']),
|
|
682
|
+
[data-ui='menu'] [data-part='item'][data-disabled][aria-haspopup='menu'] > [data-ui='icon'] {
|
|
683
|
+
color: inherit;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* The mark that says an item opens a submenu: the `chevron-right` Glyph
|
|
688
|
+
* element the binding puts at the end of the item, in a `directional` box so
|
|
689
|
+
* a menu inside a mirrored region turns without an ancestor saying so. It
|
|
690
|
+
* takes the shortcut's column, because an item that opens a submenu runs no
|
|
691
|
+
* command and so carries no binding.
|
|
692
|
+
*/
|
|
693
|
+
[data-ui='menu'] [data-part='item'][aria-haspopup='menu'] > [data-ui='icon'] {
|
|
694
|
+
--slotted-icon-size: var(--_icon-size);
|
|
695
|
+
|
|
696
|
+
grid-column: 4;
|
|
697
|
+
grid-row: 1;
|
|
698
|
+
justify-self: end;
|
|
699
|
+
/* The box sets its own line-height to 1 for inline alignment, and `1lh`
|
|
700
|
+
above reads it: the item's line box is taken back, or the chevron is a
|
|
701
|
+
type size tall and sits above the label it annotates. */
|
|
702
|
+
line-height: inherit;
|
|
703
|
+
margin-inline-start: var(--_column-gap);
|
|
704
|
+
/* Quieter than the label by the same step as the shortcut it replaces. */
|
|
705
|
+
color: var(--_muted);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/* ── Separator, empty, loading ────────────────────────────────────────── */
|
|
709
|
+
|
|
710
|
+
[data-ui='menu'] [data-part='separator'] {
|
|
711
|
+
background-color: var(--slotted-menu-separator-color, var(--_border-color));
|
|
712
|
+
block-size: var(--_border-width);
|
|
713
|
+
border: 0;
|
|
714
|
+
grid-column: 1 / -1;
|
|
715
|
+
margin-block: var(--slotted-space-2, 0.25rem);
|
|
716
|
+
margin-inline: 0;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* A menu whose items were all filtered away, and a menu still waiting for
|
|
721
|
+
* them. Both keep the surface's measure rather than collapsing to a
|
|
722
|
+
* two-pixel box, which is what a consumer sees flash when the state is not
|
|
723
|
+
* painted at all.
|
|
724
|
+
*
|
|
725
|
+
* The measure they keep is the surface's own floor and no longer a second one
|
|
726
|
+
* stated here: a row that was wider than the menu around it made the menu
|
|
727
|
+
* grow when its list emptied (M3).
|
|
728
|
+
*/
|
|
729
|
+
[data-ui='menu'] [data-part='empty'] {
|
|
730
|
+
color: var(--_muted);
|
|
731
|
+
grid-column: 1 / -1;
|
|
732
|
+
padding-block: var(--slotted-space-5, 0.75rem);
|
|
733
|
+
padding-inline: var(--_item-inset);
|
|
734
|
+
text-align: center;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
[data-ui='menu'] > [data-part='content'][data-loading] {
|
|
738
|
+
cursor: progress;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/* ── Tones ────────────────────────────────────────────────────────────── */
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* A destructive command reads as destructive before it is read at all. The
|
|
745
|
+
* tone paints the label, the icon and the highlight together, from the five
|
|
746
|
+
* the theme already carries at the contrast every scheme needs; `danger` is
|
|
747
|
+
* the one a product reaches for, and the other four cost one line each.
|
|
748
|
+
*/
|
|
749
|
+
[data-ui='menu'] [data-part='item'][data-tone='accent'] {
|
|
750
|
+
--_accent: var(--slotted-tone-accent-text);
|
|
751
|
+
--_highlight: var(--slotted-tone-accent-surface);
|
|
752
|
+
--_highlight-active: var(--slotted-tone-accent-subtle-active);
|
|
753
|
+
|
|
754
|
+
color: var(--slotted-tone-accent-text);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
[data-ui='menu'] [data-part='item'][data-tone='secondary'] {
|
|
758
|
+
--_accent: var(--slotted-tone-secondary-text);
|
|
759
|
+
--_highlight: var(--slotted-tone-secondary-surface);
|
|
760
|
+
--_highlight-active: var(--slotted-tone-secondary-subtle-active);
|
|
761
|
+
|
|
762
|
+
color: var(--slotted-tone-secondary-text);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
[data-ui='menu'] [data-part='item'][data-tone='success'] {
|
|
766
|
+
--_accent: var(--slotted-tone-success-text);
|
|
767
|
+
--_highlight: var(--slotted-tone-success-surface);
|
|
768
|
+
--_highlight-active: var(--slotted-tone-success-subtle-active);
|
|
769
|
+
|
|
770
|
+
color: var(--slotted-tone-success-text);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
[data-ui='menu'] [data-part='item'][data-tone='warning'] {
|
|
774
|
+
--_accent: var(--slotted-tone-warning-text);
|
|
775
|
+
--_highlight: var(--slotted-tone-warning-surface);
|
|
776
|
+
--_highlight-active: var(--slotted-tone-warning-subtle-active);
|
|
777
|
+
|
|
778
|
+
color: var(--slotted-tone-warning-text);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
[data-ui='menu'] [data-part='item'][data-tone='danger'] {
|
|
782
|
+
--_accent: var(--slotted-tone-danger-text);
|
|
783
|
+
--_highlight: var(--slotted-tone-danger-surface);
|
|
784
|
+
--_highlight-active: var(--slotted-tone-danger-subtle-active);
|
|
785
|
+
|
|
786
|
+
color: var(--slotted-tone-danger-text);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/* An unavailable item keeps no tone: its highlight is the neutral one. */
|
|
790
|
+
[data-ui='menu'] [data-part='item'][data-tone][data-disabled] {
|
|
791
|
+
--_accent: var(--slotted-disabled-foreground, #64748b);
|
|
792
|
+
--_highlight: var(--slotted-menu-highlight, var(--slotted-tone-secondary-subtle-hover));
|
|
793
|
+
|
|
794
|
+
color: var(--slotted-disabled-foreground, #64748b);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/* ── Forced colours ───────────────────────────────────────────────────── */
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* The one thing the theme cannot reach. Every tone surface becomes `Canvas`
|
|
801
|
+
* under forced colours, so the highlight — which is this family's selection
|
|
802
|
+
* indicator, not decoration — would disappear and leave a reader with no way
|
|
803
|
+
* to tell which command they are on.
|
|
804
|
+
*/
|
|
805
|
+
@media (forced-colors: active) {
|
|
806
|
+
[data-ui='menu'] [data-part='item']:focus {
|
|
807
|
+
background-color: Highlight;
|
|
808
|
+
color: HighlightText;
|
|
809
|
+
forced-color-adjust: none;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
[data-ui='menu'] [data-part='item'][data-disabled]:focus {
|
|
813
|
+
background-color: Highlight;
|
|
814
|
+
color: GrayText;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|