Search code examples
javascriptvue.jsvuejs3tailwind-cssheadless-ui

Popover on hover vue headllessui


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>

Solution

  • 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>