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 - basically span, nuxt-link or a 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 the nav 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.

nuxt.config.js
export default {
  nujekUi: {
    storeTemplates: {
      nav: true
    }
  }
}

IMPORTANT: Create an `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 our NjNav :nav-items="mainNavItems" component with some data from storyblok.

  • We use logo-image and classes 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 now NjNavItems :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 from NjNavMixin) 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 like transitions, backdrop effect, close handler. But you can write your own component of course.

  • We're using the default slot of NjSidebar to place again our NjNavItems 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>

  • The only new addition here is the usage of SlideOverMenu component.

  • We use mobileNavItems getter from NjNavMixin to fill our mobile menu items.

  • And we're going to use isOpenBurger getter as well from NjNavMixin to sync states between the Burger and the SlideOverMenu.

  • 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 navTypes. 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, activeIndexand activeDropdownIndex.
  • You can use the eventModifier prop to choose between click, hover (open dropdown on mouseover) and none (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)