ycs77 / headlessui-float

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

Listbox portal in Dialog #47

Closed sbc640964 closed 1 year ago

sbc640964 commented 1 year ago

Use Version Use version when bugs appear:

Describe the bug listbox in headlessui's modal does not transfer the focus to the list, nor can it be navigated and it is not possible to click with the mouse.

To Reproduce Create modal with Dialog headlessui. Create Listbox like example

<Listbox>
    <Float
        as="div"
        placement="bottom-start"
        portal
        offset={2}
        adaptiveWidth
    >
        <Listbox.Button className="w-full text-right">
            <div className="border-b pb-1.5 px-1 relative">
                {value ? (
                    <span className="block w-full truncate">{value}</span>
                ) : (
                    <span className="block w-full truncate text-gray-400">Select...</span>
                )}
                <span className="absolute inset-y-0 left-1 flex items-center pr-2 pointer-events-none">
                    <ChevronDownIcon className="w-4 h-4 text-gray-400" aria-hidden="true" />
                </span>
            </div>
        </Listbox.Button>

        <Listbox.Options className="w-full" as="div">
            <div className="h-96 bg-white rounded shadow-lg border">  
                <Listbox.Option value={'1'} as="div">
                    <div className="py-2 px-2">
                        <span className="block w-full truncate">1</span>
                    </div>
                </Listbox.Option>
                <Listbox.Option value={'2'} as="div">
                    <div className="py-2 px-2">
                        <span className="block w-full truncate">1</span>
                    </div>
                </Listbox.Option>
            </div>
        </Listbox.Options>
    </Float>
</Listbox>

click on button. see yue not can navigate between options

ycs77 commented 1 year ago

Hi @sbc640964

Fixed in the v0.11, you can update the @headlessui-float/react.

And this has an example to use it:

import { Fragment, useState } from 'react'
import { Dialog, Listbox, Transition } from '@headlessui/react'
import { Float } from '@headlessui-float/react'
import ChevronDownIcon from '~icons/heroicons/chevron-down-20-solid'

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

  function closeModal() {
    setIsOpen(false)
  }

  function openModal() {
    setIsOpen(true)
  }

  return (
    <>
      <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"
        data-testid="open-dialog-button"
      >
        Open dialog
      </button>

      <Transition appear show={isOpen} as={Fragment}>
        <Dialog as="div" className="relative z-[10001]" 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"
              data-testid="dialog-overlay"
            >
              <Transition.Child
                as={Fragment}
                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"
              >
                <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">
                    Listbox in Dialog
                  </Dialog.Title>

                  <div className="mt-4">
                    <Listbox value={value} onChange={setValue}>
                      <Float
                        as="div"
                        placement="bottom-start"
                        portal
                        offset={2}
                        adaptiveWidth
                      >
                        <Listbox.Button className="w-full text-right">
                          <div className="border-b pb-1.5 px-1 relative">
                            {value ? (
                              <span className="block w-full truncate">{value}</span>
                            ) : (
                              <span className="block w-full truncate text-gray-400">Select...</span>
                            )}
                            <span className="absolute inset-y-0 left-1 flex items-center pr-2 pointer-events-none">
                              <ChevronDownIcon className="w-4 h-4 text-gray-400" aria-hidden="true" />
                            </span>
                          </div>
                        </Listbox.Button>

                        <Listbox.Options className="w-full" as="div">
                          <div className="h-96 bg-white rounded shadow-lg border">
                            <Listbox.Option value="1" as="div" className={({ active }) => active ? 'bg-amber-100 text-amber-700' : ''}>
                              <div className="py-2 px-2">
                                <span className="block w-full truncate">1</span>
                              </div>
                            </Listbox.Option>
                            <Listbox.Option value="2" as="div" className={({ active }) => active ? 'bg-amber-100 text-amber-700' : ''}>
                              <div className="py-2 px-2">
                                <span className="block w-full truncate">2</span>
                              </div>
                            </Listbox.Option>
                          </div>
                        </Listbox.Options>
                      </Float>
                    </Listbox>
                  </div>
                </Dialog.Panel>
              </Transition.Child>
            </div>
          </div>
        </Dialog>
      </Transition>
    </>
  )
}