[Bug]: Drawer state broken when nesting from another Drawer or DropdownMenu from Kobalte
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
- Click on Open Drawer
- Click on "Open another drawer !"
- 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 !
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
pointerdownevent and closes instantly again with thepointerupevent. You could either solve this with using<Drawer.Trigger>as the element in the dropdown (In kobalte you can use theasprop) or you can setcloseOnOutsidePointerStrategy="pointerdown"so that it doesn't close instantly. You probably also have to setonCloseAutoFocus={e => e.preventDefault()on the KobalteDropdownMenu.Contentto 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.
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 !
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:
- Open Kobalte Dropdown
- Select 'Open Corvu Drawer'
- Drawer will open, but can't be closed by any means.
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.
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..