From 3cd0b9b18e32f3ea99dafcd24c88b2c4dcd8005b Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Wed, 10 Jun 2026 05:15:00 -0700 Subject: [PATCH] Studio: fix nested dropdown submenus clipped by the menu alignment nudge (#6143) * Studio: fix nested dropdown submenus clipped by the menu alignment nudge The 3px alignment nudge on DropdownMenuContent used translate, which makes the scroll container the containing block for nested position:fixed submenu wrappers, so overflow clipped every submenu (plus menu More and Projects, sidebar chat menus, Recents export). Use margin for the same visual shift without a containing block. * Portal dropdown submenus so ancestor styling can never clip them Rendered inline, the fixed submenu wrapper lives inside the parent menu's scroll container, so any transform there (like the 3px nudge) re-enables overflow clipping. Portaling to body removes the submenu from that subtree entirely, same as DropdownMenuContent. --- .../src/components/ui/dropdown-menu.tsx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/studio/frontend/src/components/ui/dropdown-menu.tsx b/studio/frontend/src/components/ui/dropdown-menu.tsx index c4f742a164..a39651d939 100644 --- a/studio/frontend/src/components/ui/dropdown-menu.tsx +++ b/studio/frontend/src/components/ui/dropdown-menu.tsx @@ -46,7 +46,10 @@ function DropdownMenuContent({ sideOffset={sideOffset} align={align} className={cn( - "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-popover text-popover-foreground min-w-48 rounded-lg p-1 duration-100 z-50 max-h-(--radix-dropdown-menu-content-available-height) w-[calc(var(--radix-dropdown-menu-trigger-width)_+_6px)] data-[align=start]:-translate-x-[3px] data-[align=end]:translate-x-[3px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto data-[state=closed]:overflow-hidden", + // The 3px alignment nudge must be margin, not translate: a transform + // here makes this scroll container the containing block for nested + // position:fixed submenu wrappers, clipping every submenu. + "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-popover text-popover-foreground min-w-48 rounded-lg p-1 duration-100 z-50 max-h-(--radix-dropdown-menu-content-available-height) w-[calc(var(--radix-dropdown-menu-trigger-width)_+_6px)] data-[align=start]:-ml-[3px] data-[align=end]:ml-[3px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto data-[state=closed]:overflow-hidden", className, )} {...props} @@ -241,14 +244,19 @@ function DropdownMenuSubContent({ ...props }: React.ComponentProps) { return ( - + // Portaled like DropdownMenuContent: rendered inline, the fixed popper + // wrapper is a descendant of the parent menu's scroll container, so any + // transform there turns on overflow clipping and hides the submenu. + + + ); }