69 lines
1.9 KiB
React
69 lines
1.9 KiB
React
|
|
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>
|
|||
|
|
)
|
|||
|
|
}
|