Give every side sheet the close affordance M3 requires
Plan step 12, containment.md C-04. `with-close-button` defaulted to false, so a drawer could render with no exit at all once `close-on-escape` and the scrim were off. It now defaults to true, and `:with-close-button="false"` is ignored where nothing else closes the sheet: Escape off, the scrim off, or a pane, which has neither. 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
d64fb91303
commit
335ea4f4f1
@@ -479,7 +479,7 @@ Props: `title`, `subtitle`, `icon` (centred hero icon), `separator`, `persistent
|
||||
|
||||
### `<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`, `close-on-escape` (default true), `without-backdrop-close`, `actions` slot. `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, and on a pane), `close-on-escape` (default true), `without-backdrop-close`, `actions` slot. `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.
|
||||
|
||||
### `<x-bottom-sheet>`
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
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
|
||||
sheet's open/close flow or tell whether it is transient or permanent
|
||||
(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
|
||||
ignored where nothing else closes the sheet: Escape off, the scrim off, or a pane, which has
|
||||
neither.
|
||||
|
||||
maryUI's API, kept: `title`, `subtitle`, `separator`, `with-close-button`, `close-on-escape`,
|
||||
`without-backdrop-close`, `right` (ignored; use `side`), and an `actions` slot. --}}
|
||||
|
||||
@@ -34,7 +41,7 @@
|
||||
'separator' => false,
|
||||
'side' => 'end',
|
||||
'right' => true,
|
||||
'withCloseButton' => false,
|
||||
'withCloseButton' => true,
|
||||
'closeOnEscape' => true,
|
||||
'withoutBackdropClose' => false,
|
||||
'width' => '25rem',
|
||||
@@ -47,6 +54,10 @@
|
||||
$model = $attributes->wire('model')->value() ?: null;
|
||||
$id = $attributes->get('id') ?? 'material-sheet-'.substr(md5($model.'|'.$title), 0, 10);
|
||||
$start = $side === 'start';
|
||||
|
||||
// M3 requires a close affordance; the prop can only ever add one, never take away the last
|
||||
// way out of the sheet.
|
||||
$closeButton = $withCloseButton || ! $closeOnEscape || $withoutBackdropClose || ($pane && ! $paneCloseOnEscape);
|
||||
@endphp
|
||||
|
||||
<div
|
||||
@@ -105,7 +116,7 @@
|
||||
$attributes->get('class'),
|
||||
]) }}
|
||||
>
|
||||
@if (filled($title) || $withCloseButton)
|
||||
@if (filled($title) || $closeButton)
|
||||
<div class="mb-4">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
@@ -118,7 +129,7 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($withCloseButton)
|
||||
@if ($closeButton)
|
||||
<span class="-me-3 -mt-2 inline-flex shrink-0">
|
||||
<x-livewire-material::button icon="close" :tooltip-left="__('Close')" x-on:click="close()" />
|
||||
</span>
|
||||
|
||||
@@ -64,6 +64,15 @@ it('leaves a pane open on Escape unless it is asked to close then too', function
|
||||
->not->toContain('keydown.window.escape');
|
||||
});
|
||||
|
||||
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"')
|
||||
->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" 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 pane pane-close-on-escape :with-close-button="false">Body</x-drawer>'))->not->toContain('aria-label="Close"');
|
||||
});
|
||||
|
||||
it('slides a side sheet in from either edge, and is a pane from expanded when asked', function () {
|
||||
expect((string) $this->blade('<x-drawer title="Details" with-close-button>Body</x-drawer>'))
|
||||
->toContain('x-trap.inert.noscroll="open && ! wide"')
|
||||
|
||||
Reference in New Issue
Block a user