Files
dashboards/apps/super-admin/src/components/Modal.tsx
T

57 lines
1.6 KiB
TypeScript

import { useEffect } from 'react'
import { createPortal } from 'react-dom'
import { X } from 'lucide-react'
import styles from './Modal.module.css'
interface ModalProps {
open: boolean
title: string
children: React.ReactNode
onClose: () => void
wide?: boolean
/** Extra-wide dialog for dense forms (e.g. invoice editor). */
xl?: boolean
}
export function Modal({ open, title, children, onClose, wide, xl }: ModalProps) {
useEffect(() => {
if (!open) return
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
const prevOverflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
document.addEventListener('keydown', onKey)
return () => {
document.body.style.overflow = prevOverflow
document.removeEventListener('keydown', onKey)
}
}, [open, onClose])
if (!open) return null
const sizeClass = xl ? styles.modalXl : wide ? styles.modalWide : ''
return createPortal(
<div
className={styles.overlay}
role="dialog"
aria-modal="true"
onMouseDown={(e) => {
if (e.target === e.currentTarget) onClose()
}}
>
<div className={`${styles.modal} ${sizeClass}`} onMouseDown={(e) => e.stopPropagation()}>
<div className={styles.header}>
<h3 className={styles.title}>{title}</h3>
<button type="button" className={styles.closeBtn} onClick={onClose} aria-label="Close">
<X size={18} />
</button>
</div>
<div className={styles.body}>{children}</div>
</div>
</div>,
document.body,
)
}