Draw the menu without Tailwind
<x-menu> renders data-md-menu with data-md-sheet-at-compact, and its popover data-md-menu-popover with data-md-position and data-md-vibrant; menu.css caps a long menu at 288px so it scrolls instead of running off the top layer (ACT-04) and ties the open/close transition to the trigger's corner (ACT-26), restyles a submenu to open in place inside a sheet-at-compact sheet, and still carries the dropdown a select's picker and a searchable choices field draw, so select.css and choices.css finish their step-36 TODO import. actions.css sheds the state-transition-fast and popover-transition utilities menu-item and menu no longer use; state-transition-default and [data-fab-menu] stay for fab-menu's own rewrite. data-menu, data-menu-filter, data-menu-sheet, data-menu-empty, data-sheet-at-compact, data-vibrant and data-active are renamed to data-md-*, also in menu.js, split-button.css (which renders a <x-menu> and now imports its stylesheet) and AppBarTest's overflow menu (plan step 36, actions). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
c040925cd4
commit
5cdeec26e8
+382
-272
@@ -1,318 +1,428 @@
|
||||
/*
|
||||
* The menus: the exposed dropdown a field drops open, and what `<x-menu>` cannot say in utilities.
|
||||
* The menus: `<x-menu>` and what a form's own dropdowns wear too, so a menu and a form read as
|
||||
* one family.
|
||||
*
|
||||
* Two lists wear the dropdown, so a form reads as one family:
|
||||
* Two lists wear the dropdown:
|
||||
*
|
||||
* - `<x-menu>`'s own popover (`[data-md-menu-popover]`) and, inside it, a submenu
|
||||
* (`[data-md-submenu]`, menu-item.css).
|
||||
* - `<x-select>`'s: the native <select>, opted into the browser's customizable select
|
||||
* (`appearance: base-select`). `::picker(select)` is the menu and each <option> a row, while the
|
||||
* keyboard, type-to-find, the screen reader and the form value stay the browser's own.
|
||||
* - `<x-choices searchable>`'s listbox, which Alpine draws, as `[data-md-field-menu]` and `[data-md-field-option]`.
|
||||
* (`appearance: base-select`). `::picker(select)` is the menu and each <option> a row, while
|
||||
* the keyboard, type-to-find, the screen reader and the form value stay the browser's own.
|
||||
* - `<x-choices searchable>`'s listbox, which Alpine draws, as `[data-md-field-menu]` and
|
||||
* `[data-md-field-option]`.
|
||||
*
|
||||
* M3's menu as its tokens have it: surface-container at elevation 2, extra-small corner, 48px rows in
|
||||
* body-large, the chosen row in secondary-container with a tick at its end (so it is not told by colour
|
||||
* alone), and the state layer on hover (only where a pointer can hover) and focus.
|
||||
* M3's menu as its tokens have it (StandardMenuTokens/VibrantMenuTokens/SegmentedMenuTokens,
|
||||
* androidx Compose Material 3, Apache-2.0): surface-container-low at elevation 2, large corner,
|
||||
* 48px rows in body-large, the chosen row in secondary-container with a tick at its end (so it is
|
||||
* not told by colour alone), and the state layer on hover (only where a pointer can hover) and
|
||||
* focus.
|
||||
*
|
||||
* Unlayered, as the rest of the package's component CSS, so a utility on the call site cannot half-undo
|
||||
* a row.
|
||||
* `[data-md-menu]` is `<x-menu>`'s root, holding the trigger and the popover; the popover itself
|
||||
* is `[data-md-menu-popover]`, capped at 18rem so a long menu scrolls instead of running off the
|
||||
* top layer's edge (ACT-04), and transitions in by growing from its trigger's corner while it
|
||||
* fades, on the spatial and effects springs respectively (ACT-26). `data-md-vibrant` swaps its
|
||||
* container for tertiary-container; a nested submenu inherits the colour through
|
||||
* `--material-menu-surface`/`--material-menu-ink`, which the sheet's own copy of the list falls
|
||||
* back to instead (it is not a descendant of the popover).
|
||||
*
|
||||
* `sheet-at-compact`'s bottom sheet is `<x-bottom-sheet>` (still Tailwind): `[data-md-menu-sheet]`
|
||||
* cancels its 24px padding and restyles a submenu to open in place under its item instead of
|
||||
* beside it (M3 calls submenus "best suited to large screens").
|
||||
*
|
||||
* Where the browser has no customizable select, the select keeps its native list, which
|
||||
* `color-scheme` themes. Every rule for the select's own rows is inside `@supports`: a browser that
|
||||
* paints option colours into its native list would otherwise show a half-painted one.
|
||||
* `color-scheme` themes. Every rule for the select's own rows is inside `@supports`: a browser
|
||||
* that paints option colours into its native list would otherwise show a half-painted one.
|
||||
*/
|
||||
|
||||
/*
|
||||
* `<x-menu>` and the submenus inside it.
|
||||
*
|
||||
* A submenu is a popover of its own nested in the menu's, so it cannot take the container colour
|
||||
* from a class the item knows nothing about: the menu declares the pair as custom properties,
|
||||
* which a nested popover inherits down the DOM whatever the top layer does with its painting. The
|
||||
* fallbacks are the standard menu's, for a submenu in some other list (a FAB menu's).
|
||||
*/
|
||||
[data-menu] {
|
||||
--material-menu-surface: var(--md-sys-color-surface-container-low);
|
||||
--material-menu-ink: var(--md-sys-color-on-surface);
|
||||
}
|
||||
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||||
|
||||
[data-menu][data-vibrant] {
|
||||
--material-menu-surface: var(--md-sys-color-tertiary-container);
|
||||
--material-menu-ink: var(--md-sys-color-on-tertiary-container);
|
||||
}
|
||||
@import './icon.css';
|
||||
|
||||
[data-submenu] {
|
||||
background: var(--material-menu-surface, var(--md-sys-color-surface-container-low));
|
||||
color: var(--material-menu-ink, var(--md-sys-color-on-surface));
|
||||
}
|
||||
|
||||
/*
|
||||
* `<x-menu filter>`: M3's menu as a filtering surface. The field stays put while the list scrolls
|
||||
* under it and takes the menu's own container, so a vibrant menu's field is vibrant too. It is a
|
||||
* plain <input> with no field chrome — M3's menus embed a text field, not a text field component.
|
||||
*/
|
||||
[data-menu-filter] {
|
||||
position: sticky;
|
||||
inset-block-start: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
min-block-size: 3rem;
|
||||
padding-inline: 1rem;
|
||||
border-block-end: 1px solid var(--md-sys-color-outline-variant);
|
||||
background: var(--material-menu-surface, var(--md-sys-color-surface-container-low));
|
||||
}
|
||||
|
||||
[data-menu-filter] input {
|
||||
flex: 1;
|
||||
min-inline-size: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font: var(--md-sys-typescale-body-lg);
|
||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
[data-menu-filter] input::placeholder {
|
||||
color: color-mix(in srgb, var(--material-menu-ink, var(--md-sys-color-on-surface)) 70%, transparent);
|
||||
}
|
||||
|
||||
/* The filter hides what the query leaves out with the `hidden` attribute; a row's own
|
||||
`display: flex` utility would otherwise beat the user agent's `[hidden] { display: none }`. */
|
||||
:is([data-menu], [data-menu-sheet]) [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* `data-active` is the row the arrow keys are on while the focus stays in the field — the state
|
||||
layer the roving focus would have drawn, without taking the focus off the field. A chosen or
|
||||
current row keeps its own colour. */
|
||||
:is([data-menu], [data-menu-sheet]) [role^="menuitem"][data-active]:not([aria-checked="true"], [aria-current="page"]) {
|
||||
background: color-mix(in srgb, var(--material-menu-ink, var(--md-sys-color-on-surface)) 8%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* `<x-menu sheet-at-compact>`: the copy of the list in the bottom sheet a compact window opens
|
||||
* instead of the popover. The sheet is surface-container-low, as the standard menu is, so the
|
||||
* fallbacks above already paint its field; the field reaches both edges of the sheet and holds
|
||||
* its icon where the rows hold theirs, 24px in (the list is 8px in, a row's own padding 16px).
|
||||
*/
|
||||
[data-menu-sheet] [data-menu-filter] {
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* A submenu there opens in place: its list stands under the item, inset by 16px, instead of in a
|
||||
* popover beside it, which a sheet has no room for (M3 calls submenus "best suited to large
|
||||
* screens", docs/reference/m3/components-actions-communication-containment.md § Menus). The
|
||||
* markup is the popover's, never shown as one: the user agent's `[popover]` box is undone, the
|
||||
* list is shown while its item says `aria-expanded="true"`, and it fades in on the effects track.
|
||||
*/
|
||||
[data-menu-sheet] [data-submenu] {
|
||||
position: static;
|
||||
inset: auto;
|
||||
inline-size: auto;
|
||||
max-inline-size: none;
|
||||
block-size: auto;
|
||||
max-block-size: none;
|
||||
margin: 0;
|
||||
padding-block: 0.125rem;
|
||||
padding-inline: 1rem 0;
|
||||
overflow: visible;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
box-shadow: none;
|
||||
opacity: 1;
|
||||
scale: none;
|
||||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
|
||||
[data-menu-sheet] [aria-expanded="true"] + [data-submenu] {
|
||||
display: block;
|
||||
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* The chevron turns from the way a popover would open to the way this list did: down, which is a
|
||||
quarter turn the other way on the chevron `rtl:-scale-x-100` mirrors — so the same RTL test. */
|
||||
[data-menu-sheet] [data-submenu-chevron] {
|
||||
transition: rotate var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);
|
||||
}
|
||||
|
||||
[data-menu-sheet] [aria-expanded="true"] > [data-submenu-chevron] {
|
||||
rotate: 90deg;
|
||||
|
||||
&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) {
|
||||
rotate: -90deg;
|
||||
}
|
||||
}
|
||||
|
||||
[data-md-field-menu] {
|
||||
max-block-size: 18rem;
|
||||
overflow-y: auto;
|
||||
padding-block: 0.5rem;
|
||||
border-radius: var(--md-sys-shape-corner-xs);
|
||||
background: var(--md-sys-color-surface-container);
|
||||
box-shadow: var(--md-sys-elevation-2);
|
||||
}
|
||||
|
||||
/* As a popover (`<x-choices searchable>`), it hangs under its field, as wide, and goes above only
|
||||
when there is no room below. */
|
||||
[data-md-field-menu][popover] {
|
||||
inset: auto;
|
||||
top: anchor(bottom);
|
||||
left: anchor(left);
|
||||
width: anchor-size(width);
|
||||
margin: 0.25rem 0;
|
||||
border: 0;
|
||||
padding-inline: 0;
|
||||
color: var(--md-sys-color-on-surface);
|
||||
position-try-fallbacks: flip-block;
|
||||
}
|
||||
|
||||
[data-md-field-option][aria-disabled="true"] {
|
||||
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-md-field-option] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
min-block-size: 3rem;
|
||||
padding-inline: 1rem;
|
||||
color: var(--md-sys-color-on-surface);
|
||||
font: var(--md-sys-typescale-body-lg);
|
||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* `data-active` is the row the arrow keys or the pointer are on. */
|
||||
[data-md-field-option][data-active] {
|
||||
background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
|
||||
}
|
||||
|
||||
[data-md-field-option][aria-selected="true"] {
|
||||
background: var(--md-sys-color-secondary-container);
|
||||
color: var(--md-sys-color-on-secondary-container);
|
||||
}
|
||||
|
||||
[data-md-field-option][aria-selected="true"][data-active] {
|
||||
background: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 8%, var(--md-sys-color-secondary-container));
|
||||
}
|
||||
|
||||
[data-md-field-check] {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
[data-md-field-option]:not([aria-selected="true"]) [data-md-field-check] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@supports (appearance: base-select) {
|
||||
select[data-md-field-control],
|
||||
select[data-md-field-control]::picker(select) {
|
||||
appearance: base-select;
|
||||
@layer material.components {
|
||||
[data-md-menu] {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* The menu. The browser anchors it to the select; the select covers its whole
|
||||
field (field.css), so the menu is at least the field's width. It hangs under
|
||||
the field and goes above only when it does not fit there — the browser's own
|
||||
order tries the roomier side first, which opened a menu upwards with room to
|
||||
spare under it. It opens with a fade and a grow on the spatial spring and
|
||||
closes with a fade, as `<x-menu>` does. */
|
||||
select[data-md-field-control]::picker(select) {
|
||||
position-try-order: normal;
|
||||
max-block-size: 18rem;
|
||||
max-inline-size: calc(100vw - 2rem);
|
||||
margin-block: 0.25rem;
|
||||
padding-block: 0.5rem;
|
||||
border: 0;
|
||||
border-radius: var(--md-sys-shape-corner-xs);
|
||||
background: var(--md-sys-color-surface-container);
|
||||
color: var(--md-sys-color-on-surface);
|
||||
[data-md-menu-trigger] {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
[data-md-menu-popover] {
|
||||
--material-menu-surface: var(--md-sys-color-surface-container-low);
|
||||
--material-menu-ink: var(--md-sys-color-on-surface);
|
||||
|
||||
inset: auto;
|
||||
margin: 0;
|
||||
margin-block: var(--md-sys-measurement-space50);
|
||||
min-inline-size: 112px;
|
||||
max-inline-size: 280px;
|
||||
max-block-size: min(288px, calc(100dvh - 32px));
|
||||
overflow-y: auto;
|
||||
border-width: 0;
|
||||
border-radius: var(--md-sys-shape-corner-lg);
|
||||
padding: var(--md-sys-measurement-space50);
|
||||
background-color: var(--material-menu-surface);
|
||||
color: var(--material-menu-ink);
|
||||
box-shadow: var(--md-sys-elevation-2);
|
||||
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
||||
opacity: 0;
|
||||
transform-origin: top;
|
||||
transition-property: opacity, display, overlay;
|
||||
transition-duration: var(--md-sys-motion-effects-fast-duration);
|
||||
transition-timing-function: var(--md-sys-motion-effects-fast);
|
||||
transition-behavior: allow-discrete;
|
||||
}
|
||||
|
||||
select[data-md-field-control]:open::picker(select) {
|
||||
opacity: 1;
|
||||
scale: 0.95;
|
||||
transition-property: opacity, scale, display, overlay;
|
||||
transition-duration: var(--md-sys-motion-spatial-fast-duration);
|
||||
transition-timing-function: var(--md-sys-motion-spatial-fast);
|
||||
}
|
||||
transition-duration:
|
||||
var(--md-sys-motion-effects-fast-duration), var(--md-sys-motion-spatial-fast-duration),
|
||||
var(--md-sys-motion-spatial-fast-duration), var(--md-sys-motion-spatial-fast-duration);
|
||||
transition-timing-function:
|
||||
var(--md-sys-motion-effects-fast), var(--md-sys-motion-spatial-fast), linear, linear;
|
||||
transition-behavior: allow-discrete;
|
||||
|
||||
@starting-style {
|
||||
select[data-md-field-control]:open::picker(select) {
|
||||
opacity: 0;
|
||||
scale: 0.95;
|
||||
&:popover-open {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
&:popover-open {
|
||||
opacity: 0;
|
||||
scale: 0.95;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select[data-md-field-control] option {
|
||||
[data-md-menu-popover]:has(> [data-md-menu-filter]) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[data-md-menu-popover][data-md-vibrant] {
|
||||
--material-menu-surface: var(--md-sys-color-tertiary-container);
|
||||
--material-menu-ink: var(--md-sys-color-on-tertiary-container);
|
||||
}
|
||||
|
||||
[data-md-menu-popover][data-md-position='bottom-start'] {
|
||||
transform-origin: top;
|
||||
position-area: bottom span-right;
|
||||
}
|
||||
|
||||
[data-md-menu-popover][data-md-position='bottom-end'] {
|
||||
transform-origin: top;
|
||||
position-area: bottom span-left;
|
||||
}
|
||||
|
||||
[data-md-menu-popover][data-md-position='top-start'] {
|
||||
transform-origin: bottom;
|
||||
position-area: top span-right;
|
||||
}
|
||||
|
||||
[data-md-menu-popover][data-md-position='top-end'] {
|
||||
transform-origin: bottom;
|
||||
position-area: top span-left;
|
||||
}
|
||||
|
||||
/*
|
||||
* `<x-menu filter>`: M3's menu as a filtering surface. The field stays put while the list
|
||||
* scrolls under it and takes the menu's own container, so a vibrant menu's field is vibrant
|
||||
* too. It is a plain <input> with no field chrome — M3's menus embed a text field, not a text
|
||||
* field component.
|
||||
*/
|
||||
[data-md-menu-filter] {
|
||||
position: sticky;
|
||||
inset-block-start: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
min-block-size: 3rem;
|
||||
padding-block: 0;
|
||||
padding-inline: 1rem;
|
||||
background: transparent;
|
||||
color: var(--md-sys-color-on-surface);
|
||||
gap: 12px;
|
||||
min-block-size: var(--md-sys-measurement-space600);
|
||||
padding-inline: var(--md-sys-measurement-space200);
|
||||
border-block-end: 1px solid var(--md-sys-color-outline-variant);
|
||||
background-color: var(--material-menu-surface, var(--md-sys-color-surface-container-low));
|
||||
}
|
||||
|
||||
[data-md-menu-filter] input {
|
||||
flex: 1;
|
||||
min-inline-size: 0;
|
||||
border-width: 0;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font: var(--md-sys-typescale-body-lg);
|
||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
select[data-md-field-control] option:hover {
|
||||
background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
|
||||
[data-md-menu-filter] input::placeholder {
|
||||
color: color-mix(in srgb, var(--material-menu-ink, var(--md-sys-color-on-surface)) 70%, transparent);
|
||||
}
|
||||
|
||||
[data-md-menu-list] {
|
||||
padding: var(--md-sys-measurement-space50);
|
||||
}
|
||||
|
||||
[data-md-menu-empty] {
|
||||
padding-inline: var(--md-sys-measurement-space200);
|
||||
padding-block: 12px;
|
||||
color: var(--md-sys-color-on-surface-variant);
|
||||
font: var(--md-sys-typescale-body-md);
|
||||
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
||||
font-variation-settings: normal;
|
||||
}
|
||||
|
||||
/* The filter hides what the query leaves out with the `hidden` attribute; a row's own
|
||||
`display: flex` would otherwise beat the user agent's `[hidden] { display: none }`. */
|
||||
:is([data-md-menu-popover], [data-md-menu-sheet]) [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* `data-md-active` is the row the arrow keys are on while the focus stays in the field — the
|
||||
state layer the roving focus would have drawn, without taking the focus off the field. A
|
||||
chosen or current row keeps its own colour. */
|
||||
:is([data-md-menu-popover], [data-md-menu-sheet]) [role^="menuitem"][data-md-active]:not([aria-checked="true"], [aria-current="page"]) {
|
||||
background-color: color-mix(in srgb, var(--material-menu-ink, var(--md-sys-color-on-surface)) 8%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* `<x-menu sheet-at-compact>`: the copy of the list in the bottom sheet a compact window opens
|
||||
* instead of the popover. The sheet is surface-container-low, as the standard menu is, so the
|
||||
* fallbacks above already paint its field; the field reaches both edges of the sheet and holds
|
||||
* its icon where the rows hold theirs, 24px in (the list is 8px in, a row's own padding 16px).
|
||||
* `-24px` cancels the bottom sheet's own padding.
|
||||
*/
|
||||
[data-md-menu-sheet] {
|
||||
margin-inline: calc(-1 * var(--md-sys-measurement-space300));
|
||||
padding-inline: var(--md-sys-measurement-space100);
|
||||
}
|
||||
|
||||
[data-md-menu-sheet]:has(> [data-md-menu-filter]) {
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
[data-md-menu-sheet] [data-md-menu-filter] {
|
||||
padding-inline: var(--md-sys-measurement-space300);
|
||||
}
|
||||
|
||||
[data-md-menu-sheet] [data-md-menu-list] {
|
||||
padding-inline: var(--md-sys-measurement-space100);
|
||||
padding-block: var(--md-sys-measurement-space50) 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* A submenu there opens in place: its list stands under the item, inset by 16px, instead of in
|
||||
* a popover beside it, which a sheet has no room for (M3 calls submenus "best suited to large
|
||||
* screens", docs/reference/m3/components-actions-communication-containment.md § Menus). The
|
||||
* markup is the popover's, never shown as one: the user agent's `[popover]` box is undone, the
|
||||
* list is shown while its item says `aria-expanded="true"`, and it fades in on the effects
|
||||
* track.
|
||||
*/
|
||||
[data-md-menu-sheet] [data-md-submenu] {
|
||||
position: static;
|
||||
inset: auto;
|
||||
inline-size: auto;
|
||||
max-inline-size: none;
|
||||
block-size: auto;
|
||||
max-block-size: none;
|
||||
margin: 0;
|
||||
padding-block: var(--md-sys-measurement-space25);
|
||||
padding-inline: var(--md-sys-measurement-space200) 0;
|
||||
overflow: visible;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
box-shadow: none;
|
||||
opacity: 1;
|
||||
scale: none;
|
||||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
|
||||
[data-md-menu-sheet] [aria-expanded="true"] + [data-md-submenu] {
|
||||
display: block;
|
||||
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
select[data-md-field-control] option:focus-visible {
|
||||
background: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);
|
||||
/* The chevron turns from the way a popover would open to the way this list did: down, which
|
||||
is a quarter turn the other way on the chevron `mirror-rtl` mirrors too. */
|
||||
[data-md-menu-sheet] [data-md-submenu-chevron] {
|
||||
transition: rotate var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);
|
||||
}
|
||||
|
||||
select[data-md-field-control] option:checked {
|
||||
background: var(--md-sys-color-secondary-container);
|
||||
color: var(--md-sys-color-on-secondary-container);
|
||||
}
|
||||
[data-md-menu-sheet] [aria-expanded="true"] > [data-md-submenu-chevron] {
|
||||
rotate: 90deg;
|
||||
|
||||
@media (hover: hover) {
|
||||
select[data-md-field-control] option:checked:hover {
|
||||
background: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 8%, var(--md-sys-color-secondary-container));
|
||||
&:dir(rtl) {
|
||||
rotate: -90deg;
|
||||
}
|
||||
}
|
||||
|
||||
select[data-md-field-control] option:checked:focus-visible {
|
||||
background: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 10%, var(--md-sys-color-secondary-container));
|
||||
[data-md-field-menu] {
|
||||
max-block-size: 18rem;
|
||||
overflow-y: auto;
|
||||
padding-block: var(--md-sys-measurement-space100);
|
||||
border-radius: var(--md-sys-shape-corner-xs);
|
||||
background-color: var(--md-sys-color-surface-container);
|
||||
box-shadow: var(--md-sys-elevation-2);
|
||||
}
|
||||
|
||||
select[data-md-field-control] option:disabled {
|
||||
background: transparent;
|
||||
/* As a popover (`<x-choices searchable>`), it hangs under its field, as wide, and goes above
|
||||
only when there is no room below. */
|
||||
[data-md-field-menu][popover] {
|
||||
inset: auto;
|
||||
top: anchor(bottom);
|
||||
left: anchor(left);
|
||||
width: anchor-size(width);
|
||||
margin: var(--md-sys-measurement-space50) 0;
|
||||
border-width: 0;
|
||||
padding-inline: 0;
|
||||
color: var(--md-sys-color-on-surface);
|
||||
position-try-fallbacks: flip-block;
|
||||
}
|
||||
|
||||
[data-md-field-option][aria-disabled="true"] {
|
||||
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* The tick, at the row's end: the browser draws it before the label, and hides
|
||||
it on every row but the chosen one. The Material Symbol, as a mask, so it
|
||||
takes the row's ink. */
|
||||
select[data-md-field-control] option::checkmark {
|
||||
content: "";
|
||||
order: 1;
|
||||
flex: none;
|
||||
inline-size: 1.5rem;
|
||||
block-size: 1.5rem;
|
||||
[data-md-field-option] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-block-size: var(--md-sys-measurement-space600);
|
||||
padding-inline: var(--md-sys-measurement-space200);
|
||||
color: var(--md-sys-color-on-surface);
|
||||
font: var(--md-sys-typescale-body-lg);
|
||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* `data-active` is the row the arrow keys or the pointer are on. */
|
||||
[data-md-field-option][data-active] {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
|
||||
}
|
||||
|
||||
[data-md-field-option][aria-selected="true"] {
|
||||
background-color: var(--md-sys-color-secondary-container);
|
||||
color: var(--md-sys-color-on-secondary-container);
|
||||
}
|
||||
|
||||
[data-md-field-option][aria-selected="true"][data-active] {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 8%, var(--md-sys-color-secondary-container));
|
||||
}
|
||||
|
||||
[data-md-field-check] {
|
||||
margin-inline-start: auto;
|
||||
background-color: currentColor;
|
||||
mask: url("../../svg/symbols/outlined/check.svg") center / contain no-repeat;
|
||||
}
|
||||
|
||||
[data-md-field-option]:not([aria-selected="true"]) [data-md-field-check] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@supports (appearance: base-select) {
|
||||
select[data-md-field-control],
|
||||
select[data-md-field-control]::picker(select) {
|
||||
appearance: base-select;
|
||||
}
|
||||
|
||||
/* The menu. The browser anchors it to the select; the select covers its whole
|
||||
field (field.css), so the menu is at least the field's width. It hangs under
|
||||
the field and goes above only when it does not fit there — the browser's own
|
||||
order tries the roomier side first, which opened a menu upwards with room to
|
||||
spare under it. It opens with a fade and a grow on the spatial spring and
|
||||
closes with a fade, as `<x-menu>` does. */
|
||||
select[data-md-field-control]::picker(select) {
|
||||
position-try-order: normal;
|
||||
max-block-size: 18rem;
|
||||
max-inline-size: calc(100vw - 32px);
|
||||
margin-block: var(--md-sys-measurement-space50);
|
||||
padding-block: var(--md-sys-measurement-space100);
|
||||
border-width: 0;
|
||||
border-radius: var(--md-sys-shape-corner-xs);
|
||||
background-color: var(--md-sys-color-surface-container);
|
||||
color: var(--md-sys-color-on-surface);
|
||||
box-shadow: var(--md-sys-elevation-2);
|
||||
opacity: 0;
|
||||
transform-origin: top;
|
||||
transition-property: opacity, display, overlay;
|
||||
transition-duration: var(--md-sys-motion-effects-fast-duration);
|
||||
transition-timing-function: var(--md-sys-motion-effects-fast);
|
||||
transition-behavior: allow-discrete;
|
||||
}
|
||||
|
||||
select[data-md-field-control]:open::picker(select) {
|
||||
opacity: 1;
|
||||
transition-property: opacity, scale, display, overlay;
|
||||
transition-duration: var(--md-sys-motion-spatial-fast-duration);
|
||||
transition-timing-function: var(--md-sys-motion-spatial-fast);
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
select[data-md-field-control]:open::picker(select) {
|
||||
opacity: 0;
|
||||
scale: 0.95;
|
||||
}
|
||||
}
|
||||
|
||||
select[data-md-field-control] option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-block-size: var(--md-sys-measurement-space600);
|
||||
padding-block: 0;
|
||||
padding-inline: var(--md-sys-measurement-space200);
|
||||
background-color: transparent;
|
||||
color: var(--md-sys-color-on-surface);
|
||||
font: var(--md-sys-typescale-body-lg);
|
||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||
font-variation-settings: normal;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
select[data-md-field-control] option:hover {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
select[data-md-field-control] option:focus-visible {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);
|
||||
}
|
||||
|
||||
select[data-md-field-control] option:checked {
|
||||
background-color: var(--md-sys-color-secondary-container);
|
||||
color: var(--md-sys-color-on-secondary-container);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
select[data-md-field-control] option:checked:hover {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 8%, var(--md-sys-color-secondary-container));
|
||||
}
|
||||
}
|
||||
|
||||
select[data-md-field-control] option:checked:focus-visible {
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 10%, var(--md-sys-color-secondary-container));
|
||||
}
|
||||
|
||||
select[data-md-field-control] option:disabled {
|
||||
background-color: transparent;
|
||||
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* The tick, at the row's end: the browser draws it before the label, and hides
|
||||
it on every row but the chosen one. The Material Symbol, as a mask, so it
|
||||
takes the row's ink. */
|
||||
select[data-md-field-control] option::checkmark {
|
||||
content: "";
|
||||
order: 1;
|
||||
flex: none;
|
||||
inline-size: var(--md-sys-measurement-space300);
|
||||
block-size: var(--md-sys-measurement-space300);
|
||||
margin-inline-start: auto;
|
||||
background-color: currentColor;
|
||||
mask: url("../../svg/symbols/outlined/check.svg") center / contain no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user