Files
Andreas Reinhold / reiniandClaude Sonnet 5 63d421ccb6 Draw the navigation bar and bar item without Tailwind
Plan step 36 (navigation group, second batch): <x-navigation-bar>'s and
<x-navigation-bar-item>'s class lists move into
resources/css/components/navigation-bar.css and navigation-bar-item.css,
keyed on data-md-navigation-bar (data-md-tall, data-md-hide-on-scroll/
-hidden) and data-md-navigation-bar-item (data-md-active), both in
material.components. The indicator's growing fill and its state layer,
shared with the rail's item, move into a new navigation-item.css both
files import — the day's own layer in datepicker.css is the precedent
for keeping it out of the shared foundation classes: the element
focused and pressed is the whole item, the layer drawn only on the
smaller indicator or pill inside it, which md-state-layer cannot do and
:focus-visible never matches. The wash colour moves from a dead
on-surface base overridden by both items to a shared on-secondary-
container declaration, and its opacities from literal 0.08/0.1 to
state.css's own tokens — no visible change, since the numbers matched.

Every size is px (16dp Tailwind quirks aside, this file had none); the
bar's own item-count and container-query layout, N-08's label-medium
fix and N-19's on-secondary-container wash already matched the audit,
so only hooks, units and layer needed to change.

The scaffold's hide-on-scroll offset rule (`--material-bottom-bar`,
navigation-bar.css) stays unlayered: <x-scaffold> still publishes that
variable with a Tailwind utility until its own rewrite, and a rule in
any layer loses to it regardless of specificity — unlike the FAB
overrides the rail commit moves into material.components, which only
have to beat another material.components rule.

navigation.css is not deleted yet (still Tailwind's for the rail); its
"Navigation" comment in tailwind.css narrows as each stream leaves.
navigation-bar.css is not in NavigationStylesheetsTest.php's dataset:
its one unlayered rule breaks that test's "every block is
material.components" assumption, so the same checks are in
NavigationBarTest.php instead, the same reason ErrorPagesTest.php
carries error-page.css's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-15 00:08:34 +02:00

70 lines
2.7 KiB
PHP

{{-- One destination in an `<x-navigation-bar>`.
<x-navigation-bar-item label="Inbox" icon="inbox" link="{{ route('inbox') }}" :active="request()->routeIs('inbox')" badge="12" />
`link` renders an anchor with `wire:navigate` (unless `external` or `no-wire-navigate`);
without one it is a button, for a destination a Livewire action switches to. `active` marks
the current destination: `aria-current="page"`, the filled icon in on-secondary-container on
the secondary-container indicator, and the label in secondary (on-secondary-container inside
the medium bar's pill).
`badge` puts M3's badge on the icon: `true` for the small dot, a number or a few characters
for the large one (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 NavigationBarTokens.kt and NavigationBarVerticalItemTokens.kt (androidx Compose
Material 3, Apache-2.0); see `<x-navigation-bar>`. --}}
@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-md-active' => $active ? true : null,
], fn ($value): bool => $value !== null));
@endphp
<{{ $tag }} data-md-navigation-bar-item {{ $attributes }}>
<span data-md-navigation-pill>
<span data-md-navigation-indicator>
<span data-md-navigation-icon>
@if ($icon)
<x-livewire-material::icon :name="$icon" :filled="$active" />
@endif
@if ($dot)
<x-livewire-material::badge floating />
@elseif ($count)
<x-livewire-material::badge :value="$badge" max="999" floating />
@endif
</span>
</span>
<span data-md-navigation-label>{{ $label ?? $slot }}</span>
</span>
@if ($spoken !== null)
<span class="md-visually-hidden">, {{ $spoken }}</span>
@endif
</{{ $tag }}>