Files
livewire-material/resources/css/components/drawer.css
T
Andreas Reinhold / reiniandClaude Opus 5 99c18e01d8 Give a closing standard side sheet's room back as it leaves
From `expanded` a standard side sheet sits in the layout beside the
content, and the view took it out of the layout the moment it closed:
`data-md-drawer-collapsed` (`display: none` on the root) followed `! open`
at once, so its fade was never seen and the content beside it took the
sheet's width, and the gap, in one jump — in every engine. M3's side
sheets: opening a standard sheet shrinks the body beside it, and closing
gives the room back.

drawer.css: the standard root is a clipping flex box whose `inline-size`
springs between none and the sheet's width, with a negative margin as
wide as its flex parent's gap (`--md-drawer-gap`), on the sheet's own
exit and entry timings; the sheet keeps its width at the root's far edge,
so it is uncovered from its inner edge while it fades. drawer.blade.php:
`settle()`, from `x-effect`, measures the gap on each change and holds
the collapse (`closing`) for the root's and the sheet's closing
durations, read a frame on; reopening lets a pending end go by. The
binding reads `collapsed`, which only `settle()` and the window's width
write: a binding reading `open` ran before `settle()` in the same flush
and, already queued, did not run again once `closing` changed.

ContainmentTest closes the showcase's sheet at 1000px and samples, in the
page, for the root part-way to none while the column beside it has grown
part of the way, then checks the collapse, the column at the row's full
width, and a reopen part-way through the exit ending fully open; it fails
on the previous code in Chrome, Firefox and Safari. OverlayTest pins the
root's declarations and the view's bindings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 21:00:23 +02:00

294 lines
12 KiB
CSS

