Skip to content

Instantly share code, notes, and snippets.

@rosswintle
Last active September 13, 2023 21:45
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rosswintle/3f15a55f0fb1e1a868ff80a2e04c7c16 to your computer and use it in GitHub Desktop.
Save rosswintle/3f15a55f0fb1e1a868ff80a2e04c7c16 to your computer and use it in GitHub Desktop.
Laravel Jetstream Livewire Navigation Menu
<?php
/**
* This class extends the base NavigationClass provided by Jetstream.
* Note that the base class includes the navigation-menu.blade.php
*/
namespace App\Http\Livewire;
class AppNavigationMenu extends \Laravel\Jetstream\Http\Livewire\NavigationMenu
{
/**
* Define the list of items to go in the navigation as 'Label' => 'route'
* @var string[]
*/
public $items = [
'Posts' => 'dashboard',
'Templates' => 'templates',
];
}
<!-- resources/views/components/desktop-menu.blade.php -->
<div class="flex">
<!-- Logo -->
<div class="flex-shrink-0 flex items-center">
<x-logo></x-logo>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
@foreach ($items as $label => $route)
<x-jet-nav-link href="{{ route($route) }}" :active="request()->routeIs($route)">
{{ $label }}
</x-jet-nav-link>
@endforeach
</div>
</div>
<div class="hidden sm:flex sm:items-center sm:ml-6">
<!-- Settings Dropdown -->
<div class="ml-3 relative">
<x-jet-dropdown align="right" width="48">
<x-slot name="trigger">
@if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
<button class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-gray-300 transition duration-150 ease-in-out">
<img class="h-8 w-8 rounded-full object-cover" src="{{ Auth::user()->profile_photo_url }}" alt="{{ Auth::user()->name }}" />
</button>
@else
<span class="inline-flex rounded-md">
<button type="button" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
{{ Auth::user()->name }}
<svg class="ml-2 -mr-0.5 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</span>
@endif
</x-slot>
<x-slot name="content">
<!-- Account Management -->
<div class="block px-4 py-2 text-xs text-gray-400">
{{ __('Manage Account') }}
</div>
<x-jet-dropdown-link href="{{ route('profile.show') }}">
{{ __('Profile') }}
</x-jet-dropdown-link>
@if (Laravel\Jetstream\Jetstream::hasApiFeatures())
<x-jet-dropdown-link href="{{ route('api-tokens.index') }}">
{{ __('API Tokens') }}
</x-jet-dropdown-link>
@endif
<div class="border-t border-gray-100"></div>
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-jet-dropdown-link href="{{ route('logout') }}"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-jet-dropdown-link>
</form>
</x-slot>
</x-jet-dropdown>
</div>
</div>
<!-- resources/views/components/mobile-menu.blade.php -->
<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
@foreach ($items as $label => $route)
<x-jet-responsive-nav-link href="{{ route($route) }}" :active="request()->routeIs($route)">
{{ $label }}
</x-jet-responsive-nav-link>
@endforeach
</div>
<!-- Responsive Settings Options -->
<div class="pt-4 pb-1 border-t border-gray-200">
<div class="flex items-center px-4">
@if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
<div class="flex-shrink-0 mr-3">
<img class="h-10 w-10 rounded-full object-cover" src="{{ Auth::user()->profile_photo_url }}" alt="{{ Auth::user()->name }}" />
</div>
@endif
<div>
<div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
</div>
</div>
<div class="mt-3 space-y-1">
<!-- Account Management -->
<x-jet-responsive-nav-link href="{{ route('profile.show') }}" :active="request()->routeIs('profile.show')">
{{ __('Profile') }}
</x-jet-responsive-nav-link>
@if (Laravel\Jetstream\Jetstream::hasApiFeatures())
<x-jet-responsive-nav-link href="{{ route('api-tokens.index') }}" :active="request()->routeIs('api-tokens.index')">
{{ __('API Tokens') }}
</x-jet-responsive-nav-link>
@endif
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-jet-responsive-nav-link href="{{ route('logout') }}"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-jet-responsive-nav-link>
</form>
</div>
</div>
</div>
<!-- resources/views/navigation-menu.blade.php -->
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<x-desktop-menu :items="$items"></x-desktop-menu>
<x-hamburger></x-hamburger>
</div>
<x-mobile-menu :items="$items"></x-mobile-menu>
</div>
</nav>
@rosswintle
Copy link
Author

@stevepurcell Yeah, my code here is based on a pretty old version of Jetstream now. Looks like the hamburger is now just in livewire/resources/views/navigation-menu.blade.php

