Files
livewire-material/resources/views/components/section-nav.blade.php
T
Andreas Reinhold / reiniandClaude Sonnet 5 f1cd6e4eb4 Draw the section navigation without Tailwind
Plan step 36 (navigation group, third batch): <x-section-nav>'s class
lists move into resources/css/components/section-nav.css, keyed on
data-md-section-nav (data-md-section-nav-picker, its own trigger and
label) and <x-tabs>'s own hooks, reused whole rather than copied
(N-16's fix keeps the real scrollable tab bar it landed): the bar's
outline-variant divider, active indicator and aria-current colouring
are tabs.css's rules, not this file's. The picker's trigger takes the
shared md-focus-ring class and the fixed md-type-body-lg/md-ink/
md-text-start text classes for its own body-large type, since it is
prose on a plain button rather than a component's own type; icons
take a size prop instead of a size class.

Hooks renamed: data-section-nav to data-md-section-nav,
data-section-picker to data-md-section-nav-picker, updated in
TabsTest.php (which carries section-nav's render tests) and
tests/Browser/BarsTest.php.

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

92 lines
4.5 KiB
PHP

{{-- The navigation inside one area of an app the sections of settings, of an admin which is a
single destination in the app's own navigation.
`items`, a list of `['title' => …, 'url' => …]` with an optional `icon`, `active` and `badge`
(an item is current when `active` is true, or when its `url` is the page's). From `medium`
(600px) they
are M3's secondary tabs as links, the current one underlined; on a compact window, where a row
of them
never fits, a button naming the current section opens a menu of all of them, the current one
marked `aria-current="page"` and each with its badge. The same list is
rendered for both, and CSS shows one.
The page's URL is `Livewire::originalUrl()`: while a Livewire component on the page updates, the
request is Livewire's update endpoint, and comparing with it left no section lit.
Up to four sections share the row as fixed tabs. From five the row is M3's scrollable tab bar
each tab as wide as its own label, the set scrolling sideways, offset 52dp from the leading
edge so it reads as scrollable which is M3's own answer to a row that will not fit, and the
one its accessibility page blesses ("horizontal scrolling tabs meet accessibility requirements
because they need to increase in width to respond to label text without affecting the
layout"). An earlier version wrapped them onto a grid instead, which left the bar's divider
under the last row only and stranded the upper rows' indicators against nothing.
`label` names the navigation ("Sections"). Links use `wire:navigate` unless
`no-wire-navigate`. --}}
@props([
'items' => [],
'label' => null,
'noWireNavigate' => false,
])
@php
$label ??= __('Sections');
$page = \Livewire\Livewire::originalUrl();
$isCurrent = fn (array $item): bool => ($item['active'] ?? false) || (filled($item['url'] ?? null) && $page === url($item['url']));
$current = collect($items)->first($isCurrent) ?? ($items[0] ?? null);
// Four fit a row at the widths this bar is used at; from five they are M3's scrollable tabs.
$scrollable = count($items) >= 5;
@endphp
<div {{ $attributes }} data-md-section-nav>
@if ($current)
<div data-md-section-nav-picker>
<x-livewire-material::menu :$label position="bottom-start">
<x-slot:trigger>
<button type="button" data-md-section-nav-trigger class="md-focus-ring md-type-body-lg md-ink md-text-start">
@isset($current['icon'])
<x-livewire-material::icon :name="$current['icon']" :size="20" class="md-ink-variant" />
@endisset
<span data-md-section-nav-trigger-label class="md-truncate">{{ $current['title'] }}</span>
<x-livewire-material::icon name="arrow_drop_down" class="md-ink-variant" />
</button>
</x-slot:trigger>
@foreach ($items as $item)
<x-livewire-material::menu-item :label="$item['title']" :icon="$item['icon'] ?? null" :link="$item['url']" :current="$isCurrent($item)" :badge="$item['badge'] ?? null" :no-wire-navigate="$noWireNavigate" />
@endforeach
</x-livewire-material::menu>
</div>
@endif
<nav aria-label="{{ $label }}">
<ul data-md-tabs-bar data-md-variant="secondary" @if ($scrollable) data-md-scrollable @endif>
@foreach ($items as $item)
@php($on = $isCurrent($item))
<li>
<a
href="{{ $item['url'] }}"
data-md-tab
@if ($on) aria-current="page" @endif
@unless ($noWireNavigate) wire:navigate @endunless
>
<span data-md-tab-content>
@isset($item['icon'])
<x-livewire-material::icon :name="$item['icon']" :filled="$on" :size="20" />
@endisset
<span class="md-truncate">{{ $item['title'] }}</span>
@if (filled($item['badge'] ?? null))
<x-livewire-material::badge :value="$item['badge']" />
@endif
<span data-md-tab-indicator aria-hidden="true"></span>
</span>
</a>
</li>
@endforeach
</ul>
</nav>
</div>