Content & marketing
Navbar code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/navbar.blade.php
@props([
'links' => [],
'sticky' => true,
])
@php
/*
| Marketing top navigation. Pass `links` as [label => href] for flat links or
| [label => [childLabel => href, ...]] for dropdowns. Use the `brand` slot
| for a logo and the `actions` slot for auth buttons.
|
| Usage:
| <x-ui.navbar :links="[
| 'Features' => '#features',
| 'Explore' => ['Motion' => route('foundations.motion'), 'Components' => route('components.index')],
| ]">
| <x-slot:brand>...</x-slot:brand>
| <x-slot:actions>...</x-slot:actions>
| </x-ui.navbar>
*/
$isNavLinkActive = function (string $href): bool {
if (str_starts_with($href, '#')) {
return request()->routeIs('home');
}
$current = rtrim(url()->current(), '/');
$target = rtrim(url($href), '/');
if ($current === $target) {
return true;
}
return $target !== '' && str_starts_with($current.'/', $target.'/');
};
$navLinkClasses = function (bool $active): string {
return $active
? 'text-sm font-semibold text-brand-600 dark:text-brand-400 transition'
: 'text-sm font-medium text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition';
};
$mobileNavLinkClasses = function (bool $active): string {
return $active
? 'block rounded-lg px-3 py-2 text-base font-semibold text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40 transition'
: 'block rounded-lg px-3 py-2 text-base font-medium text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition';
};
$mobileChildNavLinkClasses = function (bool $active): string {
return $active
? 'block rounded-lg py-2 pl-6 pr-3 text-base font-semibold text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40 transition'
: 'block rounded-lg py-2 pl-6 pr-3 text-base font-medium text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition';
};
@endphp
<nav
x-data="{ open: false }"
{{ $attributes->merge(['class' => ($sticky ? 'sticky top-0 ' : '').'z-40 border-b border-gray-200/70 dark:border-gray-800/70 bg-white/80 dark:bg-gray-950/80 backdrop-blur-lg']) }}
>
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between gap-4">
<div class="flex items-center gap-2">
@isset($brand)
{{ $brand }}
@endisset
</div>
<div class="hidden md:flex md:items-center md:gap-8">
@foreach ($links as $label => $link)
@php
$isDropdown = is_array($link);
$href = $isDropdown ? null : $link;
$children = $isDropdown ? $link : [];
$active = $isDropdown
? collect($children)->contains(fn (string $childHref): bool => $isNavLinkActive($childHref))
: $isNavLinkActive($link);
@endphp
@if ($isDropdown)
<x-ui.dropdown align="left" width="56" variant="nav">
<x-slot:trigger>
<button type="button" class="inline-flex cursor-pointer items-center gap-1 {{ $navLinkClasses($active) }}">
{{ $label }}
<x-ui.icon name="caret-down" weight="bold" size="xs" class="opacity-70" />
</button>
</x-slot:trigger>
@foreach ($children as $childLabel => $childHref)
<x-ui.dropdown.item :href="$childHref" :active="$isNavLinkActive($childHref)">
{{ $childLabel }}
</x-ui.dropdown.item>
@endforeach
</x-ui.dropdown>
@else
<a
href="{{ $href }}"
@if ($active) aria-current="page" @endif
class="{{ $navLinkClasses($active) }}"
>{{ $label }}</a>
@endif
@endforeach
</div>
<div class="hidden md:flex md:items-center md:gap-2">
<x-theme-switcher />
@isset($actions)
{{ $actions }}
@endisset
</div>
<div class="flex items-center gap-1 md:hidden">
<x-theme-switcher />
<x-ui.icon-button icon="list" label="Open menu" @click="open = ! open" x-show="! open" />
<x-ui.icon-button icon="x" label="Close menu" @click="open = false" x-show="open" x-cloak />
</div>
</div>
</div>
{{-- Mobile menu --}}
<div x-show="open" x-collapse x-cloak class="md:hidden border-t border-gray-200 dark:border-gray-800">
<div class="space-y-1 px-4 py-4">
@foreach ($links as $label => $link)
@php
$isDropdown = is_array($link);
$href = $isDropdown ? null : $link;
$children = $isDropdown ? $link : [];
$active = $isDropdown
? collect($children)->contains(fn (string $childHref): bool => $isNavLinkActive($childHref))
: $isNavLinkActive($link);
@endphp
@if ($isDropdown)
<div x-data="{ expanded: @js($active) }">
<button
type="button"
@click="expanded = ! expanded"
class="flex w-full cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-left {{ $active ? 'text-base font-semibold text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40' : 'text-base font-medium text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white' }} transition"
:aria-expanded="expanded.toString()"
>
<span>{{ $label }}</span>
<x-ui.icon
name="caret-down"
weight="bold"
size="sm"
class="opacity-70 transition-transform"
x-bind:class="expanded && 'rotate-180'"
/>
</button>
<div x-show="expanded" x-collapse class="space-y-1 pt-1">
@foreach ($children as $childLabel => $childHref)
@php($childActive = $isNavLinkActive($childHref))
<a
href="{{ $childHref }}"
@if ($childActive) aria-current="page" @endif
class="{{ $mobileChildNavLinkClasses($childActive) }}"
>{{ $childLabel }}</a>
@endforeach
</div>
</div>
@else
<a
href="{{ $href }}"
@if ($active) aria-current="page" @endif
class="{{ $mobileNavLinkClasses($active) }}"
>{{ $label }}</a>
@endif
@endforeach
@if (count(config('themes.items', [])) > 1)
{{-- Theme colour selector: the top-bar switcher only shows the dark-mode
toggle on mobile, so expose the colour picker here in the menu. --}}
<div class="mt-3 border-t border-gray-200 pt-3 dark:border-gray-800">
<x-ui.theme-color-picker align="left" :full-width="true" class="px-3 sm:hidden" />
</div>
@endif
@isset($actions)
<div class="mt-3 flex flex-col gap-2 border-t border-gray-200 dark:border-gray-800 pt-3">{{ $actions }}</div>
@endisset
</div>
</div>
</nav>