Skip to content

組合模式 v0.11+

原本的 <Float> 設計上為了使用簡單,只能且一定要有參考元素浮動元素,但沒辦法使用在更複雜的元件上使用。啟用組合模式後提供了 <FloatReference><FloatContent> 元件,可以自由更改參考元素浮動元素在 DOM 中的位置。

比如這裡有一個沒有使用組合模式的範例:

html
<Popover>
  <Float>
    <PopoverButton class="...">
      Options
    </PopoverButton>

    <PopoverPanel class="...">
      ...
    </PopoverPanel>
  </Float>
</Popover>

同樣的結果但使用組合模式:

html
<Popover>
  <Float composable>
    <FloatReference>
      <PopoverButton class="...">
        Options
      </PopoverButton>
    </FloatReference>

    <FloatContent>
      <PopoverPanel class="...">
        ...
      </PopoverPanel>
    </FloatContent>
  </Float>
</Popover>

<script setup>
import {
  Float,
  FloatContent,
  FloatReference,
} from '@headlessui-float/vue'
</script>

在組合模式下設計元件上可以更加靈活了:

html
<Popover>
  <Float composable>
    <div>
      <FloatReference>
        <PopoverButton class="...">
          Options
        </PopoverButton>
      </FloatReference>
    </div>

    <div>
      <div>...</div>
      <div>
        <div>
          <FloatContent>
            <PopoverPanel class="...">
              ...
            </PopoverPanel>
          </FloatContent>
        </div>
      </div>
    </div>
  </Float>
</Popover>

Dialog 模式

整合 Dialog 對話框,讓 Dialog 可以定位在參考元素的周圍而設計的模式,會同時開啟組合模式。如果同時使用了 <TransitionRoot> 的時候,還需要在 <FloatContent> 上啟用 transition-child,改用 Headless UI 的 <TransitionChild> 來設定轉場。

需要注意的是,在 Dialog 中要把 <FloatContent> 設為 as="template",以及裡面的第一個元素一定要是 <DialogPanel>

下面是整合 Dialog 的範例:

vue
<template>
  <Float
    dialog
    placement="bottom-start"
    :offset="4"
  >
    <FloatReference>
      <button
        type="button"
        class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-opacity-80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
        @click="openModal"
      >
        Open dialog
      </button>
    </FloatReference>

    <TransitionRoot appear :show="isOpen" as="template">
      <Dialog as="div" class="relative" @close="closeModal">
        <TransitionChild
          as="template"
          enter="duration-300 ease-out"
          enter-from="opacity-0"
          enter-to="opacity-100"
          leave="duration-200 ease-in"
          leave-from="opacity-100"
          leave-to="opacity-0"
        >
          <div class="fixed inset-0 bg-black bg-opacity-25" />
        </TransitionChild>

        <div class="fixed inset-0">
          <div class="flex min-h-full items-center justify-center p-4 text-center">
            <FloatContent
              as="template"
              transition-child
              enter="duration-300 ease-out"
              enter-from="opacity-0 scale-50"
              enter-to="opacity-100 scale-100"
              leave="duration-200 ease-in"
              leave-from="opacity-100 scale-100"
              leave-to="opacity-0 scale-50"
              tailwindcss-origin-class
            >
              <DialogPanel class="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-[transform,opacity] select-none">
                <DialogTitle as="h3" class="text-lg font-medium leading-6 text-gray-900">
                  Dialog Title
                </DialogTitle>

                <div class="mt-2">
                  <p class="text-sm text-gray-500">
                    Some text...
                  </p>
                </div>

                <div class="mt-4">
                  <button
                    type="button"
                    class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
                    @click="closeModal"
                  >
                    Close
                  </button>
                </div>
              </DialogPanel>
            </FloatContent>
          </div>
        </div>
      </Dialog>
    </TransitionRoot>
  </Float>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
import { Float, FloatContent, FloatReference } from '@headlessui-float/vue'

const isOpen = ref(false)

function closeModal() {
  isOpen.value = false
}

function openModal() {
  isOpen.value = true
}
</script>

Released under the MIT License.