Draw the navigation rail without Tailwind
Plan step 36 (navigation group, second batch): <x-navigation-rail>'s, <x-navigation-rail-item>'s and <x-navigation-rail-section>'s class lists move into navigation-rail.css, navigation-rail-item.css and navigation-rail-section.css, keyed on data-md-navigation-rail (the mode, data-md-width, data-md-align, data-md-hide-when-collapsed, data-md-divider, data-md-fill, data-md-open), data-md-navigation-rail- item (data-md-active) and data-md-navigation-rail-section. Every Tailwind wrapper class in the view — the menu row's centring padding, the FAB row, the two swapped menu glyphs, the brand's visibility — becomes a hook the stylesheet draws instead; the menu button itself renders the shared md-state-layer/md-focus-ring/md-touch-target classes (N-01's pattern) since it draws its own layer on itself, not a child. `<x-icon>` and `<x-badge>` take size and floating props instead of size/position classes; a small data-md-navigation-icon hook (the shared navigation-item.css) replaces the ad hoc "relative inline-flex" wrapper a floating badge anchors to. `rail-collapsed` (a Tailwind @custom-variant, forbidden in Phase F) is reproduced as plain selectors, branch for branch: the three width- independent conditions (a fixed collapsed mode; a collapsible rail the visitor collapsed and not open; a modal rail not open) merge into one :where() group, provably the same match set as three separate rules since :where(A, B, C) on an element is true exactly when :where(A) or :where(B) or :where(C) is; the four width-gated conditions stay separate media blocks, since CSS cannot merge different `@media` queries. Every rem length becomes px, since these are dp-based M3 tokens, not a text measure (unlike <x-pane>'s rem widths). The FAB overrides for a rail's header — elevation 0 (N-03), morphing into an extended FAB instead of swapping two by display (N-23) — move from unlayered into this file's own material.components, like toolbar.css's FAB override: fab.css's `[data-md-fab]` is one attribute, so a doubled selector here always outranks it without needing to sit outside the layer. navigation.js: the arriving-indicator stylesheet and every code comment follow the new hooks; resources/css/components/navigation.css is deleted (nothing imports it any more) and its line in tailwind.css with it. Behaviour is unchanged except one thing Tailwind's `rail-collapsed:` variant could do that plain CSS in this shape cannot: it is gone for consuming applications too, since the definition lived only in the file this commit removes. The development skill's guidance for it is rewritten to point at navigation-rail.css's own selectors instead of teaching a Tailwind variant that no longer exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
63d421ccb6
commit
25549374ec
@@ -43,32 +43,32 @@
|
||||
'wire:navigate' => $isLink && ! $external && ! $noWireNavigate && ! $attributes->has('wire:navigate') ? true : null,
|
||||
'type' => $isLink ? null : 'button',
|
||||
'aria-current' => $active ? 'page' : null,
|
||||
'data-active' => $active ? true : null,
|
||||
'data-md-active' => $active ? true : null,
|
||||
], fn ($value): bool => $value !== null));
|
||||
@endphp
|
||||
|
||||
<{{ $tag }} data-navigation-rail-item {{ $attributes }}>
|
||||
<span data-navigation-indicator>
|
||||
<span class="relative inline-flex">
|
||||
<{{ $tag }} data-md-navigation-rail-item {{ $attributes }}>
|
||||
<span data-md-navigation-indicator>
|
||||
<span data-md-navigation-icon>
|
||||
@if ($icon)
|
||||
<x-livewire-material::icon :name="$icon" :filled="$active" class="size-6" />
|
||||
<x-livewire-material::icon :name="$icon" :filled="$active" />
|
||||
@endif
|
||||
|
||||
@if ($dot)
|
||||
<x-livewire-material::badge floating />
|
||||
@elseif ($count)
|
||||
<span class="hidden rail-collapsed:contents"><x-livewire-material::badge :value="$badge" max="999" floating /></span>
|
||||
<span data-md-navigation-badge-icon><x-livewire-material::badge :value="$badge" max="999" floating /></span>
|
||||
@endif
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span data-navigation-label>{{ $label ?? $slot }}</span>
|
||||
<span data-md-navigation-label>{{ $label ?? $slot }}</span>
|
||||
|
||||
@if ($count)
|
||||
<span class="flex shrink-0 rail-collapsed:hidden"><x-livewire-material::badge :value="$badge" max="999" /></span>
|
||||
<span data-md-navigation-badge-end><x-livewire-material::badge :value="$badge" max="999" /></span>
|
||||
@endif
|
||||
|
||||
@if ($spoken !== null)
|
||||
<span class="sr-only">, {{ $spoken }}</span>
|
||||
<span class="md-visually-hidden">, {{ $spoken }}</span>
|
||||
@endif
|
||||
</{{ $tag }}>
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
$headingId = 'navigation-section-'.substr(md5((string) $label), 0, 10);
|
||||
@endphp
|
||||
|
||||
<div role="group" aria-labelledby="{{ $headingId }}" data-navigation-rail-section {{ $attributes }}>
|
||||
<p id="{{ $headingId }}" data-navigation-rail-heading>{{ $label }}</p>
|
||||
<div role="group" aria-labelledby="{{ $headingId }}" data-md-navigation-rail-section {{ $attributes }}>
|
||||
<p id="{{ $headingId }}" data-md-navigation-rail-heading>{{ $label }}</p>
|
||||
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
@@ -45,18 +45,18 @@
|
||||
`footer`, pinned to the foot. Header and footer never scroll, so nothing in them is cut off by
|
||||
the scroller's edge.
|
||||
|
||||
Anything else inside can take both shapes with the `rail-collapsed:` variant, which applies
|
||||
while the rail is drawn collapsed for whatever reason:
|
||||
`<span class="rail-collapsed:hidden">…only while expanded…</span>`
|
||||
`<span class="hidden rail-collapsed:inline-flex">…only while collapsed…</span>`.
|
||||
Nothing that shows while collapsed may be wider than 96px.
|
||||
Anything else inside can take both shapes by matching the rail's own state directly, the same
|
||||
selectors resources/css/components/navigation-rail.css uses for every branch of "collapsed" —
|
||||
`data-md-navigation-rail`'s value, `:not([data-md-open])`, and the window band each mode
|
||||
collapses in; that file is the one place the numbers need to stay right, rather than a
|
||||
Tailwind variant repeating them. Nothing that shows while collapsed may be wider than 96px.
|
||||
|
||||
Props: `label` names the landmark ("Main"); `width` is the expanded width (`16rem`, held
|
||||
Props: `label` names the landmark ("Main"); `width` is the expanded width (`256px`, held
|
||||
between M3's 220 and 360dp) — or the word `narrow`, M3's other *collapsed* width
|
||||
(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; `align` is `top` (the default) or `center`, which
|
||||
narrow rail expands to the default 256px; `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
|
||||
@@ -82,12 +82,12 @@
|
||||
https://github.com/androidx/androidx/tree/androidx-main/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3
|
||||
— surface (surface-container and elevation 2 with a large inner corner when modal), 44px above
|
||||
the header and 40px under it, 4px between collapsed items. The styles are
|
||||
resources/css/components/navigation.css; the behaviour resources/js/navigation.js. --}}
|
||||
resources/css/components/navigation-rail.css; the behaviour resources/js/navigation.js. --}}
|
||||
|
||||
@props([
|
||||
'mode' => 'collapsible',
|
||||
'label' => null,
|
||||
'width' => '16rem',
|
||||
'width' => '256px',
|
||||
'align' => 'top',
|
||||
'hideWhenCollapsed' => false,
|
||||
'menu' => null,
|
||||
@@ -105,29 +105,29 @@
|
||||
$menu ??= $interactive;
|
||||
$collapsedAtFirst = in_array($mode, ['collapsed', 'modal'], true);
|
||||
$narrow = $width === 'narrow';
|
||||
$width = $narrow ? '16rem' : $width;
|
||||
$width = $narrow ? '256px' : $width;
|
||||
$centred = $align === 'center';
|
||||
@endphp
|
||||
|
||||
<div
|
||||
data-navigation-rail="{{ $mode }}"
|
||||
@if ($narrow) data-width="narrow" @endif
|
||||
@if ($centred) data-align="center" @endif
|
||||
@if ($hideWhenCollapsed) data-hide-when-collapsed @endif
|
||||
@if ($divider) data-divider @endif
|
||||
@unless ($fill) data-fill="false" @endunless
|
||||
data-md-navigation-rail="{{ $mode }}"
|
||||
@if ($narrow) data-md-width="narrow" @endif
|
||||
@if ($centred) data-md-align="center" @endif
|
||||
@if ($hideWhenCollapsed) data-md-hide-when-collapsed @endif
|
||||
@if ($divider) data-md-divider @endif
|
||||
@unless ($fill) data-md-fill="false" @endunless
|
||||
@if ($interactive)
|
||||
x-data="materialNavigationRail('{{ $mode }}', {{ $hideWhenCollapsed ? 'true' : 'false' }})"
|
||||
x-bind:data-open="open"
|
||||
x-bind:data-md-open="open"
|
||||
@endif
|
||||
{{ $attributes->merge(['style' => "--navigation-rail-width: {$width}"]) }}
|
||||
>
|
||||
@if ($canOpen)
|
||||
<div data-navigation-rail-scrim aria-hidden="true" x-on:click="$store.rail.hide()"></div>
|
||||
<div data-md-navigation-rail-scrim aria-hidden="true" x-on:click="$store.rail.hide()"></div>
|
||||
@endif
|
||||
|
||||
<nav
|
||||
data-navigation-rail-panel
|
||||
data-md-navigation-rail-panel
|
||||
aria-label="{{ $label ?? __('Main') }}"
|
||||
@if ($canOpen)
|
||||
x-trap.inert.noscroll="open"
|
||||
@@ -135,46 +135,43 @@
|
||||
@endif
|
||||
>
|
||||
@if ($menu || isset($brand) || isset($header))
|
||||
<div data-navigation-rail-header>
|
||||
<div data-md-navigation-rail-header>
|
||||
@if ($menu || isset($brand))
|
||||
{{-- The menu button is centred on the collapsed rail: 28px either side of a
|
||||
40px button in 96, 20px in a narrow 80. --}}
|
||||
<div @class(['flex w-full min-w-0 items-center gap-3 pe-5', 'ps-7' => $menu, 'rail-collapsed:ps-5' => $menu && $narrow, 'ps-5' => ! $menu])>
|
||||
<div data-md-navigation-rail-menu-row>
|
||||
@if ($menu)
|
||||
<button
|
||||
type="button"
|
||||
data-navigation-rail-menu
|
||||
data-md-navigation-rail-menu
|
||||
aria-label="{{ $collapsedAtFirst ? __('Expand navigation') : __('Collapse navigation') }}"
|
||||
aria-expanded="{{ $collapsedAtFirst ? 'false' : 'true' }}"
|
||||
x-on:click="menu()"
|
||||
x-bind:aria-label="expanded ? @js(__('Collapse navigation')) : @js(__('Expand navigation'))"
|
||||
x-bind:aria-expanded="expanded.toString()"
|
||||
class="state-layer touch-target focus-ring inline-flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-corner-full text-on-surface-variant"
|
||||
class="md-state-layer md-focus-ring md-touch-target"
|
||||
>
|
||||
<span class="contents rail-collapsed:hidden"><x-livewire-material::icon name="menu_open" class="size-6" /></span>
|
||||
<span class="hidden rail-collapsed:contents"><x-livewire-material::icon name="menu" class="size-6" /></span>
|
||||
<span data-md-navigation-rail-menu-collapse-icon><x-livewire-material::icon name="menu_open" /></span>
|
||||
<span data-md-navigation-rail-menu-expand-icon><x-livewire-material::icon name="menu" /></span>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@isset($brand)
|
||||
<div class="min-w-0 flex-1 rail-collapsed:hidden">{{ $brand }}</div>
|
||||
<div data-md-navigation-rail-brand>{{ $brand }}</div>
|
||||
@endisset
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@isset($header)
|
||||
{{-- And so is a 56px FAB: 20px either side in 96, 12px in a narrow 80. --}}
|
||||
<div {{ $header->attributes->class(['flex w-full flex-col items-start gap-2 px-5', 'rail-collapsed:px-3' => $narrow]) }}>{{ $header }}</div>
|
||||
<div data-md-navigation-rail-fab-row {{ $header->attributes }}>{{ $header }}</div>
|
||||
@endisset
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div data-navigation-rail-destinations>
|
||||
<div data-md-navigation-rail-destinations>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
@isset($footer)
|
||||
<div data-navigation-rail-footer {{ $footer->attributes }}>{{ $footer }}</div>
|
||||
<div data-md-navigation-rail-footer {{ $footer->attributes }}>{{ $footer }}</div>
|
||||
@endisset
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user