This commit is contained in:
Vula Builder
2026-07-29 19:04:21 +00:00
parent eb14d75224
commit afe72c1892
+91
View File
@@ -0,0 +1,91 @@
'use client'
import { cn } from "@/lib/utils"
import { X } from 'lucide-react'
import { useEffect, useCallback, useRef } from "react"
interface ModalProps {
isOpen: boolean
onClose: () => void
children: React.ReactNode
className?: string
title?: string
description?: string
}
export default function Modal({ isOpen, onClose, children, className, title, description }: ModalProps) {
const overlayRef = useRef<HTMLDivElement>(null)
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") {
onClose()
}
},
[onClose]
)
useEffect(() => {
if (isOpen) {
document.addEventListener("keydown", handleKeyDown)
document.body.style.overflow = "hidden"
}
return () => {
document.removeEventListener("keydown", handleKeyDown)
document.body.style.overflow = "unset"
}
}, [isOpen, handleKeyDown])
if (!isOpen) return null
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4"
role="dialog"
aria-modal="true"
aria-labelledby={title ? "modal-title" : undefined}
aria-describedby={description ? "modal-description" : undefined}
>
{/* Overlay */}
<div
ref={overlayRef}
className="absolute inset-0 bg-black/50"
onClick={onClose}
aria-hidden="true"
/>
{/* Content */}
<div
className={cn(
"relative z-10 w-full max-w-lg bg-background rounded-lg shadow-lg",
className
)}
>
{/* Close button */}
<button
onClick={onClose}
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
aria-label="Close modal"
>
<X className="h-4 w-4" />
</button>
{/* Header */}
{(title || description) && (
<div className="flex flex-col space-y-1.5 p-6 pb-4">
{title && (
<h2 id="modal-title" className="text-2xl font-semibold leading-none tracking-tight">
{title}
</h2>
)}
{description && (
<p id="modal-description" className="text-sm text-muted-foreground">
{description}
</p>
)}
</div>
)}
{/* Body */}
<div className="p-6 pt-0">{children}</div>
</div>
</div>
)
}