Files
vula-3a425ba6/src/components/ui/Modal.tsx
T
Vula Builder 781e844abf Deploy
2026-07-28 09:03:32 +00:00

74 lines
2.0 KiB
TypeScript

'use client'
import { useEffect, useCallback } from "react"
import { X } from 'lucide-react'
import { cn } from "@/lib/utils"
interface ModalProps {
isOpen: boolean
onClose: () => void
title?: string
children: React.ReactNode
className?: string
}
export default function Modal({ isOpen, onClose, title, children, className }: ModalProps) {
const handleEscape = useCallback((e: KeyboardEvent) => {
if (e.key === "Escape") onClose()
}, [onClose])
useEffect(() => {
if (isOpen) {
document.addEventListener("keydown", handleEscape)
document.body.style.overflow = "hidden"
}
return () => {
document.removeEventListener("keydown", handleEscape)
document.body.style.overflow = "unset"
}
}, [isOpen, handleEscape])
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm"
onClick={onClose}
aria-hidden="true"
/>
<div
className={cn(
"relative bg-background rounded-lg shadow-lg max-w-md w-full mx-4 p-6 z-10",
className
)}
role="dialog"
aria-modal="true"
aria-label={title || "Dialog"}
>
{title && (
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
<button
onClick={onClose}
className="rounded-md p-1 hover:bg-accent focus:outline-none focus:ring-2 focus:ring-primary"
aria-label="Close"
>
<X className="h-5 w-5" />
</button>
</div>
)}
{!title && (
<button
onClick={onClose}
className="absolute top-4 right-4 rounded-md p-1 hover:bg-accent focus:outline-none focus:ring-2 focus:ring-primary"
aria-label="Close"
>
<X className="h-5 w-5" />
</button>
)}
{children}
</div>
</div>
)
}