I think this is what @astra1porta sent you above.

@rosswintle
Copy link
Author

@stevepurcell Actually, I think this must have been a component that I extracted and didn't include here. Sorry! Should be simple to make it yourself or inline the code.

@madsem
Copy link

madsem commented Jul 4, 2022

thanks @rosswintle !

Here's un updated version for the latest jetstream, it supports dropdowns in desktop nav as well:

#AppNavigationMenu.php

<?php

namespace App\Http\Livewire;

use Laravel\Jetstream\Http\Livewire\NavigationMenu;

class AppNavigationMenu extends NavigationMenu
{

    /**
     * Define the list of items to go in the navigation as 'Label' => 'route',
     * or to create a dropdown: 'Dropdown Label' => ['Label' => 'route']
     *
     * @var string[]
     */
    public array $items = [
        'Label' => 'route',
        'Dropdown Label' => [
            'Label' => 'route',
            'Another Label' => 'route',
        ],
    ];

}

#desktop-menu.blade.php

<div class="flex">
    <!-- Logo -->
    <div class="shrink-0 flex items-center">
        <a href="{{ route('dashboard') }}">
            <x-jet-application-mark class="block h-9 w-auto rounded-md" />
        </a>
    </div>

    <!-- Navigation Links -->
    <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">

        @foreach ($items as $label => $route)

            @if(is_array($route))
                <div class="cursor-pointer inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition">
                <x-jet-dropdown align="right" width="60">
                    <x-slot name="trigger">
                              {{ $label }}
                    </x-slot>

                    <x-slot name="content">
                        <div class="w-60">
                            @foreach ($route as $key => $val)
                            <x-jet-dropdown-link href="{{ route($val) }}" :active="request()->routeIs($val)">
                                {{ $key }}
                            </x-jet-dropdown-link>
                            @endforeach
                        </div>
                    </x-slot>
                </x-jet-dropdown>
                </div>

            @else
                <x-jet-nav-link href="{{ route($route) }}" :active="request()->routeIs($route)">
                    {{ $label }}
                </x-jet-nav-link>
            @endif
        @endforeach

    </div>
</div>

<div class="hidden sm:flex sm:items-center sm:ml-6">
    <!-- Teams Dropdown -->
    @if (Laravel\Jetstream\Jetstream::hasTeamFeatures())
        <div class="ml-3 relative">
            <x-jet-dropdown align="right" width="60">
                <x-slot name="trigger">
                                <span class="inline-flex rounded-md">
                                    <button type="button" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:bg-gray-50 hover:text-gray-700 focus:outline-none focus:bg-gray-50 active:bg-gray-50 transition">
                                        {{ Auth::user()->currentTeam->name }}

                                        <svg class="ml-2 -mr-0.5 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
                                            <path fill-rule="evenodd" d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
                                        </svg>
                                    </button>
                                </span>
                </x-slot>

                <x-slot name="content">
                    <div class="w-60">
                        <!-- Team Management -->
                        <div class="block px-4 py-2 text-xs text-gray-400">
                            {{ __('Manage Team') }}
                        </div>

                        <!-- Team Settings -->
                        <x-jet-dropdown-link href="{{ route('teams.show', Auth::user()->currentTeam->id) }}">
                            {{ __('Team Settings') }}
                        </x-jet-dropdown-link>

                        @can('create', Laravel\Jetstream\Jetstream::newTeamModel())
                            <x-jet-dropdown-link href="{{ route('teams.create') }}">
                                {{ __('Create New Team') }}
                            </x-jet-dropdown-link>
                        @endcan

                        <div class="border-t border-gray-100"></div>

                        <!-- Team Switcher -->
                        <div class="block px-4 py-2 text-xs text-gray-400">
                            {{ __('Switch Teams') }}
                        </div>

                        @foreach (Auth::user()->allTeams() as $team)
                            <x-jet-switchable-team :team="$team" />
                        @endforeach
                    </div>
                </x-slot>
            </x-jet-dropdown>
        </div>
@endif

