diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index 4b9b8380..3c4da5c9 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -766,7 +766,7 @@ M3 Expressive's navigation rail: collapsed (96px, icon over label) or expanded ( ``` - `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, one rail per window size class: hidden and opened as a modal on a compact window, collapsed and opened as a modal at `medium`, a standard rail from `expanded` — collapsed there, expanded from `large`). -- Props: `label` ("Main"), `width` (expanded width, `16rem`, held between 220 and 360px — or the word `narrow` for M3's other *collapsed* width, 80px against the default 96, where the items are their icons alone; the labels stay in the accessibility tree and a narrow rail still expands to 16rem), `menu` (the menu button; on by default for `collapsible`, `modal`, `adaptive`), `divider` (M3's optional vertical divider on the page's side — use it when the page scrolls under a fixed rail), `fill` (`false` for a transparent container, which M3 allows while the items keep 3:1 contrast). Slots: `brand` (beside the menu button, expanded only), `header` (one ``, which the rail morphs into an extended FAB and back as it expands — it also rests at elevation 0, as M3 asks of a nested FAB), the destinations (the only part that scrolls), `footer`. In a flex row the rail sticks to the top of the viewport. +- Props: `label` ("Main"), `width` (expanded width, `16rem`, held between 220 and 360px — or the word `narrow` for M3's other *collapsed* width, 80px against the default 96, where the items are their icons alone; the labels stay in the accessibility tree and a narrow rail still expands to 16rem), `align` (`top` default, or `center` for M3's centred destinations — preferred on a tablet; the menu button, brand and FAB stay at the top and the footer at the foot), `menu` (the menu button; on by default for `collapsible`, `modal`, `adaptive`), `divider` (M3's optional vertical divider on the page's side — use it when the page scrolls under a fixed rail), `fill` (`false` for a transparent container, which M3 allows while the items keep 3:1 contrast). Slots: `brand` (beside the menu button, expanded only), `header` (one ``, which the rail morphs into an extended FAB and back as it expands — it also rests at elevation 0, as M3 asks of a nested FAB), the destinations (the only part that scrolls), `footer`. In a flex row the rail sticks to the top of the viewport. - Anything else 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. - A `collapsible` rail is held to the collapsed 96px below `medium` (600px), where M3 says to use a navigation bar rather than a standard rail. `collapsed` and `expanded` are fixed-width by design: wrap one in a `medium:` element if it must not show on a phone. - ``: 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. diff --git a/resources/css/components/navigation.css b/resources/css/components/navigation.css index 9bf3bcb8..d646b646 100644 --- a/resources/css/components/navigation.css +++ b/resources/css/components/navigation.css @@ -443,6 +443,16 @@ padding-block: 0.5rem 1rem; } + /* "Alignment: top or center; on tablets prefer center alignment for reach. Menu icon and FAB + are always top-aligned" (docs/reference/m3/components-navigation-selection-inputs.md + § Navigation rail). Only the destinations move, then — the header and the footer stay at + their ends of the rail — and `safe` puts them back at the top the moment there are more of + them than fit, where centring would otherwise push the first one out of reach above the + scroller. */ + [data-navigation-rail][data-align='center'] [data-navigation-rail-destinations] { + justify-content: safe center; + } + [data-navigation-rail-heading] { display: flex; align-items: center; diff --git a/resources/views/components/navigation-rail.blade.php b/resources/views/components/navigation-rail.blade.php index a96cf67f..8db0c46d 100644 --- a/resources/views/components/navigation-rail.blade.php +++ b/resources/views/components/navigation-rail.blade.php @@ -56,7 +56,11 @@ (NavigationRailCollapsedTokens.NarrowContainerWidth, 80px against the default 96), where the items are their icons alone because no label fits under a 56px indicator at that width; the labels stay in the accessibility tree, since they are what name the destinations, and a - narrow rail expands to the default 16rem; `menu` shows the menu button (by default for `collapsible`, + narrow rail expands to the default 16rem; `align` is `top` (the default) or `center`, which + puts the destinations at the rail's vertical centre — M3 prefers that on a tablet, for reach — + while the menu button, the brand and the FAB stay at the top and the footer at the foot, as M3 + asks; more destinations than fit go back to the top rather than out of reach above the + scroller; `menu` shows the menu button (by default for `collapsible`, `modal` and `adaptive`); `divider` draws M3's optional vertical divider on the edge the page is on — which is also what M3 asks for when a page scrolls underneath a fixed rail; `fill` (`false`) drops the container colour for a transparent rail over the page's own background, @@ -78,6 +82,7 @@ 'mode' => 'collapsible', 'label' => null, 'width' => '16rem', + 'align' => 'top', 'menu' => null, 'divider' => false, 'fill' => true, @@ -91,11 +96,13 @@ $collapsedAtFirst = in_array($mode, ['collapsed', 'modal'], true); $narrow = $width === 'narrow'; $width = $narrow ? '16rem' : $width; + $centred = $align === 'center'; @endphp
BLADE, - 'Collapsed, narrow and expanded rails' => <<<'BLADE' + 'Collapsed, narrow (centred) and expanded rails' => <<<'BLADE'
@@ -81,7 +81,7 @@
- + diff --git a/tests/Feature/Components/NavigationRailTest.php b/tests/Feature/Components/NavigationRailTest.php index 2f073c51..ce2b0f8d 100644 --- a/tests/Feature/Components/NavigationRailTest.php +++ b/tests/Feature/Components/NavigationRailTest.php @@ -91,6 +91,17 @@ it('takes M3\'s narrow collapsed width, icons alone but still named', function ( ->toContain("[data-navigation-rail][data-width='narrow'] [data-navigation-rail-item] [data-navigation-label] {"); }); +it('centres the destinations on request, leaving the header and footer at their ends', function () { + expect((string) $this->blade('')) + ->toContain('data-align="center"') + ->and((string) $this->blade(''))->not->toContain('data-align') + ->and((string) $this->blade(''))->not->toContain('data-align'); + + expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css')) + // Only the destinations move, and `safe` gives the top back when there are too many. + ->toMatch("/\\[data-navigation-rail\\]\\[data-align='center'\\] \\[data-navigation-rail-destinations\\] \\{\\s+justify-content: safe center;/"); +}); + it('flattens a FAB nested in the rail header and morphs its label', function () { // Both rules hang off `data-fab` on 's root, and both are unlayered, because what they // beat — the FAB's shadow, its gap, an extended FAB's minimum width — are utilities.