Package views now write <x-livewire-material::button>, so a configured prefix (which Blade spells <x-m::button>) and an application component of the same name can no longer break or shadow them. The showcase rewrites its examples to the configured prefix. Adds the README, and waits for the adaptive rail to hear a resize before the navigation tests press its menu button. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
138 lines
6.4 KiB
PHP
138 lines
6.4 KiB
PHP
{{-- An M3 side sheet: detail or controls that slide in from the edge over a scrim; on a phone it is
|
|
the whole screen. With `pane`, from `xl` it is a list-detail pane beside the list instead.
|
|
|
|
The open state is the Livewire property in `wire:model` (entangled live, so a detail held in the
|
|
URL follows a close) — a flag or an id — and closing writes back `false` or `null`. Without
|
|
`wire:model` it reads and writes `open` in the Alpine scope around it. `close()` is in 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
|
|
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,
|
|
elevation 1; `side` `end` (the default) or `start`; `width` from `sm` (a caller's `w-*` would
|
|
race the sheet's own).
|
|
|
|
As a pane (`pane`, from `xl`) nothing is covered: the page renders the drawer after its list in
|
|
an `xl:flex xl:items-start xl: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 (the sheet's width by default). 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.
|
|
|
|
maryUI's API, kept: `title`, `subtitle`, `separator`, `with-close-button`, `close-on-escape`,
|
|
`without-backdrop-close`, `right` (ignored; use `side`), and an `actions` slot. --}}
|
|
|
|
@props([
|
|
'title' => null,
|
|
'subtitle' => null,
|
|
'separator' => false,
|
|
'side' => 'end',
|
|
'right' => true,
|
|
'withCloseButton' => false,
|
|
'closeOnEscape' => true,
|
|
'withoutBackdropClose' => false,
|
|
'width' => '25rem',
|
|
'pane' => false,
|
|
'paneWidth' => null,
|
|
])
|
|
|
|
@php
|
|
$model = $attributes->wire('model')->value() ?: null;
|
|
$id = $attributes->get('id') ?? 'material-sheet-'.substr(md5($model.'|'.$title), 0, 10);
|
|
$start = $side === 'start';
|
|
@endphp
|
|
|
|
<div
|
|
x-data="{
|
|
@if ($model !== null) open: @entangle($attributes->wire('model')).live, @endif
|
|
wide: false,
|
|
close() { this.open = typeof this.open === 'boolean' ? false : null; },
|
|
@if ($pane)
|
|
init() {
|
|
const query = window.matchMedia('(min-width: 80rem)');
|
|
this.wide = query.matches;
|
|
query.addEventListener('change', (event) => this.wide = event.matches);
|
|
},
|
|
@endif
|
|
}"
|
|
@if ($closeOnEscape) x-on:keydown.window.escape="if (open && ! wide) close()" @endif
|
|
data-sheet="{{ $id }}"
|
|
@if ($pane)
|
|
x-bind:class="! open && 'xl:hidden'"
|
|
class="xl:sticky xl:top-[calc(env(safe-area-inset-top)+1.25rem)] xl:shrink-0 xl:self-start"
|
|
data-pane
|
|
@endif
|
|
>
|
|
<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', 'xl:hidden' => $pane])
|
|
aria-hidden="true"
|
|
></div>
|
|
|
|
<aside
|
|
x-cloak
|
|
x-show="open"
|
|
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="{{ $pane ? ($start ? 'max-xl:-translate-x-full xl:opacity-0' : 'max-xl:translate-x-full xl: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="{{ $pane ? ($start ? 'max-xl:-translate-x-full xl:opacity-0' : 'max-xl:translate-x-full xl:opacity-0') : ($start ? '-translate-x-full' : 'translate-x-full') }}"
|
|
id="{{ $id }}"
|
|
x-bind:role="wide ? 'region' : 'dialog'"
|
|
x-bind:aria-modal="wide ? null : 'true'"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
@if (filled($title)) aria-labelledby="{{ $id }}-title" @endif
|
|
style="--sheet-width: {{ $width }}; --pane-width: {{ $paneWidth ?? $width }}"
|
|
{{ $attributes->whereDoesntStartWith('wire:model')->except(['id', 'class'])->class([
|
|
'fixed 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',
|
|
'end-0 sm:rounded-s-corner-lg' => ! $start,
|
|
'start-0 sm:rounded-e-corner-lg' => $start,
|
|
'sm:w-(--sheet-width) sm:max-w-[calc(100vw-4rem)]',
|
|
'xl:relative xl:top-0 xl:z-auto xl:max-h-[calc(100dvh-2.5rem-env(safe-area-inset-top))] xl:w-(--pane-width) xl:max-w-none xl:rounded-corner-lg xl:bg-surface-container xl:shadow-none' => $pane,
|
|
$attributes->get('class'),
|
|
]) }}
|
|
>
|
|
@if (filled($title) || $withCloseButton)
|
|
<div class="mb-4">
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div class="min-w-0">
|
|
@if (filled($title))
|
|
<h2 id="{{ $id }}-title" class="type-title-lg">{{ $title }}</h2>
|
|
@endif
|
|
|
|
@if (filled($subtitle))
|
|
<p class="mt-1 type-body-md text-on-surface-variant">{{ $subtitle }}</p>
|
|
@endif
|
|
</div>
|
|
|
|
@if ($withCloseButton)
|
|
<span class="-me-3 -mt-2 inline-flex shrink-0">
|
|
<x-livewire-material::button icon="close" :tooltip-left="__('Close')" x-on:click="close()" />
|
|
</span>
|
|
@endif
|
|
</div>
|
|
|
|
@if ($separator)
|
|
<x-livewire-material::divider class="mt-4" />
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
<div class="@container flex-1">
|
|
{{ $slot }}
|
|
</div>
|
|
|
|
@isset($actions)
|
|
<div class="flex shrink-0 flex-wrap items-center justify-end gap-2 pt-6">
|
|
{{ $actions }}
|
|
</div>
|
|
@endisset
|
|
</aside>
|
|
</div>
|