corvu icon indicating copy to clipboard operation
corvu copied to clipboard

[Bug]: Drawer state broken when nesting from another Drawer or DropdownMenu from Kobalte

Open Vexcited opened this issue 10 months ago • 5 comments

Bug description

In my app, I have a DropdownMenu from Kobalte and when clicking an item from this menu, it opens a Drawer from corvu using open and onOpenChange.

When I do this, it'll automatically close the Drawer. If I log the output of onOpenChange, it shows me false two times.

It does NOT only happens with elements from Kobalte, if I want to open another Drawer from a Drawer, it also does it.

Reproduction Link

https://stackblitz.com/edit/vexcited-corvu-drawer-issue-repro?file=src%2FApp.jsx

Reproduction Steps

  1. Click on Open Drawer
  2. Click on "Open another drawer !"
  3. See the new drawer close itself because of onOpenChange

Expected behavior

I expected the new drawer from not closing itself automatically.

Additional context

It's actually worse with an DropdownMenu item, because it'll close itself but after this I won't be able to interact anymore with the app because the drawer seems like to be still there.

https://github.com/user-attachments/assets/69a80548-1d01-4441-a65d-7e505050db31

The code for this looks like this...

const [isReportDrawerOpened, setReportDrawerOpen] = createSignal(false);

return (
<>
 <Drawer open={isReportDrawerOpened()} onOpenChange={setReportDrawerOpen} breakPoints={[0.75]}>
  {(props) => (
    <>
      <Drawer.Trigger class="w-fit mx-auto rd-lg bg-white/10 px-4 py-3 text-lg font-medium transition-all duration-100 hover:bg-white/20 active:translate-y-0.5">
        Open Drawer
      </Drawer.Trigger>
      <Drawer.Portal>
        <Drawer.Overlay
          class="fixed inset-0 z-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
          style={{
            'background-color': `rgb(0 0 0 / ${
              0.5 * props.openPercentage
            })`,
          }}
        />
        <Drawer.Content class="corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] fixed inset-x-0 bottom-0 z-50 flex h-full max-h-125 flex-col rounded-t-lg border-t-4 border-white/40 bg-black pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit  md:select-none">
          <div class="h-1 w-10 self-center rounded-full bg-white/40" />
          <Drawer.Label class="mt-2 text-center text-xl font-bold">
            I'm a drawer!
          </Drawer.Label>
          <Drawer.Description class="mt-1 text-center">
            Drag down to close me.
          </Drawer.Description>
        </Drawer.Content>
      </Drawer.Portal>
    </>
  )}
 </Drawer>
 <DropdownMenu>
  <DropdownMenu.Trigger class="ml-auto hover:bg-white/8 rounded-full p-1.5 -mr-1.5 transition-colors">
    <DropdownMenu.Icon>
      <MdiDotsVertical class="text-xl" />
    </DropdownMenu.Icon>
  </DropdownMenu.Trigger>
  <DropdownMenu.Portal>
    <DropdownMenu.Content class="min-w-[220px] p-2 bg-[#080808] rounded-xl outline-none border border-white/8 transform-origin-[var(--kb-menu-content-transform-origin)]">
      <DropdownMenu.Item class="cursor-pointer rounded-lg py-1.5 px-4 hover:bg-white/8 text-red/80 hover:text-white"
        onSelect={() => setReportDrawerOpen(true)}
      >
        Report
      </DropdownMenu.Item>
    </DropdownMenu.Content>
  </DropdownMenu.Portal>
</DropdownMenu>
</>
);

https://github.com/user-attachments/assets/1af1fbf6-3a95-4400-843e-9ea1c9a570cb


Also, thanks you so much for your quick patches, you're doing an awesome work and Corvu is such a treasure !

Vexcited avatar Mar 07 '25 13:03 Vexcited

hm this is tricky to solve, it's actually two issues that come to play here:

  • In your stackblitz repro the problem is that the first drawer moves the focus to the trigger after the closing transition finishes, which causes the other drawer to close too because the focus moves outside of it. You could prevent that by doing onOutsideFocus={e => e.preventDefault()} on the first drawer to prevent focusing outside if you're opening a new drawer anyways.
  • With the dropdown, it's probably the same issue and that the drawer opens with a pointerdown event and closes instantly again with the pointerup event. You could either solve this with using <Drawer.Trigger> as the element in the dropdown (In kobalte you can use the as prop) or you can set closeOnOutsidePointerStrategy="pointerdown" so that it doesn't close instantly. You probably also have to set onCloseAutoFocus={e => e.preventDefault() on the Kobalte DropdownMenu.Content to solve the first issue here.

I have to think about what first party solution could be satisfying here, sadly it's not a quick fix so I can't promise anything soon.

GiyoMoon avatar Mar 08 '25 17:03 GiyoMoon

Using what you said...

<Drawer
  trapFocus={false}
  onOutsideFocus={e => e.preventDefault()}
  ...
> ... </Drawer>

Works enough for me ! Not sure if I should keep this issue opened, I'll let you decide what to do !

Thanks for the help at least, and good luck for trying to fix this in a proper way !

Vexcited avatar Mar 10 '25 14:03 Vexcited

I noticed, if open a Drawer from kobalte's DropdownMenu by setting Drawer's open property to `true', it can not be closed afterwards.

This is a reproduction repo:

https://stackblitz.com/~/github.com/dannylin108/parkui-bug-report/tree/corvu

Steps:

  1. Open Kobalte Dropdown
  2. Select 'Open Corvu Drawer'
  3. Drawer will open, but can't be closed by any means.

dannylin108 avatar Mar 19 '25 09:03 dannylin108

hmm yeah, the Kobalte Dropdown and the Drawer don't play well together at all :/ The quickest solution in userland would be to set modal={false} on the Dropdown root and closeOnOutsideFocus={false} on the Drawer root. The Dropdown heavily controls the focus and this messes up the drawer because it receives a focus event outside of it which brings it to close again.

GiyoMoon avatar Mar 21 '25 09:03 GiyoMoon

in my case I put Dropdown inside Drawer context, removed closing animation from Dropdown when it triggers Drawer, and setTimeout(() => drawerContext.setOpen(true), 50).. it works for now..

dannylin108 avatar Mar 21 '25 10:03 dannylin108