Kairo-Jinkendo/frontend/src/components/Modal.jsx

69 lines
1.9 KiB
React
Raw Normal View History

import { useEffect, useId, useRef } from 'react'
/**
* Einfaches Modal (AP1.12b) — Fokus-Falle, Escape schließt.
*
* @param {{ open: boolean, title: string, onClose: () => void, children: import('react').ReactNode, size?: 'md'|'lg' }} props
*/
export function Modal({ open, title, onClose, children, size = 'md' }) {
const titleId = useId()
const dialogRef = useRef(null)
useEffect(() => {
if (!open) return undefined
const previousActive = document.activeElement
dialogRef.current?.focus()
function onKeyDown(event) {
if (event.key === 'Escape') {
event.preventDefault()
onClose()
}
}
document.addEventListener('keydown', onKeyDown)
const prevOverflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
return () => {
document.removeEventListener('keydown', onKeyDown)
document.body.style.overflow = prevOverflow
if (previousActive instanceof HTMLElement) {
previousActive.focus()
}
}
}, [open, onClose])
if (!open) return null
return (
<div className="modal-root" role="presentation" onMouseDown={onClose}>
<div
ref={dialogRef}
className={`modal-dialog modal-dialog--${size}`}
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
tabIndex={-1}
onMouseDown={(event) => event.stopPropagation()}
>
<header className="modal-dialog__header">
<h2 id={titleId} className="modal-dialog__title">
{title}
</h2>
<button
type="button"
className="modal-dialog__close btn btn-secondary btn-sm"
onClick={onClose}
aria-label="Schließen"
>
×
</button>
</header>
<div className="modal-dialog__body">{children}</div>
</div>
</div>
)
}