DEV Community

Monaye Win
Monaye Win

Posted on

Laravel Nova Customize Navigation

Laravel Nova is a great tool! There are many packages that will do similar to the customization I will introduce here.

As a dev, less package is always better if the required customization is simple.

So, This is what we will customize

  1. Add an icon to each navigation
  2. Add method to define return the sort value so we can sort
  3. Both navigation and group use the defined sort value for sorting

If you open a default navigation.blade.php it looks something like this

@if (count(\Laravel\Nova\Nova::resourcesForNavigation(request())))
    <h3 class="flex items-center font-normal text-white mb-6 text-base no-underline">
        <svg class="sidebar-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
            <path fill="var(--sidebar-icon)" d="M3 1h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3h-4zM3 11h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4h-4z"
            />
        </svg>
        <span class="sidebar-label">{{ __('Resources') }}</span>
    </h3>

    @foreach($navigation as $group => $resources)
        @if (count($groups) > 1)
            <h4 class="ml-8 mb-4 text-xs text-white-50% uppercase tracking-wide">{{ $group }}</h4>
        @endif

        <ul class="list-reset mb-8">
            @foreach($resources as $resource)
                <li class="leading-tight mb-4 ml-8 text-sm">
                    <router-link :to="{
                        name: 'index',
                        params: {
                            resourceName: '{{ $resource::uriKey() }}'
                        }
                    }" class="text-white text-justify no-underline dim" dusk="{{ $resource::uriKey() }}-resource-link">
                        {{ $resource::label() }}
                    </router-link>
                </li>
            @endforeach
        </ul>
    @endforeach
@endif
Enter fullscreen mode Exit fullscreen mode
  1. Add an icon to each navigation

Let's look at the part where each link is render

@foreach($resources as $resource)
                <li class="leading-tight mb-4 ml-8 text-sm">
                    <router-link :to="{
                        name: 'index',
                        params: {
                            resourceName: '{{ $resource::uriKey() }}'
                        }
                    }" class="text-white text-justify no-underline dim" dusk="{{ $resource::uriKey() }}-resource-link">
                        {{ $resource::label() }}
                    </router-link>
                </li>
            @endforeach
Enter fullscreen mode Exit fullscreen mode

If you dd($resource) it's a fully FQN of your resources. And this on each, it's calling static method to resource class to get the label

{{ $resource::label() }}
Enter fullscreen mode Exit fullscreen mode

We are going to add a new icon() method to the resource.

    /**
     * The icon of the resource.
     *
     * @return string
     */
    public static function icon()
    {
        return view('nova::icons.icon-customer')->render();
    }
Enter fullscreen mode Exit fullscreen mode

in the icon-customer.blade.php file we only have an SVG.

<svg width="20" height="20" class="sidebar-icon" fill="var(--sidebar-icon)" viewBox="0 0 25 26"  xmlns="http://www.w3.org/2000/svg"><path d="M18.501 15.62a7.161 7.161 0 003.409-6.095c0-3.949-3.202-7.161-7.137-7.161-3.935 0-7.137 3.212-7.137 7.161a7.161 7.161 0 003.408 6.095c-4.406.992-7.499 3.702-7.499 6.98 0 2.684 7.058 3.4 11.228 3.4S26 25.284 26 22.6c0-3.278-3.092-5.988-7.499-6.98zM9.106 9.525c0-3.134 2.542-5.684 5.667-5.684 3.124 0 5.666 2.55 5.666 5.684 0 3.135-2.542 5.684-5.666 5.684-3.125 0-5.667-2.55-5.667-5.684zm5.667 14.998c-5.994 0-9.582-1.27-9.757-1.927.005-3.259 4.38-5.91 9.757-5.91 5.376 0 9.75 2.65 9.756 5.909-.17.656-3.758 1.928-9.756 1.928z" /><path d="M18.501 15.62a7.161 7.161 0 003.409-6.095c0-3.949-3.202-7.161-7.137-7.161-3.935 0-7.137 3.212-7.137 7.161a7.161 7.161 0 003.408 6.095c-4.406.992-7.499 3.702-7.499 6.98 0 2.684 7.058 3.4 11.228 3.4S26 25.284 26 22.6c0-3.278-3.092-5.988-7.499-6.98zM9.106 9.525c0-3.134 2.542-5.684 5.667-5.684 3.124 0 5.666 2.55 5.666 5.684 0 3.135-2.542 5.684-5.666 5.684-3.125 0-5.667-2.55-5.667-5.684zm5.667 14.998c-5.994 0-9.582-1.27-9.757-1.927.005-3.259 4.38-5.91 9.757-5.91 5.376 0 9.75 2.65 9.756 5.909-.17.656-3.758 1.928-9.756 1.928z" /><path d="M8.169 12.236c-3.92.379-6.927 2.459-6.93 4.961.1.383 1.552 1.01 4.08 1.368a6.948 6.948 0 00-.903 1.134C2.029 19.296 0 18.534 0 17.2c0-2.787 2.604-5.09 6.315-5.933a6.1 6.1 0 01-2.87-5.18C3.444 2.73 6.14 0 9.454 0c1.94 0 3.667.936 4.767 2.385-.49.037-.967.125-1.425.258a4.727 4.727 0 00-3.342-1.387c-2.632 0-4.772 2.167-4.772 4.832a4.839 4.839 0 003.031 4.498 7.09 7.09 0 00.455 1.65zM18.595 15.56l-.094.06.128.03a4.757 4.757 0 00-.034-.09z" /></svg>
Enter fullscreen mode Exit fullscreen mode

