tailwindlabs / headlessui

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
https://headlessui.com
MIT License
25.02k stars 1.03k forks source link

React Popover v2: render in a portal breaks use case #3271

Closed prtmwrkr closed 3 weeks ago

prtmwrkr commented 3 weeks ago

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

v2.0.4

What browser are you using?

Chrome

Reproduction URL

https://codesandbox.io/p/devbox/wonderful-bassi-yvq9lk?file=%2Fsrc%2FApp.jsx%3A19%2C1-26%2C11

Describe your issue

The popover is contained in a box that becomes visible when the row is hovered. The user can click the popover, but the moment he moves the mouse on the popover itself, this gets removed. This interaction worked well with previous version (1.7.4), but it doesn't with version two. Problem seems due to the fact that now the popover render in a portal that is no longer colocated in the dom with the actual Popover component. Do you know a way I can make this working again?

thecrypticace commented 3 weeks ago

That reproduction doesn't seem to exist. I get a not found error when trying to access it. Can you update it?

prtmwrkr commented 3 weeks ago

That reproduction doesn't seem to exist. I get a not found error when trying to access it. Can you update it?

Try this link

For some reason the template that i forked (the one from the github issue template) doesn't work... but at least you should get an idea from the code, that is very simple, and that I copy below:

import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'

const MyPopover = () => {
  return (
    <Popover className="relative">
      <PopoverButton>Solutions</PopoverButton>
      <PopoverPanel anchor="bottom" className="flex flex-col">
        <a href="/analytics">Analytics</a>
        <a href="/engagement">Engagement</a>
        <a href="/security">Security</a>
        <a href="/integrations">Integrations</a>
      </PopoverPanel>
    </Popover>
  )
}

function App() {
  return (
    <div className="group/row flex items-center">
      <div>1</div>
      <div>2</div>
      <div>...</div>
      <div className="hidden group-hover/row:flex">
        <MyPopover />
      </div>
    </div>
  )
}
thecrypticace commented 3 weeks ago

Ah yeah so a PopoverPanel is not rendered in a portal unless you:

So in your case you can stop it from portal-ing by removing the anchor prop:

import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'

const MyPopover = () => {
  return (
    <Popover className="relative">
      <PopoverButton>Solutions</PopoverButton>
-      <PopoverPanel anchor="bottom" className="flex flex-col">
+      <PopoverPanel className="flex flex-col">
         <a href="/analytics">Analytics</a>
         <a href="/engagement">Engagement</a>
         <a href="/security">Security</a>
         <a href="/integrations">Integrations</a>
      </PopoverPanel>
    </Popover>
  )
}

function App() {
  return (
    <div className="group/row flex items-center">
      <div>1</div>
      <div>2</div>
      <div>...</div>
      <div className="hidden group-hover/row:flex">
        <MyPopover />
      </div>
    </div>
  )
}

Our anchor prop requires things to be portaled because there's no guarantee that the anchoring would (or even could) work as expected otherwise.

prtmwrkr commented 3 weeks ago

@thecrypticace Thank you for the quick reply - I had to postpone for now the update, but will have a look soonish.

ThenTech commented 2 weeks ago

@thecrypticace This issue is not solved yet.

I have the same problem in v2, but it's not caused by the portalling. Instead, it seems that in v2 the Popover will auto-close if the PopoverButton is not visible any more.

In @prtmwrkr's example this happened because it only becomes visible on hover, but then when moving the mouse inside the PopoverPanel, the row is not hovered any more, causing the button to be hidden, and the Popover to close itself.

I have the same problem as shown here, where in v1, I had a Transition that would hide the button offscreen when the Popover is open and animate it back in once closed. But now in v2, as soon as the animation finishes to hide it, the Popover closes itself.

So a workaround is to make sure the PopoverButton is always visible on screen. This was not a requirement in v1, and animating or hiding as @prtmwrkr did, worked as expected.