Let the rail centre its destinations
Plan step 25 (navigation Missing): M3's "alignment: top or center; on tablets prefer center alignment for reach" had no implementation — the rail was top-aligned only. `<x-navigation-rail align="center">` centres the destinations between the header and the footer, which stay at their ends because M3 keeps the menu icon and the FAB top-aligned. `safe center` gives the top back the moment there are more destinations than fit, where centring would push the first one out of reach above the scroller. 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
cef350d739
commit
a5acbe7f6b
@@ -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` (`<x-app-shell>`'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 `<x-fab label icon>`, 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 `<x-fab label icon>`, 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: `<span class="rail-collapsed:hidden">…expanded only…</span>`, `<span class="hidden rail-collapsed:inline-flex">…collapsed only…</span>`. 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.
|
||||
- `<x-navigation-rail-item>`: the same props as `<x-navigation-bar-item>`. `<x-navigation-rail-section label="…">`: a group with a heading that shows only while the rail is expanded; it names the group for screen readers either way.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
<div
|
||||
data-navigation-rail="{{ $mode }}"
|
||||
@if ($narrow) data-width="narrow" @endif
|
||||
@if ($centred) data-align="center" @endif
|
||||
@if ($divider) data-divider @endif
|
||||
@unless ($fill) data-fill="false" @endunless
|
||||
@if ($interactive)
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
</div>
|
||||
</div>
|
||||
BLADE,
|
||||
'Collapsed, narrow and expanded rails' => <<<'BLADE'
|
||||
'Collapsed, narrow (centred) and expanded rails' => <<<'BLADE'
|
||||
<div class="flex w-full flex-wrap items-start gap-6">
|
||||
<div class="flex h-[36rem] overflow-hidden rounded-corner-lg border border-outline-variant">
|
||||
<x-navigation-rail mode="collapsed" label="Collapsed example" divider>
|
||||
@@ -81,7 +81,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex h-[36rem] overflow-hidden rounded-corner-lg border border-outline-variant">
|
||||
<x-navigation-rail mode="collapsed" width="narrow" label="Narrow example" divider>
|
||||
<x-navigation-rail mode="collapsed" width="narrow" align="center" label="Narrow example" divider>
|
||||
<x-slot:header>
|
||||
<x-fab label="Compose" icon="edit" />
|
||||
</x-slot:header>
|
||||
|
||||
@@ -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('<x-navigation-rail mode="collapsed" align="center" />'))
|
||||
->toContain('data-align="center"')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsed" />'))->not->toContain('data-align')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsed" align="middle" />'))->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 <x-fab>'s root, and both are unlayered, because what they
|
||||
// beat — the FAB's shadow, its gap, an extended FAB's minimum width — are utilities.
|
||||
|
||||
Reference in New Issue
Block a user