{{-- The adaptive app shell: navigation that changes shape with the window, around the page. SealShare …the page's app bar… …the page… The navigation is M3's per window size class (docs/reference/m3/foundations.md § Layout and foundations-supplement.md § Breakpoints), and only those four numbers: - **Compact**, below `medium` (600px): a navigation bar with the destinations marked `bar`, pinned to the bottom. Everything else is in the modal rail, which slides in when something calls `$store.rail.show()` — put a menu button in the app bar for it, hidden from `medium`: ``. - **Medium** (600–839): the collapsed rail in the layout, 96px, and no bar; its menu button opens it expanded over a scrim, since 256px beside the page would leave the page too little. - **Expanded** (840–1199): a standard rail — in the layout, nothing covered — collapsed until the menu button expands it in place. - **Large and extra-large** (from 1200): the same standard rail, expanded to begin with (`rail.default`), which is what M3 prefers once there is room. From `expanded` the choice the menu button makes is remembered and applied before the first paint (`$store.rail`, ), so the rail never paints one width and snaps to the other. A visitor who has chosen keeps that choice in both bands; one who never has gets the class's own default. `--material-margin` is M3's window margin — 16px on a compact window, 24px from `medium` — and the content region is padded with it, so a page inside the shell writes no gutters of its own. Something meant to reach the window's edges opts out with `-mx-(--material-margin)`. Two panes side by side are M3's from `expanded`: `` is the second one, 360dp wide, in an `expanded:flex expanded:items-start expanded:gap-6` row inside the page. `destinations` is a list of arrays: `title`, `icon` (a Material Symbol), `url`, and optionally `active` (by default: the URL is the page's; during a Livewire update request, the page the component was rendered on rather than the update endpoint), `badge` (`true` for a dot, or a count), `badgeLabel` (what a screen reader hears for the badge instead: "3 unread"), `section` (a heading the destination is grouped under in the rail; only an expanded rail shows it), `bar` (`false` keeps it out of the bottom bar; M3 wants three to five there) and `navigate` (`false` for a full page load instead of `wire:navigate`). Slots, each rendered once: `banner` (a bar across the whole window, above the rail and the page — M3's scaffold is bars, then rails, then panes), `brand` (beside the rail's menu button while it is expanded), `rail-header` (under it: one ``, which the rail morphs between a FAB and an extended FAB as it opens), `rail-footer` (at the foot of the rail: footer destinations, an account), `actions` (a row of icon buttons at the very foot, stacked when the rail is collapsed: a theme toggle, sign out), `top` (the page's own bar, above the page and *beside* the rail) and the page itself. The rail is one element at every width, so what is in it is also in the modal rail a phone opens. `label` names both navigation landmarks ("Main"); `rail-width` is the expanded rail's width. `banner` or `top` is a decision about what the bar belongs to: an application-wide bar — one search, one account menu, the same on every page — spans the window and the rail starts under it; a bar that titles the page belongs to the page, beside the rail. Put an app bar in one or the other, never both. A banner that pins itself to the top of the window says how tall it is — `style="--material-banner: 4rem"` on `` — so the rail sticks under it instead of behind it. The page is `
` with `wire:transition.navigate`, behind a skip link that is the first thing a keyboard reaches. The snackbar host (``) is part of the shell; on a compact window it, and a `fab` button, sit above the bottom bar through `--material-bottom-bar`: the bar's 64px, the bottom safe area (`--material-safe-bottom`, else the device's inset) and `--material-bottom-extra` (0px unless the application docks something, an offline banner, on top of the bar). `max-expanded:overflow-x-clip` on the content region is the backstop under every page, and it stays `clip`: `overflow-x: hidden` would force `overflow-y` to `auto`, turn the region into a scroll container and break every `position: sticky` inside it (an app bar, a list-detail pane). Below `expanded` only, so a wide window never clips what overhangs on purpose. Nothing application-specific belongs in here: an app's destinations and chrome come in through the props and slots. --}} @props([ 'destinations' => [], 'label' => null, 'railWidth' => '16rem', ]) @php $label ??= __('Main'); $current = \Livewire\Livewire::isLivewireRequest() ? \Livewire\Livewire::originalUrl() : request()->url(); $items = collect($destinations) ->filter(fn ($item): bool => is_array($item) && filled($item['title'] ?? null)) ->map(fn (array $item): array => [ 'title' => (string) $item['title'], 'icon' => $item['icon'] ?? null, 'url' => $item['url'] ?? null, 'active' => (bool) ($item['active'] ?? (filled($item['url'] ?? null) && rtrim(url($item['url']), '/') === rtrim($current, '/'))), 'badge' => $item['badge'] ?? null, 'badgeLabel' => filled($item['badgeLabel'] ?? null) ? (string) $item['badgeLabel'] : null, 'section' => filled($item['section'] ?? null) ? (string) $item['section'] : null, 'bar' => ($item['bar'] ?? true) !== false, 'navigate' => ($item['navigate'] ?? true) !== false, ]) ->values(); $barItems = $items->where('bar', true)->values(); // Consecutive destinations under the same heading form one group, in the order given. $groups = $items->chunkWhile(fn (array $item, int $key, $chunk): bool => $item['section'] === $chunk->last()['section']); @endphp
$barItems->isNotEmpty(), ]) > {{ __('Skip to content') }} @isset($banner)
{{ $banner }}
@endisset
@isset($brand) {{ $brand }} @endisset @isset($railHeader) {{ $railHeader }} @endisset @foreach ($groups as $group) @if ($group->first()['section'] !== null) @foreach ($group as $item) @endforeach @else @foreach ($group as $item) @endforeach @endif @endforeach @if (isset($railFooter) || isset($actions)) {{ $railFooter ?? '' }} @isset($actions)
{{ $actions }}
@endisset
@endif
{{ $top ?? '' }}
{{ $slot }}
@if ($barItems->isNotEmpty())
@foreach ($barItems as $item) @endforeach
@endif