From 4797adeb8a534fc75732d378799918a88a307951 Mon Sep 17 00:00:00 2001 From: Vula Builder Date: Sat, 1 Aug 2026 16:26:35 +0000 Subject: [PATCH] Deploy --- src/components/ui/Modal.tsx | 74 +++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/components/ui/Modal.tsx diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx new file mode 100644 index 0000000..7474f08 --- /dev/null +++ b/src/components/ui/Modal.tsx @@ -0,0 +1,74 @@ +'use client' + +import { useEffect, useRef, useState } from "react" +import { X } from 'lucide-react' +import { cn } from "@/lib/utils" + +interface ModalProps { + open: boolean + onClose: () => void + title?: string + children: React.ReactNode + className?: string +} + +export default function Modal({ open, onClose, title, children, className }: ModalProps) { + const overlayRef = useRef(null) + const [mounted, setMounted] = useState(false) + + useEffect(() => { + if (open) { + setMounted(true) + document.body.style.overflow = "hidden" + } else { + document.body.style.overflow = "" + // delay unmount for exit animation + const timer = setTimeout(() => setMounted(false), 200) + return () => clearTimeout(timer) + } + }, [open]) + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && open) onClose() + } + document.addEventListener("keydown", handleKeyDown) + return () => document.removeEventListener("keydown", handleKeyDown) + }, [open, onClose]) + + if (!mounted) return null + + return ( +
{ if (e.target === overlayRef.current) onClose() }} + role="dialog" + aria-modal="true" + aria-label={title || "Modal"} + > +
+ + {title && ( +

{title}

+ )} + {children} +
+
+ ) +} \ No newline at end of file