0

I'm trying to implement the popover from headlessui of vue package with hover. I try to use the mouseenter and mouseleave and the other mouse events but nothing change.

Any solution? There is a better solution? i search on internet I cant nothing about this. I search on headlessui github discussions but nothing.

<template>
  <div class="fixed top-16 w-full max-w-sm px-4">
    <Popover v-slot="{ open }" class="relative">
      <PopoverButton
        :class="open ? '' : 'text-opacity-90'"
        class="group inline-flex items-center rounded-md bg-orange-700 px-3 py-2 text-base font-medium text-white hover:text-opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
      >
        <span>Solutions</span>
        <ChevronDownIcon
          :class="open ? '' : 'text-opacity-70'"
          class="ml-2 h-5 w-5 text-orange-300 transition duration-150 ease-in-out group-hover:text-opacity-80"
          aria-hidden="true"
        />
      </PopoverButton>

      <transition
        enter-active-class="transition duration-200 ease-out"
        enter-from-class="translate-y-1 opacity-0"
        enter-to-class="translate-y-0 opacity-100"
        leave-active-class="transition duration-150 ease-in"
        leave-from-class="translate-y-0 opacity-100"
        leave-to-class="translate-y-1 opacity-0"
      >
        <PopoverPanel
          class="absolute left-1/2 z-10 mt-3 w-screen max-w-sm -translate-x-1/2 transform px-4 sm:px-0 lg:max-w-3xl"
        >
          <div
            class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
          >
            <div class="relative grid gap-8 bg-white p-7 lg:grid-cols-2">
              <a
                v-for="item in solutions"
                :key="item.name"
                :href="item.href"
                class="-m-3 flex items-center rounded-lg p-2 transition duration-150 ease-in-out hover:bg-gray-50 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
              >
                <div
                  class="flex h-10 w-10 shrink-0 items-center justify-center text-white sm:h-12 sm:w-12"
                >
                  <div v-html="item.icon"></div>
                </div>
                <div class="ml-4">
                  <p class="text-sm font-medium text-gray-900">
                    {{ item.name }}
                  </p>
                  <p class="text-sm text-gray-500">
                    {{ item.description }}
                  </p>
                </div>
              </a>
            </div>
            <div class="bg-gray-50 p-4">
              <a
                href="##"
                class="flow-root rounded-md px-2 py-2 transition duration-150 ease-in-out hover:bg-gray-100 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
              >
                <span class="flex items-center">
                  <span class="text-sm font-medium text-gray-900">
                    Documentation
                  </span>
                </span>
                <span class="block text-sm text-gray-500">
                  Start integrating products and tools
                </span>
              </a>
            </div>
          </div>
        </PopoverPanel>
      </transition>
    </Popover>
  </div>
</template>
Dasd
  • 7
  • 4

2 Answers2

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
  • 106
  • 5
  • I favor this answer, because it does not break the accessibility, like mentioned in the link. However, when the mouseover event is triggered by the `ChevronDownIcon` the referenced element is wrong. It might be better to reference the the element directly. – WnaJ Feb 09 '23 at 11:03
  • Hey, I have solved it like this, I had to spend sometime on it thou but it solved the issue when focusing the parent. I am using nuxt3 with typescript. I'll modify the response as here is not letting me – Sam K Feb 09 '23 at 11:51
  • Hey @WnaJ the code with nuxt solution solves mostly the requirements of a menu that should be opened on hover/click and could have a link in the parent menu element. – Sam K Feb 09 '23 at 12:03
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
  • 68
  • 7