Navigation
Overview
With the Navigation component you can build various types of Websites Menus. Basically simple horizontal menus, dropdown menus and mega menus.
The following Components are are available:
NjNav
- The main Navigation component.- └─
NjBurger
- Handles open/close and visual representation of a mobile menu button toggle - └─
NjNavItems
- Dynamically renders the navigation items from Storyblok. - └─
NjNavItem
- A single Navigation item - basicallyspan
,nuxt-link
ora
tag - └─
NjNavDropdownItem
- With this component you can build Dropdown Menus with NjNav NjSlideOverMenu
- A custom NjSidebar implementation to show a fully functional mobile or desktop sliding Menu with Storyblok items.
Nav Core Tooling:
- 💫 nav vuex module - Enable
storeTemplates.nav
to register thenav store
module which handles the current state of the nav items - 💫 nav mixin - The mixin exposes getters like
mainNavItems
,isOpenBurger
, e.g. and actions to get easy access to your nav datasource
Storyblok Component Scheme
First we need to create a Storyblok component scheme which will act as datasource for the menu items.
Click to toggle Storyblok Code scheme for nav_sb_scheme.json
{
"components": [
{
"name": "navigation",
"display_name": null,
"created_at": "2021-06-21T09:06:52.447Z",
"updated_at": "2021-09-07T16:34:26.512Z",
"id": 1607403,
"schema": {
"logo": {
"type": "asset",
"filetypes": [
"images"
]
},
"navigation": {
"type": "bloks",
"restrict_components": true,
"component_whitelist": [
"navigation_item",
"navigation_item_dropdown"
]
},
"aside": {
"type": "bloks",
"restrict_components": true,
"component_whitelist": [
"navigation_item",
"navigation_item_dropdown"
]
},
"subnavigation": {
"type": "bloks",
"restrict_components": true,
"component_whitelist": [
"navigation_item"
]
},
"mobile_navigation": {
"type": "bloks",
"restrict_components": true,
"component_whitelist": [
"navigation_item",
"navigation_item_dropdown"
]
},
"mobile_subnavigation": {
"type": "bloks",
"restrict_components": true,
"component_whitelist": [
"navigation_item"
]
},
"footer_navigation": {
"type": "bloks",
"maximum": "",
"restrict_components": true,
"component_whitelist": [
"navigation_item"
]
}
},
"image": null,
"preview_field": null,
"is_root": true,
"preview_tmpl": null,
"is_nestable": false,
"all_presets": [],
"preset_id": null,
"real_name": "navigation",
"component_group_uuid": null
},
{
"name": "navigation_item",
"display_name": null,
"created_at": "2021-06-21T09:06:52.113Z",
"updated_at": "2021-09-07T16:34:26.468Z",
"id": 1607402,
"schema": {
"label": {
"type": "text"
},
"link": {
"type": "multilink"
}
},
"image": null,
"preview_field": null,
"is_root": false,
"preview_tmpl": null,
"is_nestable": true,
"all_presets": [],
"preset_id": null,
"real_name": "navigation_item",
"component_group_uuid": null
},
{
"name": "navigation_item_dropdown",
"display_name": null,
"created_at": "2021-09-07T16:32:42.445Z",
"updated_at": "2021-09-07T16:34:26.531Z",
"id": 1766141,
"schema": {
"label": {
"type": "text"
},
"subNavigation": {
"type": "bloks",
"restrict_components": true,
"component_whitelist": [
"navigation_item"
],
"required": true
}
},
"image": null,
"preview_field": null,
"is_root": false,
"preview_tmpl": null,
"is_nestable": true,
"all_presets": [],
"preset_id": null,
"real_name": "navigation_item_dropdown",
"component_group_uuid": null
}
]
}
storyblok push-components ./nav_sb_scheme.json --space <YOUR_SPACE_ID>
Enable Nav Store Config
@nujek/ui will a nav
store module which handles navigation states correctly and works with our <NjNav> component out of the box.
export default {
nujekUi: {
storeTemplates: {
nav: true
}
}
}
`index.js`
file inside the `store/`
directory to avoid an error on running the dev server.
my-project/
-- components/
-- pages/
-- plugins/
-- store/
-- index.js # <- add this file
Basic Example
NjNav
is designed to be as customizable as possible but before we dive deeper we looking into an easy "to-go" example.
-
First import ~/nujek-ui/nav-mixin from
@nujek
package to expose nav store props. You can fetch your datasource yourself but this is a convenient way in nujek to get nav data as easy as possible. -
Since our NjNavMixin exposes some useful getters like
mainNavItems
we can use it to fill ourNjNav :nav-items="mainNavItems"
component with some data from storyblok. -
We use
logo-image
andclasses
props to style the navigation.
<template>
<NjNav
:enable-burger-menu="false"
:nav-items="mainNavItems"
logo-image="/logo.svg"
:classes="{
navItemsWrapper: 'hidden bg-yellow-400 flex space-x-4 sm:flex',
header:
'bg-yellow-200 w-full flex justify-between space-x-12 px-6 md:px-8 xl:max-w-screen-xl items-center',
logoWrapper: 'flex-shrink-0'
}"
/>
</template>
<script>
import NjNavMixin from '~nujek-ui/mixins/nav-mixin.js'
export default {
mixins: [NjNavMixin]
}
</script>
Even if its entirely possible to build a menu using only a single NjNav
component you will be limited in terms of customization. We strongly recommend to use NjNav
available slots
and the child components NjNavItems
, NjNavItem
, NjNavDropdownItem
. You will find a more real world example in the part "Advanced Example".
Advanced Example
The advanced example has all customization possibilities to build simple and very complex menus like Dropdown, Mega Menus e.g. The reason why we prefer this approach is that we can control every part of the navigation with the given slots and child components of NjNav.
-
Like you have seen in the basic example we make use of
NjNavMixin
to get our datasource for the menu items. We use nowNjNavItems :items="mainNavItems"
to fill our datasource. -
NjNavItems
will handle our datasource (nav items from storyblok) and exposes slot props which you can see in the bracket{ label, tag, linkTo }
. -
With the given slot props you can use
NjNavItem
to render a single menu element. -
Hint: For the
#aside
slot we use a more "shorthand" approach to fill our menu items.
<template>
<NjNav>
<template #burger-menu>
<NjBurger :open.sync="isOpenBurger" :classes="{ wrapper: 'md:hidden', bar: 'bg-gray-800' }" />
</template>
<template #logo>
<div class="flex flex-shrink-0 w-40 lg:w-48">
<nuxt-link to="/" class="block">
<img src="/logo.svg" alt="logo">
</nuxt-link>
</div>
</template>
<template #nav>
<div
class="flex flex-grow justify-end md:justify-between"
>
<!-- main nav desktop -->
<NjNavItems v-slot="{ label, tag, linkTo }" :items="mainNavItems" class="hidden md:flex space-x-6">
<NjNavItem
:link-to="linkTo"
:tag="tag"
:label="label"
class="cursor-pointer hover:text-gray-400"
/>
</NjNavItems>
</div>
</template>
<template #aside>
<!-- nav aside -->
<NjNavItems v-slot="item" :items="asideNavItems" class="flex items-center space-x-4">
<NjNavItem v-bind="{ ...item }" class="cursor-pointer hover:text-gray-400" />
</NjNavItems>
</template>
</NjNav>
</template>
<script>
import NjNavMixin from '~nujek-ui/mixins/nav-mixin.js'
export default {
mixins: [NjNavMixin]
}
</script>
Add a SideNav interacting with the Burger
Almost every website nowadays has a mobile menu which is mostly handled by a "burger" icon. When you click on such a burger it will open a overlay where you can see the menu items. Since this behaviour is often needed we prepared some helpful components to achieve this use case.
- The following example is similar to the Advanced example but uses
isOpenBurger
prop (which is exposed fromNjNavMixin
) to handle the open/close state of the menu. - We build a separate component named
SlideOverMenu
which is kind of a toggle panel which is shown when the burger is active or closed when its not active. - We're using the .sync parameter here which is a shorthand to achieve "two way-binding" for a prop.
SlideOverMenu Component
Lets explain this component step-by-step
-
First we're using
NjSidebar
component which has some nice features built in liketransitions
,backdrop effect
,close handler
. But you can write your own component of course. -
We're using the default slot of
NjSidebar
to place again ourNjNavItems
component which will be filled with our Storyblok nav items datasource. -
The
.sync
prop saves us a few lines. What we do here is to just two-way bind a prop and emit it back to the parent. -
SlideOverMenu.vue
<template>
<NjSidebar v-bind="{ ...$props, ...$attrs }" :show.sync="isOpenBurgerComp">
<template #default>
<div class="mt-16 pl-6">
<NjNavItems
:items="items"
aria-label="Slide Over Menu"
:classes="{
nav: 'space-y-1',
navItem:
'text-white hover:text-white flex items-center px-3 py-2 text-sm'
}"
/>
</div>
</template>
</NjSidebar>
</template>
<script>
export default {
props: {
items: {
type: Array,
default: () => []
},
isOpenBurger: {
type: Boolean,
default: false
}
},
computed: {
isOpenBurgerComp: {
get () {
return this.isOpenBurger
},
set (val) {
this.$emit('update:isOpenBurger', val)
}
}
}
}
</script>
Nav Component
-
The only new addition here is the usage of
SlideOverMenu
component. -
We use
mobileNavItems
getter fromNjNavMixin
to fill our mobile menu items. -
And we're going to use
isOpenBurger
getter as well fromNjNavMixin
to sync states between the Burger and theSlideOverMenu
. -
Nav.vue
<template>
<div>
<NjNav
logo-image="/logo.svg"
>
<template #burger-menu>
<NjBurger :open.sync="isOpenBurger" :classes="{ wrapper: 'md:hidden', bar: isOpenBurger ? 'bg-white' : 'bg-gray-800' }" />
</template>
<template #nav>
<div
class="flex flex-grow justify-end md:justify-between"
>
<!-- main nav desktop -->
<NjNavItems v-slot="{ label, tag, linkTo }" :items="mainNavItems" class="hidden md:flex space-x-6">
<NjNavItem
:link-to="linkTo"
:tag="tag"
:label="label"
class="cursor-pointer hover:text-gray-400"
/>
</NjNavItems>
</div>
</template>
</NjNav>
<SlideOverMenu
:classes="{ sidenav: 'bg-gray-800 w-11/12', backdrop: 'bg-white bg-opacity-75' }"
:is-open-burger.sync="isOpenBurger"
:items="mobileNavItems"
/>
</div>
</template>
<script>
import NjNavMixin from '~/mixins/nav-mixin.js'
export default {
mixins: [NjNavMixin]
}
</script>
Component Api
<NjNav />
Props
Prop | Default | Description |
`navItems` * |
`[]` |
`Array`
The datasource where nav-items are stored.
|
`asideNavItems` |
`[]` |
`Array`
|
`logoImage` |
`` |
`String`
|
`logoAlt` |
`logo` |
`String`
|
`logoLink` |
`/` |
`String | Object`
Using `<NuxtLink>` under the hood
|
`isOpenBurger` |
`false` |
`Boolean`
|
`burgerBarColorClass` |
`bg-gray-800` |
`String`
The burger button color as tailwind class
|
`burgerWrapperClass` |
`` |
`String`
For example if you want a bg color for the burger box
|
`enableBurgerMenu` |
`true` |
`Boolean`
Show/Hide burger menu
|
Classes and variants format
Property | Description |
`wrapper` |
Wrapper of the navigation |
`container` |
A child container of the `wrapper` |
`header` |
A child `<header>` of the `container` |
`logoWrapper` |
Deprecated: For backwards compatibility. Use NjNav slots to customize |
`logoLink` |
Deprecated: For backwards compatibility. Use NjNav slots to customize |
`navItemsWrapper` |
Deprecated: For backwards compatibility. Use NjNav slots to customize |
`header` |
Deprecated: For backwards compatibility. Use NjNav slots to customize |
`header` |
Deprecated: For backwards compatibility. Use NjNav slots to customize |
Defaults
{
classes: {
wrapper: 'flex justify-center w-full',
container: 'py-2 max-w-screen-xl w-full px-6',
header: 'flex items-center',
logoWrapper: 'flex flex-shrink-0 w-40 lg:w-48',
logoLink: 'block',
navItemsWrapper: 'flex flex-grow justify-end md:justify-between',
mainNavItemsWrapper: 'hidden md:flex space-x-4',
asideNavItemsWrapper: 'flex items-center space-x-4'
}
}
Slots
Slot | Slot Props/Scoped Slots | Description |
`#header` |
Includes
#burger-menu , #logo , #nav and #aside slots.
Use it to complete override NjNav
|
|
`#burger-menu` |
`isOpenBurgerComp`: Burger Status - open/close |
Place your burger menu which you can open/close on click
|
`#logo` |
`logoLink`: Like nuxt-link, `logoImage`: filename |
Place your logo in this slot with given
logoLink and logoImage
|
`#nav` |
`navItems:` Includes the datasource for the nav items |
Use
NjNavItems here to render your Menu
|
`#aside` |
`asideNavItems:` Includes the datasource for the aside nav items |
Use
NjNavItems here to render your Aside Menu
|
<NjNavItems />
With NjNavItems you can render a list of items from storyblok. You can choose between two navType
s. Either you select single
for a single NjNavItem
or dropdown
for a NjNavDropdownItem
(If you want more customization e.g. dropdown on hover just override the default slot and use NjNavDropdownItem with your own modifier)
Props
Prop | Default | Description |
`items` * |
`[]` |
`Array`
The datasource where nav-items are stored.
|
`navType` |
`single` |
`String`
Either
`single` or `dropdown`
|
`tag` |
`nav` |
`String`
Either
`nav` or `single`
|
`ariaLabel` |
`Nav` |
`String`
The datasource where nav-items are stored.
|
Classes and variants format
Property | Description |
`nav` |
Wrapper of the nav (usually `<nav>` or `<div>` |
`navItems` |
|
`navItem` |
A single nav item |
Defaults
{
classes: {
nav: '',
navItems: '',
navItem: 'cursor-pointer'
}
}
Slots
Slot | Slot Props/Scoped Slots | Description |
`#default` |
Make use of this! If you want to render a different single item than
NjNavItems allows. Like you want a<NjNavDropdownItem eventModifier="hover"> then override the #default slot
|
<NjNavItem />
This will render a single Item within NjNavItems
. Use it for simple menus.
Props
Prop | Default | Description |
`linkTo` * |
`null` |
`String | Object`
|
`tag` * |
`` |
`String`
Use
<a>, <span> or <nuxt-link>
|
`label` |
`` |
`String`
The link label you want to use
|
Slots
Slot | Slot Props/Scoped Slots | Description |
`#default` |
Use it if you want to customize the
label
|
<NjNavDropdownItem />
Similar to NjNavItem
which should be use as a child of NjNavItems
.
- With this component you can nest another
NjNavItems
component (or whatever content you like). - You will get additional information with slot props like
isDropdownOpen
,activeIndex
andactiveDropdownIndex
. - You can use the
eventModifier
prop to choose betweenclick
,hover
(open dropdown on mouseover) andnone
(do nothing) events.
Props
Prop | Default | Description |
`linkTo` |
`null` |
`String | Object`
|
`tag` |
`` |
`String`
Use
<a>, <span> or <nuxt-link>
|
`label` |
`` |
`String`
|
`index` |
`-1` |
`String`
|
`eventModifier` |
`click` |
`click`|`hover`|`none`
|
Events
Event | Description |
`@open-dropdown` |
Trigger when dropdown is active isDropdownOpen === true |
`@close-dropdown` |
Trigger when dropdown is not active isDropdownOpen === false |
<NjBurger />
A burger is a simple toggle which is mostly used for mobile menus.
The component is build to work with <NjNav>
but can be used independently.
<NjNav>
<template #burger-menu>
<NjBurger :open.sync="isOpenBurger" :classes="{
wrapper: 'md:hidden', bar: isOpenBurger ? 'bg-white' : 'bg-gray-800' }" />
</template>
</Njav>
Props
Prop | Default | Description |
`open` |
`false` |
`Boolean`
Use always with
`open..sync` modifier
|
Classes and variants format
Property | Description |
`wrapper` |
Wrapper of the burger |
`button` |
The `<button>` which the user is clicking |
`bar` |
The burger has three `bars` inside the button. With bar you change change the color/size of the burger bar |
Defaults
{
fixedClasses: {
wrapper: 'nj-burger',
button: 'relative block border-0 focus:outline-none',
bar: ''
},
classes: {
bar: 'bg-gray-800'
}
}
Slots
Slot | Slot Props/Scoped Slots | Description |
`#default` |
With the default slot you can replace the bar inside the burger
|
Theme Configuration
The following example will hide the Burger Button on default for smaller screens
import Vue from 'vue'
import Nujek from '~nujek-ui/plugin.js'
import { NjSection, NjBurger, NjSidebar } from '~nujek-ui/components'
const settings = {
NjBurger: {
component: NjBurger,
props: {
fixedClasses: {
'wrapper': 'lg:hidden'
}
}
}
}
Vue.use(Nujek, settings)