Give the app shell one shape per M3 window size class
Plan step 17, on N-06, N-07 and C-07. The shell now changes at 600, 840 and 1200 and nowhere else: a compact window keeps the navigation bar and the modal rail; `medium` (600-839) gets the collapsed rail in the layout and no bar; `expanded` (840-1199) gets a standard rail, collapsed, whose menu button expands it in place rather than over a scrim; `large` and above start it expanded, which is what M3 prefers once there is room. `data-rail` alone could not say "collapsed at expanded, expanded at large", since it carries `rail.default` for a visitor who never chose. <x-theme-script> now also writes `data-rail-auto` while nothing is stored, the `rail-collapsed:` variant reads it in the 840-1199 band, and `$store.rail.auto` mirrors it for Alpine; the first press of the menu button drops it, so a remembered choice still wins in both bands. `rail.default` and the rest of `$store.rail` are unchanged, and the attribute rides through `wire:navigate` with the others. `--material-margin` carries M3's window margin on the shell -- 16px compact, 24px from `medium` -- and the content region is padded with it, so the showcase pages drop their own gutters. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
17723d2a76
commit
fd1e063d4c
@@ -11,13 +11,31 @@
|
||||
…the page…
|
||||
</x-app-shell>
|
||||
|
||||
- Below `sm`: 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 `sm`:
|
||||
`<span class="sm:hidden"><x-button icon="menu" tooltip="Open navigation" x-on:click="$store.rail.show()" /></span>`.
|
||||
- `sm` to `lg`: the collapsed rail, whose menu button opens it expanded, as a modal.
|
||||
- From `lg`: the expanded rail, collapsed and expanded again by its menu button; the choice is
|
||||
remembered and applied before the first paint (`$store.rail`, <x-theme-script>).
|
||||
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`:
|
||||
`<span class="medium:hidden"><x-button icon="menu" tooltip="Open navigation" x-on:click="$store.rail.show()" /></span>`.
|
||||
- **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`, <x-theme-script>), 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`: `<x-drawer pane>` 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
|
||||
@@ -37,15 +55,15 @@
|
||||
|
||||
The page is `<main id="content">` with `wire:transition.navigate`, behind a skip link that is
|
||||
the first thing a keyboard reaches. The snackbar host (`<x-toast />`) is part of the shell;
|
||||
below `sm` it, and a `fab` button, sit above the bottom bar through `--material-bottom-bar`:
|
||||
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-lg:overflow-x-clip` on the content region is the backstop under every page, and it stays
|
||||
`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
|
||||
`lg` only, so a wide window never clips what overhangs on purpose.
|
||||
`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. --}}
|
||||
@@ -84,8 +102,8 @@
|
||||
<div
|
||||
data-app-shell
|
||||
@class([
|
||||
'min-h-dvh bg-surface text-on-surface sm:flex',
|
||||
'max-sm:[--material-bottom-bar:calc(4rem+var(--material-safe-bottom,env(safe-area-inset-bottom))+var(--material-bottom-extra,0px))]' => $barItems->isNotEmpty(),
|
||||
'min-h-dvh bg-surface text-on-surface [--material-margin:1rem] medium:flex medium:[--material-margin:1.5rem]',
|
||||
'max-medium:[--material-bottom-bar:calc(4rem+var(--material-safe-bottom,env(safe-area-inset-bottom))+var(--material-bottom-extra,0px))]' => $barItems->isNotEmpty(),
|
||||
])
|
||||
>
|
||||
<a
|
||||
@@ -133,13 +151,13 @@
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
{{ $top ?? '' }}
|
||||
|
||||
<main id="content" tabindex="-1" wire:transition.navigate class="min-w-0 flex-1 outline-none max-lg:overflow-x-clip max-sm:pb-(--material-bottom-bar)">
|
||||
<main id="content" tabindex="-1" wire:transition.navigate class="min-w-0 flex-1 px-(--material-margin) outline-none max-expanded:overflow-x-clip max-medium:pb-(--material-bottom-bar)">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@if ($barItems->isNotEmpty())
|
||||
<div data-app-shell-bar class="fixed inset-x-0 bottom-0 z-30 sm:hidden">
|
||||
<div data-app-shell-bar class="fixed inset-x-0 bottom-0 z-30 medium:hidden">
|
||||
<x-livewire-material::navigation-bar :label="$label">
|
||||
@foreach ($barItems as $item)
|
||||
<x-livewire-material::navigation-bar-item :label="$item['title']" :icon="$item['icon']" :link="$item['url']" :active="$item['active']" :badge="$item['badge']" :badge-label="$item['badgeLabel']" :no-wire-navigate="! $item['navigate']" />
|
||||
|
||||
Reference in New Issue
Block a user