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:
Andreas Reinhold / reini
2026-09-14 14:51:13 +02:00
co-authored by Claude Opus 5
parent b4f1e005de
commit b5c5be1fd7
9 changed files with 578 additions and 0 deletions
@@ -802,6 +802,39 @@ A content region: M3 puts all content in panes, and each may carry its own top a
- The bar's leading button: the `leading` slot, or `back` — a URL makes it a link, `true` calls `back()` from `<x-list-detail>` around it (hidden there where both panes show). The arrow mirrors in a right-to-left document.
- `navigation` is the pane's section navigation (`<x-section-nav>`, `<x-tabs>`), under the bar and above the body with the body's margins — not the bar's leading button.
#### `<x-list-detail>`
M3's list-detail canonical layout, for parent and child content: an inbox and a message, folders and a file, settings and a category. `list` and `detail` slots.
| Breakpoint | Visible panes |
| --- | --- |
| Compact, below 600px | 1: the list, or the detail once something is selected, with a back button |
| Medium, 600839 | 1, as compact (M3's recommendation) |
| Expanded, 8401199 | 2: the list 360px, the detail the rest, 24px apart, no back button |
| Large and extra-large, from 1200 | 2: the list 412px |
```blade
<x-list-detail wire:model.live="messageId">
<x-slot:list>
<x-pane title="Inbox">
<x-list>
@foreach ($messages as $message)
<x-list-item :title="$message->subject" :link="route('messages.show', $message)" no-wire-navigate wire:click.prevent="$set('messageId', {{ $message->id }})" :selected="$message->id === $messageId" wire:key="message-{{ $message->id }}" />
@endforeach
</x-list>
</x-pane>
</x-slot:list>
<x-slot:detail>
<x-pane :title="$current?->subject" heading="h2" back>…</x-pane>
</x-slot:detail>
</x-list-detail>
```
- `selected` is what is selected: bound with `wire:model` (the server renders the right pane from the property, so the first paint is right) or `x-model`, or given once. Nothing selected is `null`, `false` or `''`; `0` is an id. Inside both slots `selected` is in Alpine scope, and `back()` clears it (a boolean to `false`, anything else to `null`). An item selects with `wire:click.prevent="$set('messageId', 7)"` or `x-on:click.prevent="selected = 7"` on a list item whose `link` (with `no-wire-navigate`) opens the same detail without script, so the keyboard reaches it.
- The back button: `<x-pane back>` in the detail slot puts it in that pane's app bar; without one, the layout draws its own row above the detail. Hidden from `expanded` either way.
- Focus: below `expanded`, selecting moves focus to the detail pane and `back()` returns it to the item it came from (or the list's `aria-current`/`aria-selected` item, or the list). From `expanded` focus stays where it is. Mark the selected item (`:selected`, `aria-current`) — M3 shows a selected state in the list where both panes show.
- A right-to-left document puts the list on the right. The root's attributes belong to Alpine (`wire:ignore.self`); the slots morph as usual.
#### `<x-surface>`
A tonal region: `<x-surface level="surface-container-low" padding="space300" corner="lg" outlined>…</x-surface>`.