Add the list-detail canonical layout
Plan step 35: <x-list-detail>, following the reference's visible-panes table row by row - one pane below expanded, the detail replacing the list with a back button once something is selected; from 840px the list a fixed 360px (412px from 1200px) beside the detail, 24px apart. The selection binds with wire:model or x-model; below expanded focus moves to the detail and back() returns it to the item (resources/js/layout.js). Grid columns mirror in RTL, and the back arrow turns with them. 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
b4f1e005de
commit
b5c5be1fd7
@@ -0,0 +1,106 @@
|
||||
{{-- M3's list-detail canonical layout: a list, and the detail of what is selected in it.
|
||||
|
||||
<x-list-detail wire:model.live="selectedId">
|
||||
<x-slot:list>
|
||||
<x-pane title="Inbox">
|
||||
<x-list>… <x-list-item wire:click="$set('selectedId', {{ $message->id }})" …/> …</x-list>
|
||||
</x-pane>
|
||||
</x-slot:list>
|
||||
<x-slot:detail>
|
||||
<x-pane :title="$message?->subject" heading="h2" back>…</x-pane>
|
||||
</x-slot:detail>
|
||||
</x-list-detail>
|
||||
|
||||
Parent and child content: an inbox and a message, folders and a file, settings and a category
|
||||
(docs/reference/m3/foundations-supplement.md § Canonical layouts → List-detail). Its
|
||||
visible-panes table, row by row: compact (below 600px) shows 1 pane; medium (600–839) 1 or 2,
|
||||
and this layout shows 1, M3's recommendation for anything but low-density content; expanded
|
||||
(840+), large (1200–1599) and extra-large (1600+) show 2.
|
||||
|
||||
So below 840px the list shows until something is selected, then the detail replaces it with
|
||||
a back button; from 840px the list is a fixed pane, 360px wide (412px from 1200px, M3's
|
||||
fixed-pane widths at expanded and at large, § Breakpoints), the detail takes the rest, 24px
|
||||
apart, and no back button shows. Resizing moves between the two as M3 describes ("rotating
|
||||
from expanded to medium collapses two panes back to one"). A right-to-left document puts the
|
||||
list on the right and turns the back arrow.
|
||||
|
||||
`selected` is what is selected, bound with `wire:model` (the Livewire property, whose value
|
||||
the server also renders from, so the first paint shows the right pane) or `x-model`, or given
|
||||
once as `selected`. Nothing selected is `null`, `false` or `''`. Inside both slots `selected`
|
||||
is in Alpine scope, and `back()` clears it (a boolean to `false`, anything else to `null`).
|
||||
A list item can select with `wire:click="$set('selectedId', 7)"` or `x-on:click="selected = 7"`.
|
||||
M3 shows the list's selected state only where both panes show; give the item `aria-current`
|
||||
for it, which also tells `back()` where to return focus.
|
||||
|
||||
The back button: `<x-pane back>` puts it in the detail pane's own app bar. Without one in the
|
||||
detail slot, the layout draws its own row with it above the detail. Either way it is hidden
|
||||
from 840px.
|
||||
|
||||
Focus (resources/js/layout.js): below 840px a selection moves focus to the detail pane, and
|
||||
`back()` returns it to the list item it came from — or the item marked `aria-current` or
|
||||
`aria-selected`, or the list — as a dialog returns focus to its trigger (WAI-ARIA APG). From
|
||||
840px focus stays where it is, since both panes are on screen.
|
||||
|
||||
The layout keeps M3's margin (16px below medium, 24px from it) unless it sits inside something
|
||||
that already does, such as `<x-scaffold>`'s content region. It takes `as`, `hide-below` and
|
||||
`hide-from` like every layout component, and the caller's `class` and `style` land on it
|
||||
untouched. Its own attributes belong to Alpine (`wire:ignore.self`), while both slots morph as
|
||||
usual. Drawn by resources/css/layout/list-detail.css. --}}
|
||||
|
||||
@props([
|
||||
'as' => null,
|
||||
'selected' => null,
|
||||
'hideBelow' => null,
|
||||
'hideFrom' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$layout = \NoNameWeb\LivewireMaterial\Support\Layout::class;
|
||||
$element = $layout::element($as);
|
||||
$model = $attributes->wire('model')->value() ?: null;
|
||||
$initial = $selected;
|
||||
|
||||
if ($model !== null && ($component = \Livewire\Livewire::current()) !== null) {
|
||||
$initial = data_get($component, $model);
|
||||
}
|
||||
|
||||
$chosen = $initial !== null && $initial !== false && $initial !== '';
|
||||
$wire = $attributes->wire('model');
|
||||
$detailSlot = $detail ?? null;
|
||||
$ownBack = $detailSlot !== null && str_contains((string) $detailSlot, 'data-md-list-detail-back');
|
||||
|
||||
$attributes = $attributes->whereDoesntStartWith('wire:model')->merge(array_filter([
|
||||
'data-md-list-detail' => true,
|
||||
'data-md-selected' => $chosen ? true : null,
|
||||
] + $layout::visibility($hideBelow, $hideFrom), fn ($value): bool => $value !== null));
|
||||
@endphp
|
||||
|
||||
<{{ $element }}
|
||||
x-data="materialListDetail(@if ($model !== null) @entangle($wire) @else @js($initial) @endif)"
|
||||
@if ($model === null) x-modelable="selected" @endif
|
||||
x-bind:data-md-selected="hasSelection ? '' : null"
|
||||
x-on:focusin="track($event)"
|
||||
x-on:focusout="track($event)"
|
||||
wire:ignore.self
|
||||
{{ $attributes }}
|
||||
>
|
||||
<div
|
||||
data-md-list-detail-pane="list"
|
||||
tabindex="-1"
|
||||
x-ref="list"
|
||||
x-on:focusin="remember($event)"
|
||||
x-on:click="remember($event)"
|
||||
>{{ $list ?? '' }}</div>
|
||||
|
||||
<div data-md-list-detail-pane="detail" tabindex="-1" x-ref="detail">
|
||||
@unless ($ownBack)
|
||||
<div data-md-list-detail-back-row>
|
||||
<span data-md-list-detail-back>
|
||||
<x-livewire-material::button icon="arrow_back" :tooltip="__('Back')" x-on:click="back()" />
|
||||
</span>
|
||||
</div>
|
||||
@endunless
|
||||
|
||||
{{ $detailSlot ?? '' }}
|
||||
</div>
|
||||
</{{ $element }}>
|
||||
@@ -114,6 +114,7 @@
|
||||
|
||||
<x-livewire-material::stack as="ul" gap="space100" class="md-type-body-md">
|
||||
<li><code><x-pane></code> — a content region with M3's margins and its own app bar.</li>
|
||||
<li><code><x-list-detail></code> — a list and the detail of its selection: one pane below 840px, two from it.</li>
|
||||
<li><code><x-surface></code> — a tonal region: a surface role, padding, a corner and an outline.</li>
|
||||
<li><code><x-stack></code>, <code><x-row></code> and <code><x-grid></code> — arrangement inside a pane.</li>
|
||||
</x-livewire-material::stack>
|
||||
|
||||
Reference in New Issue
Block a user