Now, update the navigation to render the icon if the method exists or just show the default icon.

  @if(method_exists($resource, 'icon'))
      {!! $resource::icon() !!}
   @else
     <svg class="sidebar-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill="var(--sidebar-icon)" d="M3 1h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3h-4zM3 11h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4h-4z"/>
      </svg>
  @endif
  <span class="sidebar-label">{{ $resource::label() }}</span>
Enter fullscreen mode Exit fullscreen mode
  1. Add method to define return the sort value To sort the navigation, it can be done at the navigation.blade.php but let's dive a little bit deeper into how Nova got those resources lists from. if you open vendor/laravel/nova/src/Tools/ResourceManager.php, you will find this:
    /**
     * Build the view that renders the navigation links for the tool.
     *
     * @return \Illuminate\View\View
     */
    public function renderNavigation()
    {
        $request = request();
        $groups = Nova::groups($request);
        $navigation = Nova::groupedResourcesForNavigation($request);

        return view('nova::resources.navigation', [
            'navigation' => $navigation,
            'groups' => $groups,
        ]);
    }
Enter fullscreen mode Exit fullscreen mode

if you start tracing you ended up on the

    /**
     * Get the sorting strategy to use for Nova resources.
     *
     * @return array
     */
    public static function sortResourcesWith()
    {
        return static::$sortCallback ?? function ($resource) {
            return $resource::label();
        };
    }
Enter fullscreen mode Exit fullscreen mode

If you notice, there is a $sortCallback function which you can register when Nova boots. So let's do that.

so in my NovaServiceProvider.php in the boot() func, we gonna add:

Nova::sortResourcesBy(function ($resource) {
    return method_exists($resource, 'sortOrder') ? 
         $resource::sortOrder() : $resource::label();
});
Enter fullscreen mode Exit fullscreen mode

So, we will look for the sortOrder method in our resource, if it doesn't it will fall back to the original label based sort.

That's all!!

  1. Both navigation and group use the defined sort value

For the group to order, I just utilize the sortorder of resource.

@php
        // dump($navigation);
        $orderNav = $navigation->sortBy(function($resources){
            // dump($resources->first());
            return $resources->first()::sortOrder();
        });
    @endphp
    @foreach($orderNav as $group => $resources)
       ... 
    @endforeach
Enter fullscreen mode Exit fullscreen mode

I ended up only showing the icon for the group and here is my navigation.blade.php. just FYI: not cleaned up/optimized

image

@if (count(Nova::availableResources(request())))
<ul class="sidemenu">
    @php
        $orderNav = $navigation->sortBy(function($resources){
            return $resources->first()::sortOrder();
        });
    @endphp
    @foreach($orderNav as $group => $resources)
      @if (count($groups) > 1)
      <li class="sidebar-dropdown mb-2">
        <input id="{{$group}}" class="toogle-group" type="checkbox" />
        <label for="{{$group}}" data-toggle="dropdown" class="flex items-center font-normal text-white mb-4 text-base no-underline dim">
            @if(method_exists($resources->first(), 'icon'))
                {!! $resources->first()::icon() !!}
            @endif
            {{ $group }}
            <svg class="text-gray-300 ml-auto h-5 w-5 transform group-hover:text-gray-400 transition-transform ease-in-out duration-150 focus:ring-indigo-500" viewBox="0 0 20 20" aria-hidden="true">
                <path d="M6 6L14 10L6 14V6Z" fill="currentColor" />
              </svg>
        </label>

        <ul class="dropdown-menu">
          @foreach($resources as $resource)
          <li>
            <router-link :to="{
                name: 'index',
                params: {
                    resourceName: '{{ $resource::uriKey() }}'
                }
            }" class="flex items-center font-normal text-white mb-4 text-base no-underline dim">

                <span class="sidebar-label">{{ $resource::label() }}</span>
            </router-link>
          </li>
          @endforeach
        </ul>
      </li>

      @else
        @foreach($resources as $resource)
        <li class="sidebar-dropdown">
            <router-link :to="{
                name: 'index',
                params: {
                    resourceName: '{{ $resource::uriKey() }}'
                }
            }" class="flex items-center font-normal text-white mb-6 text-base no-underline dim">
                @if(property_exists($resource, 'icon'))
                    {!! $resource::$icon !!}
                @elseif(method_exists($resource, 'icon'))
                    {!! $resource::icon() !!}
                @else
                <svg class="sidebar-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill="var(--sidebar-icon)" d="M3 1h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3h-4zM3 11h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4h-4z"
                    />
                </svg>
                @endif
                <span class="sidebar-label">{{ $resource::label() }}</span>
            </router-link>
        </li>
        @endforeach
      @endif
    @endforeach
</ul>
@endif
Enter fullscreen mode Exit fullscreen mode

Top comments (0)