Skip to content

組合模式 v0.11+

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

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

jsx
<Popover>
  <Float>
    <Popover.Button className="...">
      Options
    </Popover.Button>

    <Popover.Panel className="...">
      ...
    </Popover.Panel>
  </Float>
</Popover>

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

jsx
<Popover>
  <Float composable>
    <Float.Reference>
      <Popover.Button className="...">
        Options
      </Popover.Button>
    </Float.Reference>

    <Float.Content>
      <Popover.Panel className="...">
        ...
      </Popover.Panel>
    </Float.Content>
  </Float>
</Popover>

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

jsx
<Popover>
  <Float composable>
    <div>
      <Float.Reference>
        <Popover.Button className="...">
          Options
        </Popover.Button>
      </Float.Reference>
    </div>

    <div>
      <div>...</div>
      <div>
        <div>
          <Float.Content>
            <Popover.Panel className="...">
              ...
            </Popover.Panel>
          </Float.Content>
        </div>
      </div>
    </div>
  </Float>
</Popover>

Dialog 模式

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

需要注意的是,在 Dialog 中要把 <Float.Content> 設為 as={Fragment},以及裡面的第一個元素一定要是 <Dialog.Panel>

下面是整合 Dialog 的範例:

jsx
import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { Float } from '@headlessui-float/react'

export default function ExampleDialog() {
  const [isOpen, setIsOpen] = useState(false)

  function closeModal() {
    setIsOpen(false)
  }

  function openModal() {
    setIsOpen(true)
  }

  return (
    <Float
      dialog
      placement="bottom-start"
      offset={4}
    >
      <Float.Reference>
        <button
          type="button"
          onClick={openModal}
          className="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"
        >
          Open dialog
        </button>
      </Float.Reference>

      <Transition appear show={isOpen} as={Fragment}>
        <Dialog as="div" className="relative" onClose={closeModal}>
          <Transition.Child
            as={Fragment}
            enter="duration-300 ease-out"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="duration-200 ease-in"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <div className="fixed inset-0 bg-black bg-opacity-25" />
          </Transition.Child>

          <div className="fixed inset-0">
            <div className="flex min-h-full items-center justify-center p-4 text-center">
              <Float.Content
                as={Fragment}
                transitionChild
                enter="duration-300 ease-out"
                enterFrom="opacity-0 scale-50"
                enterTo="opacity-100 scale-100"
                leave="duration-200 ease-in"
                leaveFrom="opacity-100 scale-100"
                leaveTo="opacity-0 scale-50"
                tailwindcssOriginClass
              >
                <Dialog.Panel className="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">
                  <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
                    Dialog Title
                  </Dialog.Title>

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

                  <div className="mt-4">
                    <button
                      type="button"
                      className="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"
                      onClick={closeModal}
                    >
                      Close
                    </button>
                  </div>
                </Dialog.Panel>
              </Float.Content>
            </div>
          </div>
        </Dialog>
      </Transition>
    </Float>
  )
}

Released under the MIT License.