ycs77 / headlessui-float

Easily use Headless UI with Floating UI to position floating elements.
https://headlessui-float.vercel.app
MIT License
349 stars 13 forks source link

add Arrow using an HTML element in Vue #6

Closed mtzrmzia closed 2 years ago

mtzrmzia commented 2 years ago

When including the arrow component in the HTML element, the arrow doesn't render correctly, this issue can be easily reproduced in the demo page.

<template>
  <Float
    :show="isShowing"
    arrow
    :offset="15"
    placement="bottom"
    ...
  >
    <button
      type="button"
      class="flex justify-center items-center px-5 py-2 bg-indigo-50 text-indigo-500 text-sm rounded-md"
      @mouseenter="isShowing = true"
      @mouseleave="isShowing = false"
    >
      Options
    </button>
    <div class="relative rounded-lg shadow-lg ring-1 w-28 ring-black bg-white ring-opacity-5">
      <FloatArrow class="absolute bg-white w-5 h-5 rotate-45 border border-gray-200"  />
        <button type="button">
          ...
        </button>
     ...
    </div>
  </Float>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { Float, FloatArrow } from "headlessui-float-vue";

export default defineComponent({
  components: { Float, FloatArrow },
  data() {
    return {
      isShowing: false,
    };
  }
});
</script>

167495558-f37d356f-1fb7-4e88-a1b8-97b9a0b7118b

ycs77 commented 2 years ago

Hi~ This must be move <FloatArrow> to bottom of items, so we can add the tailwind class relative bg-white rounded-md overflow-hidden to <ul>:

<div
  class="w-48 bg-white border border-gray-200 rounded-md shadow-lg"
  @mouseenter="open"
  @mouseleave="close"
>
  <FloatArrow class="absolute bg-white w-5 h-5 rotate-45 border border-gray-200" />
  <!-- warp the items and set relative to up the <FloatArrow> element -->
  <ul class="relative bg-white rounded-md overflow-hidden">
    <li>...</li>
  </ul>
  <!-- warp end -->
</div>

and setting the <Float>:

<Float
  :show="show"
  placement="bottom-start"
  :offset="12"
  arrow
>

then will see a simple arrow menu.

But now can't move the cursor to the menu, because has some gap in the button and dropdown, so now it is necessary to add a setTimeout() to delay closing the menu:

<script setup>
const show = ref(false)
const timer = ref(null)

const open = () => {
  if (timer.value !== null) {
    clearTimeout(timer.value)
    timer.value = null
  }
  show.value = true
}

const close = () => {
  show.value = false
}

const delayClose = () => {
  timer.value = setTimeout(() => {
    show.value = false
  }, 150)
}
</script>

Full example:

<template>
  <div>
    <Float
      :show="show"
      placement="bottom-start"
      :offset="12"
      arrow
    >
      <button
        type="button"
        class="flex justify-center items-center px-5 py-2 bg-indigo-50 hover:bg-indigo-100 text-indigo-500 text-sm rounded-md"
        @mouseenter="open"
        @mouseleave="delayClose"
      >
        Options
      </button>

      <div
        class="w-48 bg-white border border-gray-200 rounded-md shadow-lg"
        @mouseenter="open"
        @mouseleave="delayClose"
      >
        <FloatArrow class="absolute bg-white w-5 h-5 rotate-45 border border-gray-200" />

        <!-- warp the items and set relative to up the <FloatArrow> element -->
        <ul class="relative bg-white rounded-md overflow-hidden">
          <li>
            <button
              type="button"
              class="block w-full px-4 py-2 hover:bg-indigo-500 hover:text-white text-left text-sm"
              @click="close"
            >
              Profile
            </button>
          </li>
          <li>
            <button
              type="button"
              class="block w-full px-4 py-2 hover:bg-indigo-500 hover:text-white text-left text-sm"
              @click="close"
            >
              Account settings
            </button>
          </li>
        </ul>
        <!-- warp end -->
      </div>
    </Float>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { Float, FloatArrow } from 'headlessui-float-vue'

const show = ref(false)
const timer = ref(null)

const open = () => {
  if (timer.value !== null) {
    clearTimeout(timer.value)
    timer.value = null
  }
  show.value = true
}

const close = () => {
  show.value = false
}

const delayClose = () => {
  timer.value = setTimeout(() => {
    show.value = false
  }, 150)
}
</script>