<!-- Settings Dropdown -->
    <div class="ml-3 relative">
        <x-jet-dropdown align="right" width="48">
            <x-slot name="trigger">
                @if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
                    <button class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-gray-300 transition">
                        <img class="h-8 w-8 rounded-full object-cover" src="{{ Auth::user()->profile_photo_url }}" alt="{{ Auth::user()->name }}" />
                    </button>
                @else
                    <span class="inline-flex rounded-md">
                                    <button type="button" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition">
                                        {{ Auth::user()->name }}

                                        <svg class="ml-2 -mr-0.5 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
                                            <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
                                        </svg>
                                    </button>
                                </span>
                @endif
            </x-slot>

            <x-slot name="content">
                <!-- Account Management -->
                <div class="block px-4 py-2 text-xs text-gray-400">
                    {{ __('Manage Account') }}
                </div>

                <x-jet-dropdown-link href="{{ route('profile.show') }}">
                    {{ __('Profile') }}
                </x-jet-dropdown-link>

                @if (Laravel\Jetstream\Jetstream::hasApiFeatures())
                    <x-jet-dropdown-link href="{{ route('api-tokens.index') }}">
                        {{ __('API Tokens') }}
                    </x-jet-dropdown-link>
                @endif

                <div class="border-t border-gray-100"></div>

                <!-- Authentication -->
                <form method="POST" action="{{ route('logout') }}" x-data>
                    @csrf

                    <x-jet-dropdown-link href="{{ route('logout') }}"
                                         @click.prevent="$root.submit();">
                        {{ __('Log Out') }}
                    </x-jet-dropdown-link>
                </form>
            </x-slot>
        </x-jet-dropdown>
    </div>
</div>

<!-- Hamburger -->
<div class="-mr-2 flex items-center sm:hidden">
    <button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition">
        <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
            <path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
            <path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
        </svg>
    </button>
</div>

#mobile-menu.blade.php

<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
    <div class="pt-2 pb-3 space-y-1">

        @foreach ($items as $label => $route)

            @if(is_array($route))
                @foreach ($route as $key => $val)
                    <x-jet-responsive-nav-link href="{{ route($val) }}" :active="request()->routeIs($val)">
                        {{ $key }}
                    </x-jet-responsive-nav-link>
                @endforeach
            @else
                <x-jet-responsive-nav-link href="{{ route($route) }}" :active="request()->routeIs($route)">
                    {{ $label }}
                </x-jet-responsive-nav-link>
            @endif
        @endforeach

    </div>

    <!-- Responsive Settings Options -->
    <div class="pt-4 pb-1 border-t border-gray-200">
        <div class="flex items-center px-4">
            @if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
                <div class="shrink-0 mr-3">
                    <img class="h-10 w-10 rounded-full object-cover" src="{{ Auth::user()->profile_photo_url }}" alt="{{ Auth::user()->name }}" />
                </div>
            @endif

            <div>
                <div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
                <div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
            </div>
        </div>

        <div class="mt-3 space-y-1">

        <!-- Account Management -->
            <x-jet-responsive-nav-link href="{{ route('profile.show') }}" :active="request()->routeIs('profile.show')">
                {{ __('Profile') }}
            </x-jet-responsive-nav-link>

            @if (Laravel\Jetstream\Jetstream::hasApiFeatures())
                <x-jet-responsive-nav-link href="{{ route('api-tokens.index') }}" :active="request()->routeIs('api-tokens.index')">
                    {{ __('API Tokens') }}
                </x-jet-responsive-nav-link>
            @endif

        <!-- Authentication -->
            <form method="POST" action="{{ route('logout') }}" x-data>
                @csrf

                <x-jet-responsive-nav-link href="{{ route('logout') }}"
                                           @click.prevent="$root.submit();">
                    {{ __('Log Out') }}
                </x-jet-responsive-nav-link>
            </form>

            <!-- Team Management -->
            @if (Laravel\Jetstream\Jetstream::hasTeamFeatures())
                <div class="border-t border-gray-200"></div>

                <div class="block px-4 py-2 text-xs text-gray-400">
                    {{ __('Manage Team') }}
                </div>

                <!-- Team Settings -->
                <x-jet-responsive-nav-link href="{{ route('teams.show', Auth::user()->currentTeam->id) }}" :active="request()->routeIs('teams.show')">
                    {{ __('Team Settings') }}
                </x-jet-responsive-nav-link>

                @can('create', Laravel\Jetstream\Jetstream::newTeamModel())
                    <x-jet-responsive-nav-link href="{{ route('teams.create') }}" :active="request()->routeIs('teams.create')">
                        {{ __('Create New Team') }}
                    </x-jet-responsive-nav-link>
                @endcan

                <div class="border-t border-gray-200"></div>

                <!-- Team Switcher -->
                <div class="block px-4 py-2 text-xs text-gray-400">
                    {{ __('Switch Teams') }}
                </div>

                @foreach (Auth::user()->allTeams() as $team)
                    <x-jet-switchable-team :team="$team" component="jet-responsive-nav-link" />
                @endforeach
            @endif
        </div>
    </div>
</div>

@randyocampo
Copy link

Undefined variable $items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment