This seem to have worked for me (Nuxt and Bootstrap 5), idea was to have dropdown as child component that toggles via data, this is still work in progress but it seem to work, I am learning Js and Vue as I go so this might not be the best way to do it.
ThePrimary.vue
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<nuxt-link class="navbar-brand" to="/">Navbar</nuxt-link>
<button
@click="isNavbarCollapsed = !isNavbarCollapsed"
ref="navbar-toggler"
:aria-expanded="[!isNavbarCollapsed ? 'true' : 'false']"
:class="{ collapsed: isNavbarCollapsed}"
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div
:class="{ show: !isNavbarCollapsed}"
class="collapse navbar-collapse"
id="navbarNavDropdown"
>
<NavbarNav :items="loadedPrimaryMenu" />
</div>
</div>
</nav>
</template>
<script>
import NavbarNav from '@/components/Navigation/ThePrimary/NavbarNav'
export default {
name: 'TheNavigationPrimary',
data() {
return {
isNavbarCollapsed: true
}
},
computed: {
loadedPrimaryMenu() {
return this.$store.getters.loadedPrimaryMenu
}
},
components: {
NavbarNav
}
}
</script>
<style scoped lang="scss">
</style>
NavbarNav.vue
<template>
<ul class="navbar-nav">
<li
v-for="item in items"
:key="item.id"
class="nav-item"
:class="{ dropdown: hasChildren(item.children) }"
>
<NavLink
v-if="!hasChildren(item.children)"
:attributes="item"
/>
<NavbarNavDropdownMenu
v-else
:item="item"
/>
</li>
</ul>
</template>
<script>
import NavbarNavDropdownMenu from "@/components/Navigation/ThePrimary/NavbarNavDropdownMenu";
import NavLink from '@/components/Navigation/NavLink';
export default {
name: "NavbarNav",
props: {
items: {
type: Array,
required: true,
},
},
data() {
return {
};
},
methods: {
hasChildren(item) {
return item.length > 0 ? true : false;
},
},
components: {
NavbarNavDropdownMenu,
NavLink
}
};
</script>
<style scoped lang="scss">
</style>
NavbarNavDropdownMenu.vue
<template>
<span v-if="item">
<nuxt-link
to="#"
@click.prevent.native="openDropdownMenu"
v-click-outside="closeDropdownMenu"
:title="item.title"
:class="[
item.cssClasses,
{ show: isDropdownMenuVisible }
]"
:id="`navbarDropdownMenuLink-${item.id}`"
:aria-expanded="[isDropdownMenuVisible ? true : false]"
class="nav-link dropdown-toggle"
aria-current="page"
role="button"
data-toggle="dropdown"
>
{{ item.label }}
</nuxt-link>
<ul
:class="{ show: isDropdownMenuVisible }"
:aria-labelledby="`navbarDropdownMenuLink-${item.id}`"
class="dropdown-menu"
>
<li v-for="item in item.children" :key="item.id">
<NavLink
:attributes="item"
class="dropdown-item"
/>
</li>
</ul>
</span>
</template>
<script>
import NavLink from '@/components/Navigation/NavLink';
export default {
name: "DropdownMenu",
props: {
item: {
type: Object,
required: true,
},
},
data() {
return {
isDropdownMenuVisible: false,
};
},
methods: {
openDropdownMenu() {
this.isDropdownMenuVisible = !this.isDropdownMenuVisible;
},
closeDropdownMenu() {
this.isDropdownMenuVisible = false;
}
},
components: {
NavLink
}
};
</script>
<style scoped lang="scss">
</style>
NavLink.vue
<template>
<component
v-bind="linkProps(attributes.path)"
:is="attributes"
:title="attributes.title"
:class="[ attributes.cssClasses ]"
class="nav-link active"
aria-current="page"
prefetch
>
{{ attributes.label }}
</component>
</template>
<script>
export default {
name: 'NavLink',
props: {
attributes: {
type: Object,
required: true
}
},
methods: {
linkProps (path) {
if (path.match(/^(http(s)?|ftp):\/\//) || path.target === '_blank') {
return {
is: 'a',
href: path,
target: '_blank',
rel: 'noopener'
}
}
return {
is: 'nuxt-link',
to: path
}
}
}
}
</script>
<style scoped lang="scss">
</style>
To close dropdown on click outside
import Vue from 'vue'
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
if (!(el == event.target || el.contains(event.target))) {
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
});