Rewrite the drawer (side sheet) without Tailwind, remove pane
Plan step 36 (containment group): <x-drawer>'s class lists move into resources/css/components/drawer.css, keyed on data-md-drawer (with data-md-open, data-md-standard) and data-md-drawer-scrim/-sheet (data-md-side)/-head/-head-row/-heading/-title/-subtitle/-close/-body/ -actions. The modal sheet slides in on the emphasized-decelerate easing and out on emphasized-accelerate (an asymmetric CSS transition expressed by giving the open-state selector its own transition-duration/-timing-function, which the "after-change style" rule picks up); the sheet stays x-show-driven, with @starting-style + transition-behavior: allow-discrete taking the place of x-transition's enter/leave classes, matching resources/css/components/search.css's already-rewritten scrim/view. A standard sheet drops the scrim and trap from 840px and only fades in place (an in-flow box does not travel); data-md-drawer-collapsed (from the view's `wide` Alpine state) takes it out of the layout while closed there. <x-drawer pane> is removed: step 35's <x-list-detail> is M3's form of that job (the plan's decision, docs/plans/material-3-alignment.md "Decisions"). Removed the prop, its CSS classes and its tests (OverlayTest's two pane-only tests deleted, the others' pane cases dropped); pointed each mention at <x-list-detail> in the material-3 guideline, the material-3-design and livewire-material-development skills, and added a line to UPGRADE.md's 2.0.0 notes. No other living file named `<x-drawer pane>` (grepped views, tests, README, boost, workbench excluding build output); docs/plans and docs/audits keep their history unchanged. Imported from the Containment block of components.css, importing button.css and divider.css for what the view renders. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3c235ecc1e
commit
9dd527f303
+3
-1
@@ -75,7 +75,9 @@ over a colour, use the role directly.
|
|||||||
- `<x-menu-item>` rows are 48px with 16px sides; a selected item draws a trailing check unless it
|
- `<x-menu-item>` rows are 48px with 16px sides; a selected item draws a trailing check unless it
|
||||||
has `icon-right`; a long menu scrolls.
|
has `icon-right`; a long menu scrolls.
|
||||||
- `<x-drawer>` renders a close button by default (`:with-close-button="false"` to drop it, ignored
|
- `<x-drawer>` renders a close button by default (`:with-close-button="false"` to drop it, ignored
|
||||||
on a `pane` or when Escape and the scrim are off) and left-aligns its `actions` in a 72px row.
|
on a `standard` sheet or when Escape and the scrim are off) and left-aligns its `actions` in a
|
||||||
|
72px row. `<x-drawer pane>` is removed; use `<x-list-detail>` for the second pane of a
|
||||||
|
list-detail layout.
|
||||||
- `<x-bottom-sheet>` opens at `50dvh` (was `90dvh`); any `height` is capped at
|
- `<x-bottom-sheet>` opens at `50dvh` (was `90dvh`); any `height` is capped at
|
||||||
`calc(100dvh - 72px)`.
|
`calc(100dvh - 72px)`.
|
||||||
- `<x-carousel>` `padding` defaults to `16`; `layout="full-screen"` scrolls vertically with
|
- `<x-carousel>` `padding` defaults to `16`; `layout="full-screen"` scrolls vertically with
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ Every view in this application is Material 3 Expressive (m3.material.io), throug
|
|||||||
|
|
||||||
### Layout and breakpoints
|
### Layout and breakpoints
|
||||||
- Widths are M3's window size classes, the only variants that compile: compact below 600px (the default), `medium:` 600, `expanded:` 840, `large:` 1200, `extra-large:` 1600, and `max-medium:` … for "below". In scripts, `from()` and `upTo()` from `resources/js/breakpoints.js`.
|
- Widths are M3's window size classes, the only variants that compile: compact below 600px (the default), `medium:` 600, `expanded:` 840, `large:` 1200, `extra-large:` 1600, and `max-medium:` … for "below". In scripts, `from()` and `upTo()` from `resources/js/breakpoints.js`.
|
||||||
- What changes per class: compact — navigation bar, one pane, full-screen dialogs, a bottom sheet for choices; medium — collapsed rail, one pane; expanded — rail (collapsible), two panes, menus and basic dialogs; large and extra-large — the rail expanded, two panes, a third only at extra-large as a side sheet. `<x-scaffold>` does this; content lives in panes (`<x-drawer pane>`), never beside the rail by hand.
|
- What changes per class: compact — navigation bar, one pane, full-screen dialogs, a bottom sheet for choices; medium — collapsed rail, one pane; expanded — rail (collapsible), two panes, menus and basic dialogs; large and extra-large — the rail expanded, two panes, a third only at extra-large as a side sheet. `<x-scaffold>` does this; content lives in panes (`<x-pane>`, `<x-list-detail>` for a list's second pane), never beside the rail by hand.
|
||||||
- Margins are 16px below `medium` and 24px from it; spacing sits on the 4px grid, as padding and gaps on the parent, with margins only between layout regions. A fixed pane is 360px (expanded) or 412px (large); a side sheet at most 400px.
|
- Margins are 16px below `medium` and 24px from it; spacing sits on the 4px grid, as padding and gaps on the parent, with margins only between layout regions. A fixed pane is 360px (expanded) or 412px (large); a side sheet at most 400px.
|
||||||
- Write logical properties (`ps-*`, `me-*`, `start-*`, `text-start`); directional icons mirror in RTL; charts and media controls stay LTR. Keep controls inside the safe area (`--material-safe-*`).
|
- Write logical properties (`ps-*`, `me-*`, `start-*`, `text-start`); directional icons mirror in RTL; charts and media controls stay LTR. Keep controls inside the safe area (`--material-safe-*`).
|
||||||
|
|
||||||
|
|||||||
@@ -539,9 +539,9 @@ Props: `title`, `subtitle`, `icon` (centred hero icon), `separator` (draw the di
|
|||||||
|
|
||||||
### `<x-drawer>`
|
### `<x-drawer>`
|
||||||
|
|
||||||
An M3 side sheet, bound like `<x-modal>`; `close()` in scope. Props: `title`, `subtitle`, `separator`, `side` (`end` default, `start`), `width` (`25rem`), `with-close-button` (**default true** — M3 requires a close affordance; `:with-close-button="false"` is ignored when Escape or the scrim is off, and on a pane), `close-on-escape` (default true), `without-backdrop-close`, `actions` slot (**left**-aligned in a 72dp row, which is what the side-sheet spec says; a dialog's are trailing-aligned). `pane` (with `pane-width`, `22.5rem` — M3's 360dp fixed pane) turns it into a second pane from `expanded`, where M3 shows two panes: render it after the list inside `<div class="expanded:flex expanded:items-start expanded:gap-6">`. Escape leaves a pane open unless `pane-close-on-escape`. Its body is a size container — lay out inside with `@md:` (a *container* query), never a window class.
|
An M3 side sheet, bound like `<x-modal>`; `close()` in scope. Props: `title`, `subtitle`, `separator`, `side` (`end` default, `start`), `width` (`25rem`), `with-close-button` (**default true** — M3 requires a close affordance; `:with-close-button="false"` is ignored when Escape or the scrim is off, or on a `standard` sheet), `close-on-escape` (default true), `without-backdrop-close`, `actions` slot (**left**-aligned in a 72dp row, which is what the side-sheet spec says; a dialog's are trailing-aligned). For the second pane of a list-detail layout use `<x-list-detail>` instead — `<x-drawer>` no longer has a `pane` prop. Its body is a size container — lay out inside with `@md:` (a *container* query), never a window class.
|
||||||
|
|
||||||
`standard` is M3's other side-sheet variant, and it is not the same thing as `pane`. A **pane** is the list-detail companion: it shows what the list beside it has selected, 360dp wide on `surface-container` with a large corner. A **standard** sheet is supplementary content beside the primary content — filters, details, a list of actions: co-planar from `expanded`, flat on `surface` with 0dp elevation and no corner, the window's full height, an outline-variant rule down its inner edge instead of a scrim, nothing inert and no focus trap. Below `expanded` it is the modal sheet. Capped at M3's 400dp whatever `width` says, and it always draws the close button. Render it in the same `expanded:flex` row as a pane.
|
`standard` is M3's other side-sheet variant: supplementary content beside the primary content — filters, details, a list of actions — co-planar from `expanded`, flat on `surface` with 0dp elevation and no corner, the window's full height, an outline-variant rule down its inner edge instead of a scrim, nothing inert and no focus trap. Below `expanded` it is the modal sheet. Capped at M3's 400dp whatever `width` says, and it always draws the close button. Render it beside its content in a row that only lays out side by side from `expanded` (`<x-supporting-pane>`, or a caller's own row).
|
||||||
|
|
||||||
### `<x-bottom-sheet>`
|
### `<x-bottom-sheet>`
|
||||||
|
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ Layout keys on the width of the window, in M3's five window size classes; they a
|
|||||||
| Large | 1200–1599px | `large:` | rail expanded | 2; a fixed pane 412px | basic dialog; menu | 24px |
|
| Large | 1200–1599px | `large:` | rail expanded | 2; a fixed pane 412px | basic dialog; menu | 24px |
|
||||||
| Extra-large | 1600px and up | `extra-large:` | rail expanded | 2, or 3 with a standard side sheet (at most 400px) | basic dialog; menu | 24px |
|
| Extra-large | 1600px and up | `extra-large:` | rail expanded | 2, or 3 with a standard side sheet (at most 400px) | basic dialog; menu | 24px |
|
||||||
|
|
||||||
- `<x-scaffold>` implements the navigation column; `<x-drawer pane>` is the second pane of a list-detail layout from `expanded:`, a supporting pane (360px, beside the focus pane) from `expanded:` and below it before that. Moving up a class, ask what to reveal, divide into panes, resize, reposition or swap — never swap a component for one that does not do the same job.
|
- `<x-scaffold>` implements the navigation column; `<x-list-detail>` is the second pane of a list-detail layout from `expanded:`, `<x-supporting-pane>` puts a supporting pane (360px, beside the focus pane) from `expanded:` and below it before that. Moving up a class, ask what to reveal, divide into panes, resize, reposition or swap — never swap a component for one that does not do the same job.
|
||||||
- Scaffold: bars (app bar at the top, navigation bar at the bottom: 3–5 destinations), rails (the navigation rail, toolbars, the FAB, on the leading edge), panes (all content), around a safety region that stays clear of the device's own chrome (`--material-safe-top|bottom|left|right`).
|
- Scaffold: bars (app bar at the top, navigation bar at the bottom: 3–5 destinations), rails (the navigation rail, toolbars, the FAB, on the leading edge), panes (all content), around a safety region that stays clear of the device's own chrome (`--material-safe-top|bottom|left|right`).
|
||||||
- Canonical layouts: feed (a grid of cards that gains columns per class), list-detail (one pane on compact, two from expanded; a back button only in single-pane mode, a selected row only in two-pane mode), supporting pane (two thirds focus, one third support).
|
- Canonical layouts: feed (a grid of cards that gains columns per class), list-detail (one pane on compact, two from expanded; a back button only in single-pane mode, a selected row only in two-pane mode), supporting pane (two thirds focus, one third support).
|
||||||
- Bidirectionality: write logical properties (`ps-4`, `me-2`, `start-0`, `text-start`, `border-s`); leading and trailing icons swap, directional icons (back, send) mirror, the rail moves to the right; charts, media controls, clocks and Hebrew progress bars stay left-to-right.
|
- Bidirectionality: write logical properties (`ps-4`, `me-2`, `start-0`, `text-start`, `border-s`); leading and trailing icons swap, directional icons (back, send) mirror, the rail moves to the right; charts, media controls, clocks and Hebrew progress bars stay left-to-right.
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
@import './components/list-item.css';
|
@import './components/list-item.css';
|
||||||
@import './components/list.css';
|
@import './components/list.css';
|
||||||
@import './components/modal.css';
|
@import './components/modal.css';
|
||||||
|
@import './components/drawer.css';
|
||||||
|
|
||||||
/* Navigation */
|
/* Navigation */
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* `<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 (hidden by JS once the sheet is wide/standard)
|
||||||
|
* [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, 56dp side-sheet
|
||||||
|
* width start — all 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; below
|
||||||
|
* `expanded` it slides, and once `standard` makes it co-planar it only fades (an in-flow box does
|
||||||
|
* not need to travel).
|
||||||
|
*
|
||||||
|
* 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`. `data-md-drawer-collapsed` (the view, from the
|
||||||
|
* `wide` — window ≥ 840px — Alpine state) takes the standard sheet out of the layout entirely
|
||||||
|
* while it is closed there, so the content beside it fills the space.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||||||
|
|
||||||
|
@import './button.css';
|
||||||
|
@import './divider.css';
|
||||||
|
|
||||||
|
@layer material.components {
|
||||||
|
[data-md-drawer-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-drawer-scrim] {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-drawer-sheet] {
|
||||||
|
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: 100% 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition-property: translate, opacity, 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-drawer-sheet][data-md-side='end'] {
|
||||||
|
inset-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-drawer-sheet][data-md-side='start'] {
|
||||||
|
inset-inline-start: 0;
|
||||||
|
translate: -100% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-drawer][data-md-open] [data-md-drawer-sheet] {
|
||||||
|
translate: 0 0;
|
||||||
|
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-open] [data-md-drawer-sheet] {
|
||||||
|
translate: 100% 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-drawer][data-md-open] [data-md-drawer-sheet][data-md-side='start'] {
|
||||||
|
translate: -100% 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;
|
||||||
|
height: 100dvh;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-drawer][data-md-standard][data-md-drawer-collapsed] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-drawer][data-md-standard] [data-md-drawer-sheet] {
|
||||||
|
position: relative;
|
||||||
|
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, display;
|
||||||
|
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] {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +1,49 @@
|
|||||||
{{-- An M3 side sheet: detail or controls that slide in from the edge over a scrim; on a phone it is
|
{{-- An M3 side sheet: detail or controls that slide in from the edge over a scrim; on a phone it
|
||||||
the whole screen. With `pane`, from `expanded` (840px) it is a second pane beside the list
|
is the whole screen.
|
||||||
instead — M3 shows two panes for list-detail from expanded upwards, and puts a supporting pane
|
|
||||||
on the side of the focus pane at expanded, 360dp wide
|
|
||||||
(docs/reference/m3/foundations-supplement.md § Canonical layout examples).
|
|
||||||
|
|
||||||
The open state is the Livewire property in `wire:model` (entangled live, so a detail held in the
|
The open state is the Livewire property in `wire:model` (entangled live, so a detail held in
|
||||||
URL follows a close) — a flag or an id — and closing writes back `false` or `null`. Without
|
the URL follows a close) — a flag or an id — and closing writes back `false` or `null`.
|
||||||
`wire:model` it reads and writes `open` in the Alpine scope around it. `close()` is in scope for
|
Without `wire:model` it reads and writes `open` in the Alpine scope around it. `close()` is in
|
||||||
anything inside the sheet, so it can draw its own close button.
|
scope for anything inside the sheet, so it can draw its own close button.
|
||||||
|
|
||||||
As a sheet it is modal: the page inert and still (`x-trap.inert.noscroll`), and it enters on
|
As a sheet it is modal: the page inert and still (`x-trap.inert.noscroll`), and it enters on
|
||||||
emphasized decelerate rather than a spring — a sheet anchored to the edge that overshot would
|
emphasized decelerate rather than a spring — a sheet anchored to the edge that overshot would
|
||||||
open a gap. M3's modal side sheet: surface-container-low, a large corner on its inner edge,
|
open a gap. M3's modal side sheet (NavigationDrawerTokens.kt-adjacent side-sheet sources;
|
||||||
elevation 1; `side` `end` (the default) or `start`; `width` from `medium` (a caller's `w-*`
|
docs/reference/m3/components-actions-communication-containment.md § Side sheets): surface-
|
||||||
would race the sheet's own — a compact window gets the full-bleed sheet).
|
container-low, a large corner on its inner edge, elevation 1; `side` `end` (the default) or
|
||||||
|
`start`; `width` from `medium` (a caller's `class` would race the sheet's own — a compact
|
||||||
|
window gets the full-bleed sheet).
|
||||||
|
|
||||||
As a **standard** side sheet (`standard`, from `expanded`) it is M3's other variant: co-planar
|
As a **standard** side sheet (`standard`, from `expanded`) it is M3's other variant: co-planar
|
||||||
with the content rather than over it — no scrim, no focus trap, nothing inert, 0dp elevation
|
with the content rather than over it — no scrim, no focus trap, nothing inert, 0dp elevation
|
||||||
(material-components-android's `SideSheet.md`: "standard side sheet elevation = 0dp,
|
(material-components-android's `SideSheet.md`: "standard side sheet elevation = 0dp,
|
||||||
coplanar"), `surface` rather than surface-container-low, no corner, and an outline-variant
|
coplanar"), `surface` rather than surface-container-low, no corner, and an outline-variant
|
||||||
divider down its inner edge in place of the scrim. It sits in the page flow beside the
|
divider down its inner edge in place of the scrim. It sits in the page flow beside the
|
||||||
content, spans the window's height and scrolls on its own. M3 calls the standard sheet
|
content, spans the window's height and scrolls on its own; below `expanded` (840px) it is the
|
||||||
"supplementary surfaces mainly for medium to expanded breakpoints" and the modal one
|
modal sheet — M3 calls the standard sheet "supplementary surfaces mainly for medium to
|
||||||
"preferred at compact breakpoints"; the library switches at `expanded` (840px) rather than at
|
expanded breakpoints" and the modal one "preferred at compact breakpoints", and the switch
|
||||||
medium, because M3 also caps a side sheet at 400dp and a 600px window has too little left
|
sits at `expanded` rather than `medium` because M3 also caps a side sheet at 400dp and a
|
||||||
beside one — below `expanded` a `standard` sheet is the modal sheet
|
600px window has too little left beside one.
|
||||||
(docs/reference/m3/components-actions-communication-containment.md § Side sheets).
|
|
||||||
|
|
||||||
`standard` and `pane` are not the same thing and neither replaces the other: a **pane** is the
|
For the second pane of a list-detail layout use `<x-list-detail>` instead (step 35's canonical
|
||||||
second pane of a list-detail layout — it shows what the list beside it has selected, is
|
layout); the two are not the same thing — a pane shows what the list beside it selected, a
|
||||||
`22.5rem` wide (M3's 360dp fixed pane), sticks under the top of the viewport and sits on
|
standard sheet is supplementary content (filters, details, a list of actions) beside the
|
||||||
`surface-container` with a large corner. A **standard** sheet is supplementary content beside
|
primary content, divided from it by a rule rather than being part of its selection.
|
||||||
the primary content — filters, details, a list of actions — full height, flat on `surface`,
|
|
||||||
divided from the content by a rule. Pass one or the other; `pane` wins if both are given.
|
|
||||||
|
|
||||||
As a pane (`pane`, from `expanded`) nothing is covered: the page renders the drawer after its
|
|
||||||
list in an `expanded:flex expanded:items-start expanded:gap-6` row, the drawer sticks under the
|
|
||||||
top of the viewport, the
|
|
||||||
list stays usable and another row swaps what it shows — no scrim, no trap, no inert page. While
|
|
||||||
closed it takes no room. `pane-width` sizes the pane (`22.5rem`, M3's 360dp fixed pane). Escape closes
|
|
||||||
the sheet but leaves a pane open, since the page beside it is still in use; `pane-close-on-escape`
|
|
||||||
closes the pane on Escape too, for a pane that is a transient detail. The body is
|
|
||||||
a size container, so its contents lay out by the room the sheet or pane actually has (`@md:`),
|
|
||||||
never by the viewport.
|
|
||||||
|
|
||||||
M3 **requires** a close affordance on a side sheet — without one nobody can predict the
|
M3 **requires** a close affordance on a side sheet — without one nobody can predict the
|
||||||
sheet's open/close flow or tell whether it is transient or permanent
|
sheet's open/close flow or tell whether it is transient or permanent
|
||||||
(docs/reference/m3/components-actions-communication-containment.md § Side sheets →
|
(docs/reference/m3/components-actions-communication-containment.md § Side sheets →
|
||||||
Accessibility) — so `with-close-button` is on by default, and `:with-close-button="false"` is
|
Accessibility, C-04) — so `with-close-button` is on by default, and
|
||||||
ignored where nothing else closes the sheet: Escape off, the scrim off, or a pane or a
|
`:with-close-button="false"` is ignored where nothing else closes the sheet: Escape off, the
|
||||||
standard sheet, which have neither from `expanded`.
|
scrim off, or `standard`, which has neither from `expanded`.
|
||||||
|
|
||||||
maryUI's API, kept: `title`, `subtitle`, `separator`, `with-close-button`, `close-on-escape`,
|
maryUI's API, kept: `title`, `subtitle`, `separator`, `with-close-button`, `close-on-escape`,
|
||||||
`without-backdrop-close`, `right` (ignored; use `side`), and an `actions` slot. --}}
|
`without-backdrop-close`, `right` (ignored; use `side`), and an `actions` slot.
|
||||||
|
|
||||||
|
The root renders `data-md-drawer`, `data-md-open` and `data-md-standard`; the sheet is
|
||||||
|
`data-md-drawer-sheet` with `data-md-side`, its parts `data-md-drawer-head`/-head-row/
|
||||||
|
-heading/-title/-subtitle/-close/-body/-actions — drawn by resources/css/components/
|
||||||
|
drawer.css, which imports button.css and divider.css for what the view renders. --}}
|
||||||
|
|
||||||
@props([
|
@props([
|
||||||
'title' => null,
|
'title' => null,
|
||||||
@@ -64,23 +55,14 @@
|
|||||||
'closeOnEscape' => true,
|
'closeOnEscape' => true,
|
||||||
'withoutBackdropClose' => false,
|
'withoutBackdropClose' => false,
|
||||||
'width' => '25rem',
|
'width' => '25rem',
|
||||||
'pane' => false,
|
|
||||||
'paneWidth' => '22.5rem',
|
|
||||||
'paneCloseOnEscape' => false,
|
|
||||||
'standard' => false,
|
'standard' => false,
|
||||||
])
|
])
|
||||||
|
|
||||||
@php
|
@php
|
||||||
$model = $attributes->wire('model')->value() ?: null;
|
$model = $attributes->wire('model')->value() ?: null;
|
||||||
$id = $attributes->get('id') ?? 'material-sheet-'.substr(md5($model.'|'.$title), 0, 10);
|
$id = $attributes->get('id') ?? 'material-sheet-'.substr(md5($model.'|'.$title), 0, 10);
|
||||||
$start = $side === 'start';
|
$side = $side === 'start' ? 'start' : 'end';
|
||||||
|
$standard = (bool) $standard;
|
||||||
// One or the other: a pane is the list-detail companion, a standard sheet is supplementary
|
|
||||||
// content beside the primary content.
|
|
||||||
$standard = $standard && ! $pane;
|
|
||||||
|
|
||||||
// Both stop being modal from `expanded`, so both watch the window for that width.
|
|
||||||
$wide = $pane || $standard;
|
|
||||||
|
|
||||||
// The side-sheet specs table caps the sheet at 400dp, which is where `width` already starts;
|
// The side-sheet specs table caps the sheet at 400dp, which is where `width` already starts;
|
||||||
// a wider one is the modal sheet's to take, not the co-planar standard sheet's.
|
// a wider one is the modal sheet's to take, not the co-planar standard sheet's.
|
||||||
@@ -89,7 +71,7 @@
|
|||||||
// M3 requires a close affordance; the prop can only ever add one, never take away the last
|
// M3 requires a close affordance; the prop can only ever add one, never take away the last
|
||||||
// way out of the sheet. A standard sheet keeps no scrim and no trap from `expanded`, and
|
// way out of the sheet. A standard sheet keeps no scrim and no trap from `expanded`, and
|
||||||
// Escape leaves it open there, so it always draws one.
|
// Escape leaves it open there, so it always draws one.
|
||||||
$closeButton = $withCloseButton || ! $closeOnEscape || $withoutBackdropClose || $standard || ($pane && ! $paneCloseOnEscape);
|
$closeButton = $withCloseButton || ! $closeOnEscape || $withoutBackdropClose || $standard;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -97,7 +79,7 @@
|
|||||||
@if ($model !== null) open: @entangle($attributes->wire('model')).live, @endif
|
@if ($model !== null) open: @entangle($attributes->wire('model')).live, @endif
|
||||||
wide: false,
|
wide: false,
|
||||||
close() { this.open = typeof this.open === 'boolean' ? false : null; },
|
close() { this.open = typeof this.open === 'boolean' ? false : null; },
|
||||||
@if ($wide)
|
@if ($standard)
|
||||||
init() {
|
init() {
|
||||||
const query = window.matchMedia('(width >= 52.5rem)');
|
const query = window.matchMedia('(width >= 52.5rem)');
|
||||||
this.wide = query.matches;
|
this.wide = query.matches;
|
||||||
@@ -105,98 +87,62 @@
|
|||||||
},
|
},
|
||||||
@endif
|
@endif
|
||||||
}"
|
}"
|
||||||
@if ($closeOnEscape) x-on:keydown.window.escape="{{ $paneCloseOnEscape ? 'if (open) close()' : 'if (open && ! wide) close()' }}" @endif
|
@if ($closeOnEscape) x-on:keydown.window.escape="if (open && ! wide) close()" @endif
|
||||||
data-sheet="{{ $id }}"
|
x-bind:data-md-open="open ? '' : null"
|
||||||
@if ($pane)
|
x-bind:data-md-drawer-collapsed="(! open && wide) ? '' : null"
|
||||||
x-bind:class="! open && 'expanded:hidden'"
|
data-md-drawer
|
||||||
class="expanded:sticky expanded:top-[calc(var(--material-safe-top,env(safe-area-inset-top))+1.25rem)] expanded:shrink-0 expanded:self-start"
|
@if ($standard) data-md-standard @endif
|
||||||
data-pane
|
|
||||||
@elseif ($standard)
|
|
||||||
{{-- A standard sheet spans the window's height beside the content and takes no room while
|
|
||||||
closed; sticky, so it stays put as the page scrolls past it. --}}
|
|
||||||
x-bind:class="! open && 'expanded:hidden'"
|
|
||||||
class="expanded:sticky expanded:top-0 expanded:h-dvh expanded:shrink-0 expanded:self-start"
|
|
||||||
data-standard
|
|
||||||
@endif
|
|
||||||
>
|
>
|
||||||
<div
|
<div data-md-drawer-scrim x-cloak x-show="open" @if (! $withoutBackdropClose) x-on:click="close()" @endif aria-hidden="true"></div>
|
||||||
x-cloak
|
|
||||||
x-show="open"
|
|
||||||
x-transition.opacity.duration.200ms
|
|
||||||
@if (! $withoutBackdropClose) x-on:click="close()" @endif
|
|
||||||
@class(['fixed inset-0 z-40 bg-scrim/32', 'expanded:hidden' => $wide])
|
|
||||||
aria-hidden="true"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
x-cloak
|
x-cloak
|
||||||
x-show="open"
|
x-show="open"
|
||||||
x-trap.inert.noscroll="open && ! wide"
|
x-trap.inert.noscroll="open && ! wide"
|
||||||
x-transition:enter="transition-[translate,opacity] duration-(--md-sys-motion-spatial-default-duration) ease-emphasized-decelerate"
|
|
||||||
x-transition:enter-start="{{ $wide ? ($start ? 'max-expanded:-translate-x-full expanded:opacity-0' : 'max-expanded:translate-x-full expanded:opacity-0') : ($start ? '-translate-x-full' : 'translate-x-full') }}"
|
|
||||||
x-transition:enter-end="translate-x-0 opacity-100"
|
|
||||||
x-transition:leave="transition-[translate,opacity] duration-(--md-sys-motion-effects-default-duration) ease-emphasized-accelerate"
|
|
||||||
x-transition:leave-start="translate-x-0 opacity-100"
|
|
||||||
x-transition:leave-end="{{ $wide ? ($start ? 'max-expanded:-translate-x-full expanded:opacity-0' : 'max-expanded:translate-x-full expanded:opacity-0') : ($start ? '-translate-x-full' : 'translate-x-full') }}"
|
|
||||||
id="{{ $id }}"
|
|
||||||
x-bind:role="wide ? 'region' : 'dialog'"
|
x-bind:role="wide ? 'region' : 'dialog'"
|
||||||
x-bind:aria-modal="wide ? null : 'true'"
|
x-bind:aria-modal="wide ? null : 'true'"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
@if (filled($title)) aria-labelledby="{{ $id }}-title" @endif
|
@if (filled($title)) aria-labelledby="{{ $id }}-title" @endif
|
||||||
style="--sheet-width: {{ $sheetWidth }}; --pane-width: {{ $paneWidth }}"
|
id="{{ $id }}"
|
||||||
{{ $attributes->whereDoesntStartWith('wire:model')->except(['id', 'class'])->class([
|
data-md-drawer-sheet
|
||||||
'fixed top-[var(--material-safe-top,env(safe-area-inset-top))] bottom-0 z-50 flex w-full flex-col overflow-y-auto bg-surface-container-low p-6 text-on-surface shadow-elevation-1',
|
data-md-side="{{ $side }}"
|
||||||
'end-0 medium:rounded-s-corner-lg' => ! $start,
|
style="--sheet-width: {{ $sheetWidth }}"
|
||||||
'start-0 medium:rounded-e-corner-lg' => $start,
|
{{ $attributes->whereDoesntStartWith('wire:model')->except(['id']) }}
|
||||||
'medium:w-(--sheet-width) medium:max-w-[calc(100vw-4rem)]',
|
|
||||||
'expanded:relative expanded:top-0 expanded:z-auto expanded:max-h-[calc(100dvh-2.5rem-var(--material-safe-top,env(safe-area-inset-top)))] expanded:w-(--pane-width) expanded:max-w-none expanded:rounded-corner-lg expanded:bg-surface-container expanded:shadow-none' => $pane,
|
|
||||||
// M3's standard side sheet from `expanded`: co-planar on `surface`, square, 0dp
|
|
||||||
// elevation, the window's full height, with an outline-variant rule down the edge it
|
|
||||||
// meets the content on — the divider its anatomy lists, in place of the scrim.
|
|
||||||
'expanded:relative expanded:top-0 expanded:z-auto expanded:h-full expanded:rounded-corner-none expanded:bg-surface expanded:shadow-none' => $standard,
|
|
||||||
'expanded:border-s expanded:border-outline-variant' => $standard && ! $start,
|
|
||||||
'expanded:border-e expanded:border-outline-variant' => $standard && $start,
|
|
||||||
$attributes->get('class'),
|
|
||||||
]) }}
|
|
||||||
>
|
>
|
||||||
@if (filled($title) || $closeButton)
|
@if (filled($title) || $closeButton)
|
||||||
<div class="mb-4">
|
<div data-md-drawer-head>
|
||||||
<div class="flex items-start justify-between gap-3">
|
<div data-md-drawer-head-row>
|
||||||
<div class="min-w-0">
|
<div data-md-drawer-heading>
|
||||||
@if (filled($title))
|
@if (filled($title))
|
||||||
<h2 id="{{ $id }}-title" class="type-title-lg">{{ $title }}</h2>
|
<h2 id="{{ $id }}-title" data-md-drawer-title>{{ $title }}</h2>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if (filled($subtitle))
|
@if (filled($subtitle))
|
||||||
<p class="mt-1 type-body-md text-on-surface-variant">{{ $subtitle }}</p>
|
<p data-md-drawer-subtitle>{{ $subtitle }}</p>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ($closeButton)
|
@if ($closeButton)
|
||||||
<span class="-me-3 -mt-2 inline-flex shrink-0">
|
<span data-md-drawer-close>
|
||||||
<x-livewire-material::button icon="close" :tooltip-left="__('Close')" x-on:click="close()" />
|
<x-livewire-material::button icon="close" :tooltip-left="__('Close')" x-on:click="close()" />
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ($separator)
|
@if ($separator)
|
||||||
<x-livewire-material::divider class="mt-4" />
|
<x-livewire-material::divider />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="@container flex-1">
|
<div data-md-drawer-body>{{ $slot }}</div>
|
||||||
{{ $slot }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@isset($actions)
|
@isset($actions)
|
||||||
{{-- The side-sheet specs table: bottom actions 72dp tall, 16dp above them, 24dp below
|
{{-- The side-sheet specs table: bottom actions 72dp tall, 16dp above them, 24dp below
|
||||||
(the sheet's own padding), aligned **left** — a dialog's are trailing-aligned, and
|
(the sheet's own padding), aligned **left** — a dialog's are trailing-aligned, and
|
||||||
M3 specifies the two differently. --}}
|
M3 specifies the two differently (C-10). --}}
|
||||||
<div class="flex min-h-18 shrink-0 flex-wrap items-center justify-start gap-2 pt-4">
|
<div data-md-drawer-actions>{{ $actions }}</div>
|
||||||
{{ $actions }}
|
|
||||||
</div>
|
|
||||||
@endisset
|
@endisset
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ dataset('containment components', [
|
|||||||
'list',
|
'list',
|
||||||
'list-item',
|
'list-item',
|
||||||
'modal',
|
'modal',
|
||||||
|
'drawer',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -174,41 +174,27 @@ it('gives a full-screen dialog M3\'s 56px header and action bar', function () {
|
|||||||
->toContain('min-height: var(--md-sys-measurement-space700);');
|
->toContain('min-height: var(--md-sys-measurement-space700);');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('leaves a pane open on Escape unless it is asked to close then too', function () {
|
|
||||||
expect((string) $this->blade('<x-drawer pane>Body</x-drawer>'))
|
|
||||||
->toContain('x-on:keydown.window.escape="if (open && ! wide) close()"')
|
|
||||||
->and((string) $this->blade('<x-drawer pane pane-close-on-escape>Body</x-drawer>'))
|
|
||||||
->toContain('x-on:keydown.window.escape="if (open) close()"')
|
|
||||||
->and((string) $this->blade('<x-drawer pane pane-close-on-escape :close-on-escape="false">Body</x-drawer>'))
|
|
||||||
->not->toContain('keydown.window.escape');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('always offers a way out of a side sheet, as M3 requires', function () {
|
it('always offers a way out of a side sheet, as M3 requires', function () {
|
||||||
expect((string) $this->blade('<x-drawer title="Details">Body</x-drawer>'))->toContain('aria-label="Close"')
|
expect((string) $this->blade('<x-drawer title="Details">Body</x-drawer>'))->toContain('aria-label="Close"')
|
||||||
->and((string) $this->blade('<x-drawer title="Details" :with-close-button="false">Body</x-drawer>'))->not->toContain('aria-label="Close"')
|
->and((string) $this->blade('<x-drawer title="Details" :with-close-button="false">Body</x-drawer>'))->not->toContain('aria-label="Close"')
|
||||||
->and((string) $this->blade('<x-drawer title="Details" :with-close-button="false" :close-on-escape="false">Body</x-drawer>'))->toContain('aria-label="Close"')
|
->and((string) $this->blade('<x-drawer title="Details" :with-close-button="false" :close-on-escape="false">Body</x-drawer>'))->toContain('aria-label="Close"')
|
||||||
->and((string) $this->blade('<x-drawer title="Details" :with-close-button="false" without-backdrop-close>Body</x-drawer>'))->toContain('aria-label="Close"')
|
->and((string) $this->blade('<x-drawer title="Details" :with-close-button="false" without-backdrop-close>Body</x-drawer>'))->toContain('aria-label="Close"')
|
||||||
->and((string) $this->blade('<x-drawer pane :with-close-button="false">Body</x-drawer>'))->toContain('aria-label="Close"')
|
->and((string) $this->blade('<x-drawer standard :with-close-button="false">Body</x-drawer>'))->toContain('aria-label="Close"');
|
||||||
->and((string) $this->blade('<x-drawer pane pane-close-on-escape :with-close-button="false">Body</x-drawer>'))->not->toContain('aria-label="Close"');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('puts a side sheet\'s actions on the left, in M3\'s 72dp row', function () {
|
it('puts a side sheet\'s actions on the left, in M3\'s 72dp row', function () {
|
||||||
expect((string) $this->blade('<x-drawer title="Details">Body<x-slot:actions><button>Delete</button></x-slot:actions></x-drawer>'))
|
expect((string) $this->blade('<x-drawer title="Details">Body<x-slot:actions><button>Delete</button></x-slot:actions></x-drawer>'))
|
||||||
->toContain('flex min-h-18 shrink-0 flex-wrap items-center justify-start gap-2 pt-4');
|
->toMatch('/<div\s+data-md-drawer-actions\s*>/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('slides a side sheet in from either edge, and is a pane from expanded when asked', function () {
|
it('slides a side sheet in from either edge', function () {
|
||||||
expect((string) $this->blade('<x-drawer title="Details" with-close-button>Body</x-drawer>'))
|
expect((string) $this->blade('<x-drawer title="Details" with-close-button>Body</x-drawer>'))
|
||||||
->toContain('x-trap.inert.noscroll="open && ! wide"')
|
->toContain('x-trap.inert.noscroll="open && ! wide"')
|
||||||
->toContain('end-0 medium:rounded-s-corner-lg')
|
->toContain('data-md-side="end"')
|
||||||
->toContain('bg-surface-container-low')
|
->toContain('data-md-drawer-sheet')
|
||||||
->toContain('role="dialog"')
|
->toContain('role="dialog"')
|
||||||
->toContain('aria-label="Close"')
|
->toContain('aria-label="Close"')
|
||||||
->and((string) $this->blade('<x-drawer side="start">Body</x-drawer>'))->toContain('start-0 medium:rounded-e-corner-lg')
|
->and((string) $this->blade('<x-drawer side="start">Body</x-drawer>'))->toContain('data-md-side="start"');
|
||||||
->and((string) $this->blade('<x-drawer pane pane-width="28rem">Body</x-drawer>'))
|
|
||||||
->toContain('data-pane')
|
|
||||||
->toContain('--pane-width: 28rem')
|
|
||||||
->toContain("matchMedia('(width >= 52.5rem)')");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('draws a modal bottom sheet with a drag handle, or a standard one without a scrim', function () {
|
it('draws a modal bottom sheet with a drag handle, or a standard one without a scrim', function () {
|
||||||
@@ -234,24 +220,37 @@ it('is M3\'s standard side sheet from expanded, and the modal one below', functi
|
|||||||
$html = (string) $this->blade('<x-drawer standard title="Filters">Body</x-drawer>');
|
$html = (string) $this->blade('<x-drawer standard title="Filters">Body</x-drawer>');
|
||||||
|
|
||||||
expect($html)
|
expect($html)
|
||||||
->toContain('data-standard')
|
->toContain('data-md-standard')
|
||||||
->toContain('expanded:sticky expanded:top-0 expanded:h-dvh expanded:shrink-0 expanded:self-start')
|
|
||||||
->toContain('expanded:relative expanded:top-0 expanded:z-auto expanded:h-full expanded:rounded-corner-none expanded:bg-surface expanded:shadow-none')
|
|
||||||
->toContain('expanded:border-s expanded:border-outline-variant')
|
|
||||||
->toContain('--sheet-width: min(25rem, 25rem)')
|
->toContain('--sheet-width: min(25rem, 25rem)')
|
||||||
->toContain("matchMedia('(width >= 52.5rem)')")
|
->toContain("matchMedia('(width >= 52.5rem)')")
|
||||||
->toContain('x-trap.inert.noscroll="open && ! wide"')
|
->toContain('x-trap.inert.noscroll="open && ! wide"')
|
||||||
->toContain('bg-scrim/32 expanded:hidden')
|
|
||||||
->toContain('aria-label="Close"')
|
->toContain('aria-label="Close"')
|
||||||
->and((string) $this->blade('<x-drawer standard side="start" width="30rem">Body</x-drawer>'))
|
->and((string) $this->blade('<x-drawer standard side="start" width="30rem">Body</x-drawer>'))
|
||||||
->toContain('expanded:border-e expanded:border-outline-variant')
|
->toContain('data-md-side="start"')
|
||||||
->toContain('--sheet-width: min(30rem, 25rem)')
|
->toContain('--sheet-width: min(30rem, 25rem)')
|
||||||
->and((string) $this->blade('<x-drawer standard pane>Body</x-drawer>'))
|
|
||||||
->toContain('data-pane')
|
|
||||||
->not->toContain('data-standard')
|
|
||||||
->and((string) $this->blade('<x-drawer title="Filters">Body</x-drawer>'))
|
->and((string) $this->blade('<x-drawer title="Filters">Body</x-drawer>'))
|
||||||
->not->toContain('data-standard')
|
->not->toContain('data-md-standard');
|
||||||
->not->toContain('expanded:hidden');
|
|
||||||
|
expect(File::get(__DIR__.'/../../../resources/css/components/drawer.css'))
|
||||||
|
->toContain('[data-md-drawer][data-md-standard]')
|
||||||
|
->toContain('[data-md-drawer][data-md-standard][data-md-drawer-collapsed]')
|
||||||
|
->toContain('[data-md-drawer][data-md-standard] [data-md-drawer-sheet][data-md-side=\'end\']')
|
||||||
|
->toContain('[data-md-drawer][data-md-standard] [data-md-drawer-sheet][data-md-side=\'start\']')
|
||||||
|
->toContain('box-shadow: none;');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('draws the drawer from a stylesheet imported from the containment block, with the pane prop gone', function () {
|
||||||
|
$view = File::get(__DIR__.'/../../../resources/views/components/drawer.blade.php');
|
||||||
|
|
||||||
|
expect($view)
|
||||||
|
->not->toContain("'pane'")
|
||||||
|
->not->toContain('paneWidth')
|
||||||
|
->not->toContain('paneCloseOnEscape')
|
||||||
|
->not->toContain('pane-width')
|
||||||
|
->not->toContain('pane-close-on-escape')
|
||||||
|
->not->toContain('data-pane')
|
||||||
|
->and(File::get(__DIR__.'/../../../resources/css/components.css'))
|
||||||
|
->toContain("@import './components/drawer.css';");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gives a bottom sheet M3\'s preset heights, cycled from the drag handle', function () {
|
it('gives a bottom sheet M3\'s preset heights, cycled from the drag handle', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user