/*
* `<x-drawer>`: M3's side sheet, modal by default and co-planar as `standard` from 840px
* (docs/reference/m3/components-actions-communication-containment.md § Side sheets → Specs; no
* androidx Compose token file exists for it — Compose support is "Unavailable" per the site — so
* the numbers come from the specs page and, for the standard variant's 0dp elevation,
* material-components-android's `SideSheet.md`).
*
* [data-md-drawer] the wrapper; data-md-open, data-md-standard
* [data-md-drawer-scrim] modal only (a standard sheet's is hidden from 840px)
* [data-md-drawer-sheet] surface-container-low, elevation 1; data-md-side
* [data-md-drawer-head] title/subtitle/close, and the separator's divider
* [data-md-drawer-head-row], [data-md-drawer-heading], [data-md-drawer-title],
* [data-md-drawer-subtitle], [data-md-drawer-close]
* [data-md-drawer-body] a size container, so its contents lay out by the sheet's own
* width (`@md:`), never the viewport's
* [data-md-drawer-actions] left-aligned, 72dp (C-10 — a dialog's are trailing-aligned)
*
* 400px cap, 24dp start/end padding, a large corner on the inner edge only — from the specs
* table. It enters on the emphasized-decelerate easing rather than a spring, because a sheet
* anchored to an edge that overshot would open a gap, and leaves on emphasized accelerate: the
* view keeps `x-show`, so `@starting-style` gives the entry its start and the view's `x-transition`
* holds `display` through the exit, whose timing comes from the closed state's own rule (a
* transition takes the after-change style's). Not `allow-discrete` on `display`: Firefox does not
* transition `display`, and in the other engines it would hold a second time after Alpine's hold.
* Alpine holds for the first `transition-duration` listed, so each exit's longest transition is
* listed first. Reduced motion zeroes both duration tokens (tokens/motion.css).
* Below `expanded` it slides, from whichever edge `data-md-side` names in the document's direction;
* once `standard` makes it co-planar it only fades (an in-flow box does not need to travel). Every
* state rule matches the sheet and scrim as the root's children, so a sheet nested in an open one
* keeps its own.
*
* A `standard` sheet drops the scrim, the trap and the elevation, and swaps surface-container-low
* for `surface` with an outline-variant rule down its inner edge, in place of the scrim
* (its anatomy's "Divider (optional)"). Below `expanded` (840px) it is the modal sheet — M3 caps
* a side sheet at 400dp, and a 600px window has too little room left beside one, so the switch
* sits at `expanded` rather than `medium`. Opening it shrinks the body beside it and closing it
* gives the room back (M3's side sheets, "Adaptive"): the root's `inline-size` springs from none to
* the sheet's width — the sheet itself keeps its width, sits at the root's far edge and is clipped,
* so it is uncovered from its inner edge — together with a negative margin as wide as the flex
* parent's gap (`--md-drawer-gap`, which the view measures), so the content beside does not jump by
* the gap when the root leaves the layout. `data-md-drawer-collapsed` (the view, while the window is
* ≥ 840px and the sheet is closed) then takes the root out of the layout entirely, but only once the
* exit has run: the view's `closing` holds it, where it once cut the fade and the resize off on
* their first frame.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './button.css';
@import './divider.css';
@layer material.components {
/* The scrim fades in from `@starting-style` and back out while the view's `x-transition` holds
its `display` for this duration. Not `allow-discrete` on `display`: Firefox does not transition
`display`, so the scrim vanished there, and in the other engines it would hold a second time
after Alpine's hold. */
[data-md-drawer-scrim] {
position: fixed;
inset: 0;
z-index: 40;
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
opacity: 0;
transition-property: opacity;
transition-duration: var(--md-sys-motion-effects-default-duration);
transition-timing-function: var(--md-sys-motion-effects-default);
}
[data-md-drawer][data-md-open] > [data-md-drawer-scrim] {
opacity: 1;
}
@starting-style {
[data-md-drawer][data-md-open] > [data-md-drawer-scrim] {
opacity: 0;
}
}
/* Closed, the sheet waits one width past its own edge (`--sheet-offset`, mirrored in RTL) and
slides there on emphasized accelerate; open, it comes back on emphasized decelerate. */
[data-md-drawer-sheet] {
--sheet-offset: 100%;
position: fixed;
top: var(--material-safe-top, env(safe-area-inset-top));
bottom: 0;
z-index: 50;
display: flex;
width: 100%;
flex-direction: column;
overflow-y: auto;
padding: var(--md-sys-measurement-space300);
background-color: var(--md-sys-color-surface-container-low);
color: var(--md-sys-color-on-surface);
box-shadow: var(--md-sys-elevation-1);
translate: var(--sheet-offset) 0;
transition-property: translate;
transition-duration: var(--md-sys-motion-effects-default-duration);
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate);
}
[data-md-drawer-sheet][data-md-side='end'] {
inset-inline-end: 0;
}
[data-md-drawer-sheet][data-md-side='start'] {
--sheet-offset: -100%;
inset-inline-start: 0;
}
[data-md-drawer-sheet][data-md-side='end']:is([dir='rtl'], [dir='rtl'] *) {
--sheet-offset: -100%;
}
[data-md-drawer-sheet][data-md-side='start']:is([dir='rtl'], [dir='rtl'] *) {
--sheet-offset: 100%;
}
[data-md-drawer][data-md-open] > [data-md-drawer-sheet] {
translate: 0 0;
transition-duration: var(--md-sys-motion-spatial-default-duration);
transition-timing-function: var(--md-sys-motion-easing-emphasized-decelerate);
}
@starting-style {
[data-md-drawer][data-md-open] > [data-md-drawer-sheet] {
translate: var(--sheet-offset) 0;
}
}
@media (width >= 600px) {
[data-md-drawer-sheet] {
width: var(--sheet-width);
max-width: calc(100vw - 64px);
}
[data-md-drawer-sheet][data-md-side='end'] {
border-start-start-radius: var(--md-sys-shape-corner-lg);
border-end-start-radius: var(--md-sys-shape-corner-lg);
}
[data-md-drawer-sheet][data-md-side='start'] {
border-start-end-radius: var(--md-sys-shape-corner-lg);
border-end-end-radius: var(--md-sys-shape-corner-lg);
}
}
/* Co-planar from expanded: sticky beside the content, no scrim, no elevation, flat on
`surface` with an outline-variant rule down the inner edge instead. It only fades here —
an in-flow box does not travel like an overlay does. */
@media (width >= 840px) {
[data-md-drawer][data-md-standard] {
position: sticky;
top: 0;
display: flex;
justify-content: flex-end;
height: 100dvh;
flex-shrink: 0;
align-self: flex-start;
overflow: clip;
inline-size: 0;
margin-inline-start: calc(-1 * var(--md-drawer-gap, 0px));
transition-property: inline-size, margin-inline;
transition-duration: var(--md-sys-motion-effects-default-duration);
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate);
}
/* A start sheet sits at the start edge, with the gap after it. */
[data-md-drawer][data-md-standard]:where(:has(> [data-md-drawer-sheet][data-md-side='start'])) {
justify-content: flex-start;
margin-inline-start: 0;
margin-inline-end: calc(-1 * var(--md-drawer-gap, 0px));
}
[data-md-drawer][data-md-standard][data-md-open] {
inline-size: min(var(--sheet-width), calc(100vw - 64px));
margin-inline: 0;
transition-duration: var(--md-sys-motion-spatial-default-duration);
transition-timing-function: var(--md-sys-motion-easing-emphasized-decelerate);
}
@starting-style {
[data-md-drawer][data-md-standard][data-md-open] {
inline-size: 0;
margin-inline-start: calc(-1 * var(--md-drawer-gap, 0px));
}
[data-md-drawer][data-md-standard][data-md-open]:where(:has(> [data-md-drawer-sheet][data-md-side='start'])) {
margin-inline-start: 0;
margin-inline-end: calc(-1 * var(--md-drawer-gap, 0px));
}
}
[data-md-drawer][data-md-standard][data-md-drawer-collapsed] {
display: none;
}
[data-md-drawer][data-md-standard] > [data-md-drawer-scrim] {
display: none;
}
[data-md-drawer][data-md-standard] > [data-md-drawer-sheet] {
position: relative;
flex-shrink: 0;
top: 0;
z-index: auto;
height: 100%;
translate: 0 0;
border-radius: var(--md-sys-shape-corner-none);
background-color: var(--md-sys-color-surface);
box-shadow: none;
opacity: 0;
transition-property: opacity;
transition-duration: var(--md-sys-motion-effects-default-duration);
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate);
}
[data-md-drawer][data-md-standard][data-md-open] > [data-md-drawer-sheet] {
opacity: 1;
transition-duration: var(--md-sys-motion-spatial-default-duration);
transition-timing-function: var(--md-sys-motion-easing-emphasized-decelerate);
}
@starting-style {
[data-md-drawer][data-md-standard][data-md-open] > [data-md-drawer-sheet] {
translate: 0 0;
opacity: 0;
}
}
[data-md-drawer][data-md-standard] > [data-md-drawer-sheet][data-md-side='end'] {
border-inline-start: 1px solid var(--md-sys-color-outline-variant);
}
[data-md-drawer][data-md-standard] > [data-md-drawer-sheet][data-md-side='start'] {
border-inline-end: 1px solid var(--md-sys-color-outline-variant);
}
}
[data-md-drawer-head] {
margin-bottom: var(--md-sys-measurement-space200);
}
[data-md-drawer-head-row] {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
[data-md-drawer-heading] {
min-width: 0;
}
[data-md-drawer-title] {
font: var(--md-sys-typescale-title-lg);
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
}
[data-md-drawer-subtitle] {
margin-top: var(--md-sys-measurement-space50);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-body-md);
letter-spacing: var(--md-sys-typescale-body-md-tracking);
}
[data-md-drawer-close] {
display: inline-flex;
flex-shrink: 0;
margin-top: calc(-1 * var(--md-sys-measurement-space100));
margin-inline-end: -12px;
}
[data-md-drawer-head] > [data-md-divider] {
margin-top: var(--md-sys-measurement-space200);
}
[data-md-drawer-body] {
container-type: inline-size;
flex: 1 1 0%;
}
[data-md-drawer-actions] {
display: flex;
min-height: var(--md-sys-measurement-space900);
flex-shrink: 0;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
gap: var(--md-sys-measurement-space100);
padding-top: var(--md-sys-measurement-space200);
}
}