M3 Expressive's flexible navigation bar, the collapsed, expanded and modal navigation rail with its state applied before the first paint, and an adaptive app shell composing them. The head script now restores the theme and rail attributes that wire:navigate strips from <html>. Completes Phase 8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
75 lines
3.1 KiB
PHP
75 lines
3.1 KiB
PHP
{{-- One destination in an `<x-navigation-rail>`, drawn in whichever shape the rail has.
|
||
|
||
<x-navigation-rail-item label="Inbox" icon="inbox" link="{{ route('inbox') }}" :active="request()->routeIs('inbox')" badge="12" />
|
||
|
||
Expanded, it is a 56px full-width pill: the icon, the label-large label beside it and a count
|
||
at its end. Collapsed, the icon sits in a 56×32 indicator over a label-medium label (two lines
|
||
at most), with the count on the icon. The current destination (`active`) is
|
||
`aria-current="page"` with the filled icon on secondary-container, its label in secondary when
|
||
collapsed.
|
||
|
||
`link` renders an anchor with `wire:navigate` (unless `external` or `no-wire-navigate`);
|
||
without one it is a button. `badge`: `true` for M3's small dot on the icon, a number or a few
|
||
characters for the large badge (999+ at most). Screen readers hear a count after the label
|
||
(", 12"); `badge-label` replaces it with words ("12 unread") and gives a dot something to say.
|
||
|
||
Values from NavigationRailVerticalItemTokens.kt, NavigationRailHorizontalItemTokens.kt,
|
||
NavigationRailBaselineItemTokens.kt and NavigationRailColorTokens.kt (androidx Compose
|
||
Material 3, Apache-2.0); see `<x-navigation-rail>`. Compose's expanded item hugs its label; the
|
||
package draws the full-width pill, which leaves room for the count at the end. --}}
|
||
|
||
@props([
|
||
'label' => null,
|
||
'icon' => null,
|
||
'link' => null,
|
||
'external' => false,
|
||
'noWireNavigate' => false,
|
||
'active' => false,
|
||
'badge' => null,
|
||
'badgeLabel' => null,
|
||
])
|
||
|
||
@php
|
||
$isLink = filled($link);
|
||
$tag = $isLink ? 'a' : 'button';
|
||
$dot = $badge === true;
|
||
$count = ! $dot && $badge !== null && $badge !== false && $badge !== '';
|
||
$spoken = $badgeLabel ?? ($count ? $badge : null);
|
||
|
||
$attributes = $attributes->merge(array_filter([
|
||
'href' => $isLink ? $link : null,
|
||
'target' => $isLink && $external ? '_blank' : null,
|
||
'rel' => $isLink && $external ? 'noopener' : null,
|
||
'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,
|
||
], fn ($value): bool => $value !== null));
|
||
@endphp
|
||
|
||
<{{ $tag }} data-navigation-rail-item {{ $attributes }}>
|
||
<span data-navigation-indicator>
|
||
<span class="relative inline-flex">
|
||
@if ($icon)
|
||
<x-icon :name="$icon" :filled="$active" class="size-6" />
|
||
@endif
|
||
|
||
@if ($dot)
|
||
<x-badge floating />
|
||
@elseif ($count)
|
||
<span class="hidden rail-collapsed:contents"><x-badge :value="$badge" max="999" floating /></span>
|
||
@endif
|
||
</span>
|
||
</span>
|
||
|
||
<span data-navigation-label>{{ $label ?? $slot }}</span>
|
||
|
||
@if ($count)
|
||
<span class="flex shrink-0 rail-collapsed:hidden"><x-badge :value="$badge" max="999" /></span>
|
||
@endif
|
||
|
||
@if ($spoken !== null)
|
||
<span class="sr-only">, {{ $spoken }}</span>
|
||
@endif
|
||
</{{ $tag }}>
|