tests / browser (chrome, chromium) (push) Successful in 3m34s
tests / browser (safari, webkit) (push) Successful in 6m2s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m6s
tests / feature (8.5) (push) Successful in 1m9s
tests / browser (firefox, firefox) (push) Successful in 4m20s
M3 Expressive top app bars (small, centered, medium and large flexible, search) that collapse with CSS sticky, docked and floating toolbars, primary and secondary tabs with a view-transition indicator, section navigation, the account menu and theme toggles. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
82 lines
3.8 KiB
PHP
82 lines
3.8 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 request's). From `sm` they
|
|
are M3's secondary tabs as links, the current one underlined; below `sm`, where a row of them
|
|
never fits, a button naming the current section opens a menu of all of them. The same list is
|
|
rendered for both, and CSS shows one.
|
|
|
|
A row too long for its column wraps onto a grid rather than scrolling: below `xl` five or six
|
|
sections go 3 + 3 and seven or more go four to a row — tabs that scroll hid the last sections on
|
|
a tablet. `label` names the navigation ("Sections"). Links use `wire:navigate` unless
|
|
`no-wire-navigate`. --}}
|
|
|
|
@props([
|
|
'items' => [],
|
|
'label' => null,
|
|
'noWireNavigate' => false,
|
|
])
|
|
|
|
@php
|
|
$label ??= __('Sections');
|
|
$isCurrent = fn (array $item): bool => ($item['active'] ?? false) || (filled($item['url'] ?? null) && url()->current() === url($item['url']));
|
|
$current = collect($items)->first($isCurrent) ?? ($items[0] ?? null);
|
|
|
|
$layout = match (true) {
|
|
count($items) < 5 => 'sm:flex',
|
|
count($items) < 7 => 'sm:grid sm:grid-cols-3 xl:flex',
|
|
default => 'sm:grid sm:grid-cols-4 xl:flex',
|
|
};
|
|
@endphp
|
|
|
|
<div {{ $attributes->class(['min-w-0']) }} data-section-nav>
|
|
@if ($current)
|
|
<div class="sm:hidden" data-section-picker>
|
|
<x-menu :$label position="bottom-start">
|
|
<x-slot:trigger>
|
|
<button type="button" class="focus-ring flex h-12 w-[min(20rem,calc(100vw-2rem))] cursor-pointer items-center gap-3 rounded-corner-xs border border-outline px-4 text-start type-body-lg text-on-surface">
|
|
@isset($current['icon'])
|
|
<x-icon :name="$current['icon']" class="size-5 text-on-surface-variant" />
|
|
@endisset
|
|
<span class="min-w-0 flex-1 truncate">{{ $current['title'] }}</span>
|
|
<x-icon name="arrow_drop_down" class="size-6 text-on-surface-variant" />
|
|
</button>
|
|
</x-slot:trigger>
|
|
|
|
@foreach ($items as $item)
|
|
<x-menu-item :label="$item['title']" :icon="$item['icon'] ?? null" :link="$item['url']" :selected="$isCurrent($item)" :no-wire-navigate="$noWireNavigate" />
|
|
@endforeach
|
|
</x-menu>
|
|
</div>
|
|
@endif
|
|
|
|
<nav aria-label="{{ $label }}" class="max-sm:hidden">
|
|
<ul data-tabs-bar data-variant="secondary" class="{{ $layout }}">
|
|
@foreach ($items as $item)
|
|
@php($on = $isCurrent($item))
|
|
|
|
<li class="flex min-w-0">
|
|
<a
|
|
href="{{ $item['url'] }}"
|
|
data-tab
|
|
@if ($on) aria-current="page" @endif
|
|
@unless ($noWireNavigate) wire:navigate @endunless
|
|
>
|
|
<span data-tab-content>
|
|
@isset($item['icon'])
|
|
<x-icon :name="$item['icon']" :filled="$on" class="size-5" />
|
|
@endisset
|
|
<span class="truncate">{{ $item['title'] }}</span>
|
|
@if (filled($item['badge'] ?? null))
|
|
<x-badge :value="$item['badge']" />
|
|
@endif
|
|
<span data-tab-indicator aria-hidden="true"></span>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
</nav>
|
|
</div>
|