diff --git a/src/components/dashboard/ui.tsx b/src/components/dashboard/ui.tsx index dbd151e..5b57735 100644 --- a/src/components/dashboard/ui.tsx +++ b/src/components/dashboard/ui.tsx @@ -14,6 +14,7 @@ import { createContext, useCallback, useContext, useEffect, useId, useRef, useState, type ReactNode, } from "react"; +import { createPortal } from "react-dom"; import { MessageCircle, Ticket, Phone, Mail, BookOpen, Rocket, Shield, ShieldCheck, Lock, CreditCard, User, Bell, Eye, EyeOff, Camera, Upload, Plus, Star, Send, @@ -221,6 +222,11 @@ export function Modal({ open, onClose, title, subtitle, icon, children, footer, children: ReactNode; footer?: ReactNode; size?: "sm" | "md" | "lg"; }) { const titleId = useId(); + // Portal the overlay up to `.dash-root` so its position:fixed anchors to the viewport, + // not to a transformed/overflow panel ancestor (which would clip or offset the modal). + const [host, setHost] = useState(null); + const [mounted, setMounted] = useState(false); + useEffect(() => { setHost(document.querySelector(".dash-root")); setMounted(true); }, []); useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; @@ -228,8 +234,8 @@ export function Modal({ open, onClose, title, subtitle, icon, children, footer, return () => document.removeEventListener("keydown", onKey); }, [open, onClose]); - if (!open) return null; - return ( + if (!open || !mounted) return null; + const overlay = (
e.stopPropagation()}>
@@ -247,6 +253,7 @@ export function Modal({ open, onClose, title, subtitle, icon, children, footer,
); + return host ? createPortal(overlay, host) : overlay; } /* ---------------------------------------------------------- */