Search code examples
vuejs3vue-routerheadless-ui

Vue3 + RouteLinks+headless UI: Menu does not close after click


I have a Vue3 project with vue-router and headless UI. In my Navbar component i want to have a dropDownMenu. For the dropDownMenu I came across headless UI Menu Component https://headlessui.com/vue/menu.

In the headlessUI example they use <a> tags for the links, which do work as intended. The Menu does indeed close after click. If I use instead <RouterLink>...</RouterLink> I will be redirected to the related path and View, but the Menu does not close.

I tried using the close() slot prop as they show here https://headlessui.com/vue/menu#closing-menus-manually but it still do not work.

  1. Why is not working with <RouterLink>?
  2. Should I use the <a> instead RouterLink? (As I understood <a> should be used for external navigation like to other pages(youtube etc) and vue-router for internal pages

Below my code: Navbar:

<template >
    <header class="sticky top-0 shadow-lg bg-navbar" v-show="loggedIn">
        <nav class="container flex flex-col sm:flex-row items-center gap-4  ">
        <div class="px-2 pt-2 sm:flex">
            <RouterLink :to="{name:'profile'}">
                <p class=" mt-1 block px-2 py-1 font-semibold rounded hover:bg-navbar-buttons " >Meine Medien</p>
            </RouterLink>
            <RouterLink v-if="showLoanBoard" :to="{name:'ausleihe'}">
                <p class="mt-1 block px-2 py-1 font-semibold rounded hover:bg-navbar-buttons">Ausleihe/Rückname</p>
            </RouterLink>
            <InvetoryDropDown v-if="showInventoryDropDown" /> 
            <InventoryMenu v-if="showInventoryDropDown"/>
            <InventoryMenuCopy v-if="showInventoryDropDown"/>
            <AdminDropDownVue v-if="showAdminDropDown" />
        </div>
        <div>
            <button 
            class=""
            @click="logout">Logout</button>
        </div>
    </nav>
    </header>
</template>

<script setup>
import AdminDropDownVue from './AdminDropDown.vue';
import InvetoryDropDown from './InventoryDropDown.vue';
import InventoryMenu from './InventoryMenu.vue';
import InventoryMenuCopy from './InventoryMenuCopy.vue';
import { computed, ref } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'


const store = useStore();
const router = useRouter();


const loggedIn = computed(() =>{
    return store.state.auth.status.loggedIn;
})

//A computed ref! Access with .value
const currentUser = computed(() =>{
    return store.state.auth.user;
})

const showAdminDropDown = computed(() =>{
    if(currentUser.value && currentUser.value.roles){
        return currentUser.value['roles'].includes('ADMIN')
    }
})

const showLoanBoard = computed(() =>{
    if(currentUser && currentUser.value.roles){
        return currentUser.value['roles'].includes('LIBRARIAN') 
        ||currentUser.value['roles'].includes('ADMIN') 
        ||currentUser.value['roles'].includes('LOAN_HELPER')  
    }
})

const showInventoryDropDown = computed(() =>{
    if(currentUser && currentUser.value.roles){
        return currentUser.value['roles'].includes('LIBRARIAN')
        || currentUser.value['roles'].includes('ADMIN')
        || currentUser.value['roles'].includes('INVENTORY_HELPER') 
    }
})

const logout = () => {
    store.dispatch('auth/logout');
    router.push('/login')
}

</script>

Menu with <a>

<template>
    <Menu as="div" class="relative inline-block text-left">
      <div>
        <MenuButton class="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
          A href
          <ChevronDownIcon class="-mr-1 h-5 w-5 text-gray-400" aria-hidden="true" />
        </MenuButton>
      </div>
  
      <transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95" enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75" leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
        <MenuItems class="absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
          <div class="py-1">
            <MenuItem v-slot="{ active }">
                <a href="/inventory" :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block px-4 py-2 text-sm']">Übersicht</a>
            </MenuItem>
            
            <MenuItem v-slot="{ active }">
                <a href="/inventory/add" :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block px-4 py-2 text-sm']">Aufnehmen</a>
            </MenuItem>
            <MenuItem v-slot="{ active }">
              <a href="/inventory/delete" :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block px-4 py-2 text-sm']">Löschen</a>
            </MenuItem>
          </div>
        </MenuItems>
      </transition>
    </Menu>
  </template>
  
  <script setup>
  import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
  import { ChevronDownIcon } from '@heroicons/vue/20/solid'
  </script>

Menu with RouterLink, which do not close on click

<template>
    <Menu as="div" class="relative inline-block text-left">
      <div>
        <MenuButton class="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
          RouteLinks
          <ChevronDownIcon class="-mr-1 h-5 w-5 text-gray-400" aria-hidden="true" />
        </MenuButton>
      </div>
  
      <transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95" enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75" leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
        <MenuItems class="absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
          <div class="py-1">
            <MenuItem v-slot="{ close }">
                <RouterLink 
                :class="[close ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block px-4 py-2 text-sm']" to="/inventory"
                @click="close">
                  Übersicht
                </RouterLink>
            </MenuItem>

            <MenuItem v-slot="{ close }">
                <RouterLink @click="close"  :class="[close ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block px-4 py-2 text-sm']" to="/inventory/add">Aufnehmen</RouterLink>
            </MenuItem>
          </div>
        </MenuItems>
      </transition>
    </Menu>
  </template>
  
  <script setup>
  import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
  import { ChevronDownIcon } from '@heroicons/vue/20/solid'
  </script>

RouteLinkMenu Picture of navbar


Solution

  • The @click listener in your RouterLink doesn't work. I'm not sure it's a pretty solution but I resolved it like this:

    <MenuItem v-for="item in userNavigation" :key="item.name" v-slot="{ active }">
        <a @click="routerLinkMethod(item.href)">
            {{ item.name }}
        </a>
    </MenuItem>
    

    In the methods:

    routerLinkMethod(href) {
        this.$router.push(href); 
    },