[Vuejs]-Popover on hover vue headllessui

3👍

Seems like common request in HeadlessUI community.

Another solution found this solution on Github that worked fine for me.

for vanilla vue 3 with js the original solution link Github Issue

Nuxt 3 with typescript maintaining accessibility here’s the code ↓

<script setup lang="ts">
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'

interface Props {
  label: string
  hasHref?: boolean
  href?: string
}
const props = defineProps<Props>()

const popoverHover = ref(false)
const popoverTimeout = ref()

const hoverPopover = (e: any, open: boolean): void => {
  popoverHover.value = true
  if (!open) {
    e.target.parentNode.click()
  }
}

const closePopover = (close: any): void => {
  popoverHover.value = false
  if (popoverTimeout.value) clearTimeout(popoverTimeout.value)
  popoverTimeout.value = setTimeout(() => {
    if (!popoverHover.value) {
      close()
    }
  }, 100)
}
</script>

<template>
  <Popover v-slot="{ open, close }" class="relative">
    <PopoverButton
      :class="[
        open ? 'text-primary' : 'text-gray-900',
        'group inline-flex items-center rounded-md bg-white text-base font-medium hover:text-primary focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2'
      ]"
      @mouseover="(e) => hoverPopover(e, open)"
      @mouseleave="closePopover(close)"
    >
      <span v-if="!hasHref">{{ props.label }}</span>
      <span v-else>
        <NuxtLink :to="href">
          {{ props.label }}
        </NuxtLink>
      </span>
      <IconsChevronDown
        :class="[
          open ? 'rotate-180 transform text-primary' : '',
          ' ml-1 h-5 w-5 text-primary transition-transform group-hover:text-primary'
        ]"
        aria-hidden="true"
      />
    </PopoverButton>

    <transition
      enter-active-class="transition ease-out duration-200"
      enter-from-class="opacity-0 translate-y-1"
      enter-to-class="opacity-100 translate-y-0"
      leave-active-class="transition ease-in duration-150"
      leave-from-class="opacity-100 translate-y-0"
      leave-to-class="opacity-0 translate-y-1"
    >
      <PopoverPanel
        class="absolute left-1/2 z-10 mt-3 ml-0 w-auto min-w-[15rem] -translate-x-1/2 transform px-2 sm:px-0"
        @mouseover.prevent="popoverHover = true"
        @mouseleave.prevent="closePopover(close)"
      >
        <div
          class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
        >
          <div class="relative grid gap-1 bg-white p-3">
            <slot> put content here </slot>
          </div>
        </div>
      </PopoverPanel>
    </transition>
  </Popover>
</template>
👤Sam K

0👍

It is in the docs:showing-hiding-popover.
open is an internal state used to determine if the component is shown or hidden. To implement your own functionality, you can remove it and use the static prop to always render a component. Then you can mange the visibility with your own state ref and a v-if/v-show. The mouse-action has to be in the upper scope, so it is not triggered leaving the component, e.g. moving the mouse from button to panel.

Below is a modified example from the API documentation:

<template>
  <Popover
    @mouseenter="open = true"
    @mouseleave="open = false"
    @click="open = !open"
  >
    <PopoverButton @click="open = !open">
      Solutions
      <ChevronDownIcon :class="{ 'rotate-180 transform': open }" />
    </PopoverButton>

    <div v-if="open">
      <PopoverPanel static>
        <a href="/insights">Insights</a>
        <a href="/automations">Automations</a>
        <a href="/reports">Reports</a>
      </PopoverPanel>
    </div>
  </Popover>
</template>

<script setup>
import { ref } from 'vue';
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue';
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
const open = ref(false);
</script>
👤WnaJ

Leave a comment