This commit is contained in:
Vula Builder
2026-06-04 09:17:03 +00:00
parent 00dcf4e09a
commit 41c06a4a8a
+73
View File
@@ -0,0 +1,73 @@
'use client'
import { cn } from "@/lib/utils"
import { X } from 'lucide-react'
import React, { useEffect, useCallback } from "react"
interface ModalProps {
open: boolean
onClose: () => void
title?: string
children: React.ReactNode
className?: string
}
export default function Modal({ open, onClose, title, children, className }: ModalProps) {
const handleEscape = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") onClose()
},
[onClose]
)
useEffect(() => {
if (open) {
document.addEventListener("keydown", handleEscape)
document.body.style.overflow = "hidden"
}
return () => {
document.removeEventListener("keydown", handleEscape)
document.body.style.overflow = ""
}
}, [open, handleEscape])
if (!open) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/50"
onClick={onClose}
aria-hidden="true"
/>
{/* Modal content */}
<div
className={cn(
"relative z-10 w-full max-w-md rounded-lg bg-card p-6 shadow-lg",
className
)}
role="dialog"
aria-modal="true"
aria-label={title || "Modal"}
>
{/* Close button */}
<button
onClick={onClose}
className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-primary"
aria-label="Close modal"
>
<X className="h-4 w-4" />
</button>
{/* Title */}
{title && (
<h2 className="text-lg font-semibold leading-none tracking-tight mb-4">
{title}
</h2>
)}
{/* Content */}
{children}
</div>
</div>
)
}