All checks were successful
Deploy Development / deploy (push) Successful in 53s
Test Suite / pytest-backend (push) Successful in 1m47s
Test Suite / lint-backend (push) Successful in 3s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 12s
Ersetzt Inline-Formulare durch Dialoge; Scope-Breadcrumb aktualisiert sich nach Profil-Speichern. Co-authored-by: Cursor <cursoragent@cursor.com>
69 lines
1.9 KiB
JavaScript
69 lines
1.9 KiB
JavaScript
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>
|
||
)
|
||
}
|