From c03b9509c83e437b72cfd747c73e186253fa0946 Mon Sep 17 00:00:00 2001 From: Jhon Date: Sun, 13 Jul 2025 11:36:10 -0300 Subject: [PATCH] fix(ui): resolve dialog infinite render loops with tall content - Force modal=false on all dialogs to prevent Radix UI render loops - Add React context to share dialog state between components - Implement custom overlay with proper click-to-close behavior - Add body scroll lock tied to dialog open state (prevents stuck scroll) - Create scrollable content wrapper with overscroll-contain - Remove complex wheel event handlers that caused tab hangs - Simplify dialog architecture for better maintainability --- apps/dokploy/components/ui/dialog.tsx | 105 ++++++++++++++++++++------ 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/apps/dokploy/components/ui/dialog.tsx b/apps/dokploy/components/ui/dialog.tsx index 37e8f685f..8ed1520ed 100644 --- a/apps/dokploy/components/ui/dialog.tsx +++ b/apps/dokploy/components/ui/dialog.tsx @@ -1,10 +1,19 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; import * as React from "react"; - import { cn } from "@/lib/utils"; -const Dialog = DialogPrimitive.Root; +const DialogContext = React.createContext<{ + onOpenChange?: (open: boolean) => void; + open?: boolean; +}>({}); + +const Dialog = ({ onOpenChange, open, ...props }: React.ComponentPropsWithoutRef) => ( + + + +); +Dialog.displayName = DialogPrimitive.Root.displayName; const DialogTrigger = DialogPrimitive.Trigger; @@ -30,25 +39,77 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - -
{children}
- - - Close - -
-
-)); +>(({ className, children, ...props }, ref) => { + const contentRef = React.useRef(null); + const { onOpenChange, open } = React.useContext(DialogContext); + + React.useEffect(() => { + if (!open) return; + + const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; + const body = document.body; + const originalPaddingRight = body.style.paddingRight; + const originalOverflow = body.style.overflow; + + body.style.overflow = 'hidden'; + if (scrollbarWidth > 0) { + body.style.paddingRight = `${scrollbarWidth}px`; + } + + return () => { + body.style.overflow = originalOverflow; + body.style.paddingRight = originalPaddingRight; + }; + }, [open]); + + const handleOverlayClick = React.useCallback((e: React.MouseEvent) => { + if (e.target === e.currentTarget && onOpenChange) { + onOpenChange(false); + } + }, [onOpenChange]); + + const hasPaddingOverride = className?.includes("p-0"); + + return ( + +
+ { + const originalEvent = e.detail.originalEvent; + const target = originalEvent.target as HTMLElement; + if (target.closest('[data-radix-popper-content-wrapper]')) { + e.preventDefault(); + } + }} + {...props} + > +
+ {children} +
+ + + + Close + +
+ + ); +}); DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({ @@ -117,4 +178,4 @@ export { DialogFooter, DialogTitle, DialogDescription, -}; +}; \ No newline at end of file