diff --git a/config/livewire-material.php b/config/livewire-material.php index 6ddfc30c..eaf4f2f2 100644 --- a/config/livewire-material.php +++ b/config/livewire-material.php @@ -34,6 +34,22 @@ return [ 'legacy_keys' => [], ], + /* + |-------------------------------------------------------------------------- + | Navigation rail + |-------------------------------------------------------------------------- + | + | Whether a collapsible navigation rail starts 'expanded' or 'collapsed' + | until the visitor toggles it. The head script applies the choice before + | the first paint, from localStorage under 'storage_key'. + | + */ + + 'rail' => [ + 'default' => 'expanded', + 'storage_key' => 'material-rail', + ], + /* |-------------------------------------------------------------------------- | Fields diff --git a/docs/plans/livewire-material.md b/docs/plans/livewire-material.md index 9e369782..43948aa8 100644 --- a/docs/plans/livewire-material.md +++ b/docs/plans/livewire-material.md @@ -524,6 +524,28 @@ worktrees. What changed from the step above: the theme script), content region with `wire:transition.navigate`, snackbar host. Nothing app-specific inside; apps pass destinations and extra chrome as slots. +**Phase 8 is done (2026-09-13).** What changed from the steps above: + +- **App bars, toolbars, tabs, section nav, account menu and theme toggle were built in main; the + navigation bar, rail and app shell by an agent in its own worktree.** +- **A medium or large app bar collapses without script moving anything**: the bar is sticky at a + negative top (its measured height less the 64px row, so a wrapped title still fits) and its row + is sticky at 0 inside it; script only reports `scrolled` and `collapsed`. +- **The tab indicator moves in a view transition** (`view-transition-name` from a per-tablist + custom property, `view-transition-class` for the timing); every tab draws its own indicator, so it + is right before Alpine starts. +- **Livewire 4.4's `wire:navigate` gives `` the next page's attributes and removes the rest**, + which dropped `data-theme` on every in-app navigation. The head script saves the theme and rail + attributes on `livewire:navigating` and puts them back in `onSwap`, before anything paints. +- **The rail's collapsed state is applied before first paint** by the head script + (``, `rail.default`, `rail.storage_key`) and read by a `rail-collapsed:` variant; + `$store.rail` changes it. The shell draws no phone menu button: the app bar in `top` calls + `$store.rail.show()`. +- **Playwright's locators are strict**: `assertAttribute` on a selector matching two elements fails. +- **Tailwind only compiles classes it can see**: a class written only in a test probe (`h-[200vh]`) + does not exist in the Workbench build; use inline styles there. +- **A Tailwind `@variant` nested under a pseudo-element or a `* +` selector compiles to broken CSS.** + ### Phase 9 — Data, pages, mail 30. `table` (`.data-table`, descendant selectors, fine-pointer density, `position: relative`), diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index a1b5e73b..449c3d07 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -535,6 +535,84 @@ M3 search bar that opens into a search view: docked under the bar from `sm`, ful The docked view overlaps what is under it; never place a search inside an element with `overflow-hidden` (a card), which clips it. +### `` + +The adaptive app shell, a whole layout's body: a navigation bar below `sm`, a collapsed rail that opens as a modal to `lg`, an expanded rail the visitor can collapse from `lg`, the page as `
` behind a skip link, and the snackbar host (do not add another ``). It needs `` in ``. + +```blade + + SealShare + + + + + + + + + {{-- the page's app bar; its menu button opens the modal rail on a phone --}} + + + + {{ $slot }} + +``` + +- `destinations`: `title`, `icon`, `url`; optional `active` (default: the URL is the current one), `badge` (`true` for a dot, or a count), `section` (a heading in the rail, shown only while it is expanded; consecutive destinations with the same section are grouped), `bar` (default `true`; `false` keeps it out of the bottom bar — M3 wants three to five there), `navigate` (`false` for a full page load instead of `wire:navigate`). +- Slots, each rendered once: `brand` (beside the rail's menu button, expanded only), `rail-header` (a FAB), `rail-footer` (pinned to the foot of the rail), `actions` (a row of icon buttons at the very foot, stacked when collapsed), `top` (the app bar, above the page at every width), and the page. `label` names the landmarks ("Main"); `rail-width` is the expanded width (`16rem`). +- The rail is one element at every width: what is in it is also what a phone sees in the modal rail. Below `sm` nothing opens it but `$store.rail.show()`, so a page whose destinations are not all in the bar needs a menu button in its app bar (hidden from `sm`). +- Below `sm` the shell sets `--material-bottom-bar`, so the snackbar and a `fab` button clear the bar; pad anything else you pin to the bottom with it. +- The content region is `max-lg:overflow-x-clip`. Never make a page wrapper `overflow-x-hidden`: it turns the region into a scroll container and breaks every `sticky` inside. + +### ``, `` + +M3 Expressive's flexible navigation bar, for three to five destinations. It does not position itself; wrap it (`` does): + +```blade +
+ + + + +
+``` + +64px in surface-container with the bottom safe area under it. Narrower than 600px the icon sits in a 56×32 indicator over the label; from 600px (the bar's own width) icon and label share a 40px pill and the items gather in the middle. ``: `label` ("Main"). ``: `label` / slot, `icon`, `link` (with `wire:navigate` unless `external` or `no-wire-navigate`; without a link it is a button), `active` (`aria-current="page"`, filled icon, secondary-container indicator), `badge` (`true` for a dot, a number for a count, 999+ at most), `badge-label` (what a screen reader hears instead of ", 3"). + +### ``, ``, `` + +M3 Expressive's navigation rail: collapsed (96px, icon over label) or expanded (a 56px full-width pill, icon beside label, count at the end). + +```blade +
+ + SealShare + + + + + + + + + + + + +
+
+``` + +- `mode`: `collapsed`, `expanded`, `collapsible` (default: expanded until its menu button collapses it; the choice is `$store.rail`, remembered and applied before the first paint), `modal` (collapsed in the layout; the menu button or `$store.rail.show()` opens it expanded over a scrim, focus held until Escape, the scrim or leaving the page), `adaptive` (``'s: hidden and opened as a modal below `sm`, collapsed and opened as a modal to `lg`, collapsible from `lg`). +- Props: `label` ("Main"), `width` (expanded width, `16rem`, held between 220 and 360px), `menu` (the menu button; on by default for `collapsible`, `modal`, `adaptive`). Slots: `brand` (beside the menu button, expanded only), `header` (a FAB), the destinations (the only part that scrolls), `footer`. In a flex row the rail sticks to the top of the viewport. +- Anything inside a rail takes both shapes with the `rail-collapsed:` variant, true while that rail is drawn collapsed for whatever reason: `…expanded only…`, ``. Put the variant on a wrapper, never on a component. Nothing that shows while collapsed may be wider than 96px. +- ``: the same props as ``. ``: a group with a heading that shows only while the rail is expanded; it names the group for screen readers either way. +- `$store.rail`: `collapsed`, `toggle()`, `collapse()`, `expand()` (the remembered choice), `open`, `show()`, `hide()` (the modal rail; closed on every `wire:navigate`). `config/livewire-material.php` → `rail.default` (`expanded` or `collapsed`) and `rail.storage_key` (`material-rail`). + ### `` M3 Expressive top app bar, sticky by default (`:sticky="false"` to scroll away), turning surface-container once content scrolls under it. `variant`: `small` (default), `center`, `medium` and `large` (a big title that collapses into the row as the page scrolls — CSS sticky, no layout shift), `search` (put an `` in the slot). Props: `title`, `subtitle`, `heading` (`h1` default). Slots: `navigation` (leading icon button), `actions` (trailing icon buttons, avatar). diff --git a/resources/css/components/navigation.css b/resources/css/components/navigation.css new file mode 100644 index 00000000..3c83990c --- /dev/null +++ b/resources/css/components/navigation.css @@ -0,0 +1,584 @@ +/* + * M3 Expressive navigation: the flexible navigation bar and the navigation rail — collapsed, + * expanded and modal. + * + * Values from androidx Compose Material 3 (Apache-2.0) at androidx-main + * 27cf9a7d5788aa0f5f2d8b6699ce279560daf326: tokens/NavigationBarTokens.kt, + * NavigationBarVerticalItemTokens.kt, NavigationBarHorizontalItemTokens.kt, + * NavigationRailCollapsedTokens.kt, NavigationRailExpandedTokens.kt, + * NavigationRailBaselineItemTokens.kt, NavigationRailVerticalItemTokens.kt, + * NavigationRailHorizontalItemTokens.kt, NavigationRailColorTokens.kt, and the layout in + * ShortNavigationBar.kt, WideNavigationRail.kt and NavigationItem.kt. + * + * [data-navigation-bar] surface-container, 64px, the bottom safe area under it + * [data-navigation-bar-items] equal widths; centred from a 600px-wide bar + * [data-navigation-bar-item] data-active + * [data-navigation-pill] icon and label; the indicator itself from 600px + * [data-navigation-indicator] the 56×32 indicator around the icon below 600px + * [data-navigation-label] + * + * [data-navigation-rail="collapsed|expanded|collapsible|modal|adaptive"] data-open + * [data-navigation-rail-scrim] modal and adaptive rails + * [data-navigation-rail-panel] the