Components

Tooltip

A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.

Source Code

You will need to install the HeadlessUI Float - Vue package

yarn add @headlessui-float/vue

Create the following composable called useHoverMenu.ts in your project.

export const useHoverMenu = (delay = 350, openDelay = 200) => {
  const show = ref(false);
  const closeTimer = ref(null) as Ref<ReturnType<typeof setTimeout> | null>;
  const openTimer = ref(null) as Ref<ReturnType<typeof setTimeout> | null>;

  const open = () => {
    if (closeTimer.value) {
      clearTimeout(closeTimer.value);
      closeTimer.value = null;
    }
    openTimer.value = setTimeout(() => {
      show.value = true;
      openTimer.value = null;
    }, openDelay);
  };

  const close = () => {
    if (openTimer.value) {
      clearTimeout(openTimer.value);
      openTimer.value = null;
    }
    closeTimer.value = setTimeout(() => {
      show.value = false;
      closeTimer.value = null;
    }, delay);
  };

  return { show, closeTimer, openTimer, open, close };
};

Copy the following code into your project.

<template>
  <HPopover ref="parent" @mouseleave="close()">
    <Float
      :arrow="arrow"
      :offset="offset"
      flip
      shift
      :placement="placement"
      :show="show"
      transition-name="popover"
    >
      <HPopoverButton as="div" @mouseover="open()">
        <slot></slot>
      </HPopoverButton>

      <HPopoverPanel
        class="rounded-md border bg-popover text-popover-foreground"
        @mouseover="open()"
        static
      >
        <FloatArrow v-if="arrow" class="absolute h-5 w-5 rotate-45 border bg-popover" />
        <slot name="content">
          <div class="relative w-full max-w-xs rounded-md bg-popover p-2 text-sm">
            <p v-html="content"></p>
          </div>
        </slot>
      </HPopoverPanel>
    </Float>
  </HPopover>
</template>

<script setup lang="ts">
  import { Float, type FloatProps, FloatArrow } from "@headlessui-float/vue";
  const props = withDefaults(
    defineProps<{
      openDelay?: number;
      closeDelay?: number;
      placement?: FloatProps["placement"];
      arrow?: FloatProps["arrow"];
      offset?: FloatProps["offset"];
      content?: string;
    }>(),
    {
      openDelay: 50,
      closeDelay: 250,
      placement: "bottom",
      offset: 10,
    }
  );

  const { show, open, close } = useHoverMenu(toValue(props.closeDelay), toValue(props.openDelay));

  const parent = ref(null) as Ref<HTMLElement | null>;
  onClickOutside(parent, () => {
    close();
  });
</script>

<style scoped>
  .popover-enter-active,
  .popover-leave-active {
    transition: all 200ms ease-in-out;
  }

  .popover-enter-from,
  .popover-leave-to {
    opacity: 0;
    transform: scale(0.95);
  }
</style>