Sidebar Navigation
The sidebar of a Nexus application is configured in resources/nexus/vue/LayoutSidebar.vue. This gives the application developer full control over the menu structure.
Nexus includes two helper components for creating standard menu layouts. It supports up to 3 levels of navigation (links), optionally broken up into sections.
Links
A typical link is added like this:
<LayoutSidebarLink
active="nexus.home"
:href="$route('nexus.home')"
text="Dashboard"
/>
The href must be a link to an internal Inertia.js page (external links are not supported, as this would be confusing for users).
The text prop is the text to be displayed on the link.
HTML in Links
To include icons or other HTML you can use the #text slot instead:
<LayoutSidebarLink
active="nexus.home"
:href="$route('nexus.home')"
>
<template #text>
<FontAwesome :icon="faHome" text="Dashboard" />
</template>
</LayoutSidebarLink>
Active Links
The active parameter determines when the link is styled as the active (current) link. Only one link at each level should be active at once. In the simplest case, it is the name of the route you are linking to, as above. If it is a section with several sub-pages, use a wildcard:
<LayoutSidebarLink
active="examples.*"
:href="$route('examples.index')"
text="Examples"
/>
If there are several pages with different prefixes, use an array of strings:
<LayoutSidebarLink
:active="['examples.*', 'more-examples.*']"
:href="$route('examples.index')"
text="Examples"
/>
For more advanced scenarious, set active to a boolean:
<LayoutSidebarLink
:active="$routeIs('examples.index') && $page.props.request.deleted === '1'"
:href="$route('examples.index', { deleted: true })"
text="Deleted"
/>
Note: Request parameters are always strings. You must return a boolean, not a string, so simplifying this to ... && $page.props.request.deleted would not work (it would return the string '1').
Nested Menus
Each link can have children, up to 3 levels deep:
<LayoutSidebarLink
active="examples.*"
:href="$route('examples.index')"
text="Examples"
>
<LayoutSidebarLink
active="examples.one"
:href="$route('examples.one')"
text="Example 1"
/>
<LayoutSidebarLink
active="examples.two"
:href="$route('examples.two')"
text="Example 2"
>
...
</LayoutSidebarLink>
</LayoutSidebarLink>
[Demo with 3 levels - expand the "Demo Package" section to see it.]
The children are only displayed when the link is active.
Self-Links
Generally the first child link should be a self-link - i.e. a link to the same page as the parent link - to make it easy for the user to get back to that page:
<LayoutSidebarLink
active="examples.*"
:href="$route('examples.index')"
text="Examples"
>
<template #self>
<LayoutSidebarLink
active="examples.index"
:href="$route('examples.index')"
text="Examples"
/>
</template>
...
</LayoutSidebarLink>
This is similar to the WordPress admin area.
If the self-link is the only child, it will be hidden. This allows you to conditionally hide the other children (see below) and the menu will adapt automatically if there is only the one link left.
Section Navigation
As an alternative to nested menus, you may want to consider using section nav bars (tabs) for some links.
Sections (Subheadings)
After the first batch of links with no heading, there may be any number of sections with headings. Sections may not be nested, but each section may contain up to three levels of links. For example:
<LayoutSidebarSection title="Administration">
<LayoutSidebarLink
active="settings.*"
:href="$route('settings.index')"
text="Settings"
>
...
</LayoutSidebarLink>
...
</LayoutSidebarSection>
Collapsible Sections
You can optionally make the section collapsible. This is useful for hiding less important sections such as Administration that aren't regularly needed.
<LayoutSidebarSection title="Administration" collapsible>
...
</LayoutSidebarSection>
Collapsible sections should generally be at the end of the sidebar, after any non-collapsible sections/links. It is less confusing for users if you make all sections either collapsible or not, rather than mixing the two - although there may be cases where that is necessary.
By default, collapsible sections are collapsed when the page loads. You can choose to have them open by default:
<LayoutSidebarSection title="Administration" collapsible default-open>
...
</LayoutSidebarSection>
When the user expands/collapses a section, it is remembered using localStorage - i.e. per browser, not per user. The key name is sidebar-expanded- + the title in lowercase - so if two subheadings have the same title they will conflict, and if you change a title the state will be forgotten. You can override this by providing the id parameter, e.g. id="administration".
Note: Because the state is remembered, you may not see any difference when adding/removing the default-open prop. To test it, clear your local storage using web developer tools, then refresh the page.
HTML in Titles
Similar to links, you can use the #title slot instead of the title prop if you want to use any HTML:
<LayoutSidebarSection>
<template #title>
<span class="mr-1">{{ school.school_name }}</span>
<span v-if="school.is_partner" class="badge badge-info">Partner</span>
</template>
...
</LayoutSidebarSection>
However, if you use collapsible with the #title slot, you must also provide the id parameter since it can't be generated automatically:
<LayoutSidebarSection collapsible id="demo package">
<template #title>
<em>Demo</em> Package
</template>
...
</LayoutSidebarSection>
(If you forget, you will get an error message in the console.)
Conditionally Show/Hide Links
To conditionally show/hide a link, use v-if on the link itself.
If you need data from the server (e.g. user permissions), add it to nav array in App\Providers\InertiaServiceProvider:
Inertia::share('nav', static function () {
//...
return [
//...
'userCan' => user_can([
Permission::ViewExamples,
]),
];
});
Then use v-if="$page.props.nav.<name>":
<LayoutSidebarLink
v-if="$page.props.nav.userCan.ViewExamples"
active="examples.*"
:href="$route('examples.index')"
text="Examples"
/>
Warning: The nav array is included in the response on every Inertia.js page load - so don't load a large amount of data.
Sidebar Links Provided by Packages
Some Nexus packages provide components so you can easily add links to their pages, e.g.
<LayoutSidebarLinkUsers />(Nexus Users - admin links)<LayoutSidebarLinkSwitchBack />(Nexus Users - masquerade function)<LayoutSidebarLinkMediaLibrary />(Nexus Media Library)
If you need more control, you can manually add the links instead.
Notes for Package Developers
- These components should be functional components - otherwise Vue thinks there is always a child link even when they are all hidden, so the link/section collapsing doesn't work correctly.
- Data can be added to every page using
Inertia::share()in the service provider. It should be namespaced using the package name - e.g.nexusUsers_userCan.
Component Reference
<LayoutSidebar>
Props: None.
Slots: None.
Events: None.
<LayoutSidebarLink>
Props:
href(String - required) - The URL to link to.text(String - required unless the#textslot is used) - The link text.active- (Boolean, String or Array) - Either a boolean to say whether the link is active or not, or a string/array of route name patterns that should be treated as active.link-class- (String, Array or Object) - Classes to add to the<a>tag.
Slots:
#text- An alternative to thetextprop, in case you want to format it with HTML.#self- A link to the default page in the section. If this is the only child link visible, it is hidden.- default - Optional child links.
Events: None.
<LayoutSidebarSection>
Props:
title(String, required unless the#titleslot is used) - The section title.collapsible(Boolean) - Allow the user to open/close the section.default-open(Boolean) - Default a collapsible section to open instead of closed.id(String) - The ID to use to record the collapse state. Defaults to thetitleprop in lowercase. Required ifcollapsibleis true and the#titleslot is used rather than thetitleprop.
Slots:
- default - Child links. If there are no child links visible, the section is hidden.
#title- An alternative to thetitleprop, in case you want to format it with HTML.
Events: None.