feat: portal popover menus

Render fixed-position popover menus through document.body so transformed layout containers cannot offset or clip the menu.
This commit is contained in:
Matthew Meszaros
2026-06-01 05:51:05 +02:00
parent 8b28b86b7f
commit 6483f34dfb
+13 -2
View File
@@ -41,6 +41,7 @@ import React, {
useRef,
useState,
} from "react";
import { createPortal } from "react-dom";
import { AnimatePresence, motion } from "framer-motion";
import { cn } from "@/lib/utils";
@@ -229,7 +230,16 @@ export function PopoverMenuContent({
// ones settle up 4px.
const enterY = side === "bottom" ? -4 : 4;
return (
if (typeof document === "undefined") return null;
// Render through a portal to <body>. The menu is `position: fixed` with
// viewport coordinates, but a transformed ancestor (e.g. the sidebar
// <aside>, which uses translate-x for its drawer slide — even
// `md:translate-x-0` on desktop) becomes the containing block for fixed
// descendants, so the menu would otherwise anchor to the sidebar instead
// of the viewport and render trapped inside it. The portal lifts it out of
// any transformed/overflow-clipped ancestor.
return createPortal(
<AnimatePresence>
{open && (
<motion.div
@@ -269,7 +279,8 @@ export function PopoverMenuContent({
{children}
</motion.div>
)}
</AnimatePresence>
</AnimatePresence>,
document.body,
);
}