Plan step 36 (containment group): <x-bottom-sheet>'s class lists move into resources/css/components/bottom-sheet.css, keyed on data-md-bottom-sheet (data-md-open, data-md-standard) and data-md-bottom-sheet-scrim/-probe/-panel (data-md-preset while heights/snap gives it stops)/-handle/-grip/-announce/-body/-title/ -actions. x-transition's enter/leave classes are replaced the same way as drawer.css: @starting-style + transition-behavior: allow-discrete on the x-show-driven panel, the asymmetric open (spatial-default, emphasized-decelerate) vs close (effects-default, emphasized- accelerate) timing coming from the more specific open-state selector's own transition-duration/-timing-function, which a CSS transition always takes from the after-change style. data-md-preset adds the height-between-stops transition (the spatial spring) alongside it, independent of the open/close slide. The drag handle (C-01, already fixed in the Tailwind-era markup this replaces) renders the shared md-focus-ring and md-touch-target classes instead of a hand-rolled ring: unlike the datepicker's day, its own visible bar is not a smaller indicator drawn inside a bigger box those classes cannot reach, so nothing here refines them further. Its 22px vertical padding (SheetDefaults.kt's DragHandleVerticalPadding) is a literal, matching alert.css's 12px for the same reason: 22 is not on the measurement scale. Hooks renamed data-drag-handle -> data-md-bottom-sheet-handle, updated in resources/js/bottom-sheet.js and tests/Browser/ContainmentTest.php; data-sheet -> data-md-drawer in that test's comment. <x-menu sheet-at-compact> renders this component unchanged; menu.css now imports bottom-sheet.css, and tests/Feature/Components/MenuTest.php's stale Tailwind-class assertion for the embedded sheet is replaced with a hook check. The two sheet-at-compact browser tests in ActionsTest.php stay on assertVisible(): the sheet is still x-show-driven (a deliberate choice for this batch, keeping the existing modal/side-sheet/bottom-sheet architecture and converting only their styling), so open still shows as visibility rather than :popover-open; their comments no longer say "not rewritten in this batch". Imported from the Containment block of components.css. Browser tests owed by docs/plans/material-3-browser-tests.md, added to ContainmentTest.php but not run: dialog divider marks on scroll (including that a nested dialog keeps its own marks), the full-screen phone bar, separator always, no fade under reduced motion, the scrolling body's inset focus ring; bottom sheet preset-height cycling with its announcement and a drag settling on the nearest stop; the standard side sheet from 840px without a scrim or a focus trap, modal below it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
155 lines
6.7 KiB
CSS
155 lines
6.7 KiB
CSS
/*
|
||
* `<x-bottom-sheet>`: M3's modal and standard bottom sheets (SheetBottomTokens.kt, androidx
|
||
* Compose Material 3, Apache-2.0; docs/reference/m3/
|
||
* components-actions-communication-containment.md § Bottom sheets → Specs).
|
||
*
|
||
* [data-md-bottom-sheet] the wrapper; data-md-open, data-md-standard
|
||
* [data-md-bottom-sheet-scrim] modal only
|
||
* [data-md-bottom-sheet-probe] hidden, measures a stop's px height for a drag to settle on
|
||
* [data-md-bottom-sheet-panel] surface-container-low, extra-large top corners, elevation 1;
|
||
* data-md-preset while `heights`/`snap` gives it stops
|
||
* [data-md-bottom-sheet-handle] the drag target and M3's 48dp hit area (C-01)
|
||
* [data-md-bottom-sheet-grip] the 32×4px bar, the shared classes' touch target
|
||
* [data-md-bottom-sheet-announce] a live region, only while stops exist
|
||
* [data-md-bottom-sheet-body], [data-md-bottom-sheet-title], [data-md-bottom-sheet-actions]
|
||
*
|
||
* 640px max width, centred; 28px top corners only (`DockedContainerShape` = CornerExtraLargeTop);
|
||
* 50dvh default open height under a ceiling of the screen less M3's 72dp top margin (C-19). It
|
||
* rises on emphasized decelerate and only falls on emphasized accelerate — the asymmetric timing a
|
||
* more specific selector for the open state supplies, since a CSS transition always takes its
|
||
* duration and easing from the *after-change* style (the technique resources/css/components/
|
||
* drawer.css also uses). `data-md-preset` adds the height-between-stops transition on the spatial
|
||
* spring, independent of the open/close slide.
|
||
*
|
||
* The handle's own 22px top/bottom padding (`SheetDefaults.kt`'s `DragHandleVerticalPadding`) is a
|
||
* literal — 22 is not on the measurement scale — matching alert.css's 12px for the same reason.
|
||
* The grip button draws the shared `md-focus-ring` and `md-touch-target` classes rather than a
|
||
* hand-rolled ring: its own visible bar is not a smaller indicator drawn inside a bigger box those
|
||
* classes cannot reach (unlike the datepicker's day) — the box they draw *is* the button's whole
|
||
* hit area, so nothing here refines them further.
|
||
*/
|
||
|
||
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||
|
||
@layer material.components {
|
||
[data-md-bottom-sheet-scrim] {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 40;
|
||
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
|
||
transition-property: opacity, display;
|
||
transition-duration: var(--md-sys-motion-effects-default-duration);
|
||
transition-timing-function: var(--md-sys-motion-effects-default);
|
||
transition-behavior: allow-discrete;
|
||
}
|
||
|
||
@starting-style {
|
||
[data-md-bottom-sheet-scrim] {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
[data-md-bottom-sheet-probe] {
|
||
position: fixed;
|
||
inset-inline-start: 0;
|
||
top: 0;
|
||
width: 0;
|
||
visibility: hidden;
|
||
pointer-events: none;
|
||
}
|
||
|
||
[data-md-bottom-sheet-panel] {
|
||
position: fixed;
|
||
inset-inline: 0;
|
||
bottom: 0;
|
||
z-index: 50;
|
||
display: flex;
|
||
width: 100%;
|
||
max-width: 640px;
|
||
margin-inline: auto;
|
||
flex-direction: column;
|
||
max-height: var(--sheet-max-height);
|
||
touch-action: pan-y;
|
||
border-radius: var(--md-sys-shape-corner-xl) var(--md-sys-shape-corner-xl) var(--md-sys-shape-corner-none) var(--md-sys-shape-corner-none);
|
||
background-color: var(--md-sys-color-surface-container-low);
|
||
color: var(--md-sys-color-on-surface);
|
||
box-shadow: var(--md-sys-elevation-1);
|
||
padding-bottom: var(--material-safe-bottom, env(safe-area-inset-bottom));
|
||
translate: 0 100%;
|
||
transition-property: translate, display;
|
||
transition-duration: var(--md-sys-motion-effects-default-duration);
|
||
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate);
|
||
transition-behavior: allow-discrete;
|
||
}
|
||
|
||
[data-md-bottom-sheet][data-md-open] [data-md-bottom-sheet-panel] {
|
||
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-bottom-sheet][data-md-open] [data-md-bottom-sheet-panel] {
|
||
translate: 0 100%;
|
||
}
|
||
}
|
||
|
||
/* With stops the panel is the height of its current one, moving between them on the spatial
|
||
spring, independent of the translate that opens and closes the sheet. */
|
||
[data-md-bottom-sheet-panel][data-md-preset] {
|
||
height: var(--sheet-max-height);
|
||
transition-property: translate, height, display;
|
||
transition-duration: var(--md-sys-motion-effects-default-duration), var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-effects-default-duration);
|
||
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate), var(--md-sys-motion-spatial-default), var(--md-sys-motion-easing-emphasized-accelerate);
|
||
}
|
||
|
||
[data-md-bottom-sheet][data-md-open] [data-md-bottom-sheet-panel][data-md-preset] {
|
||
transition-duration: var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-spatial-default-duration);
|
||
transition-timing-function: var(--md-sys-motion-easing-emphasized-decelerate), var(--md-sys-motion-spatial-default), var(--md-sys-motion-easing-emphasized-decelerate);
|
||
}
|
||
|
||
[data-md-bottom-sheet-handle] {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
justify-content: center;
|
||
padding-block: 22px;
|
||
cursor: grab;
|
||
}
|
||
|
||
[data-md-bottom-sheet-handle]:active {
|
||
cursor: grabbing;
|
||
}
|
||
|
||
[data-md-bottom-sheet-grip] {
|
||
width: 32px;
|
||
height: 4px;
|
||
border-radius: var(--md-sys-shape-corner-full);
|
||
background-color: var(--md-sys-color-on-surface-variant);
|
||
}
|
||
|
||
[data-md-bottom-sheet-body] {
|
||
min-height: 0;
|
||
flex: 1 1 0%;
|
||
overflow-y: auto;
|
||
padding-inline: var(--md-sys-measurement-space300);
|
||
padding-bottom: var(--md-sys-measurement-space300);
|
||
}
|
||
|
||
[data-md-bottom-sheet-title] {
|
||
margin-bottom: var(--md-sys-measurement-space200);
|
||
font: var(--md-sys-typescale-title-lg);
|
||
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
||
}
|
||
|
||
[data-md-bottom-sheet-actions] {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: var(--md-sys-measurement-space100);
|
||
padding-inline: var(--md-sys-measurement-space300);
|
||
padding-bottom: var(--md-sys-measurement-space300);
|
||
}
|
||
}
|