Refactor modal logic and implement ModalPortal component

Moved modal rendering logic to a new `ModalPortal` component to improve reusability and separation of concerns. Adjusted styles for better UI consistency, including improved backdrop and modal behavior. Enhanced accessibility by handling escape key events and blocking background scrolling when the modal is open.
This commit is contained in:
2026-02-06 12:43:24 +01:00
parent 556a7a5d81
commit 52ace41ac4
3 changed files with 127 additions and 75 deletions

View File

@@ -2,7 +2,19 @@ import React, { useEffect, useState } from "react";
import { api } from "../api/client"; import { api } from "../api/client";
import { styles } from "../styles/styles"; import { styles } from "../styles/styles";
import { stylesTokens } from "../styles/theme"; import { stylesTokens } from "../styles/theme";
import { createPortal } from "react-dom";
useEffect(() => {
if (!open) return;
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = prev;
};
}, [open]);
export default function AdminPanel() { export default function AdminPanel() {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
@@ -112,7 +124,8 @@ export default function AdminPanel() {
))} ))}
</div> </div>
{open && ( {open &&
createPortal(
<div style={styles.modalOverlay} onMouseDown={closeModal}> <div style={styles.modalOverlay} onMouseDown={closeModal}>
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}> <div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
<div style={styles.modalHeader}> <div style={styles.modalHeader}>
@@ -175,8 +188,10 @@ export default function AdminPanel() {
</div> </div>
</div> </div>
</div> </div>
</div> </div>,
)} document.body
)
}
</div> </div>
); );
} }

View File

@@ -0,0 +1,40 @@
import React, { useEffect } from "react";
import { createPortal } from "react-dom";
import { styles } from "../styles/styles";
export default function ModalPortal({ open, onClose, children }) {
useEffect(() => {
if (!open) return;
const onKeyDown = (e) => {
if (e.key === "Escape") onClose?.();
};
// Scroll der Seite sperren
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
window.addEventListener("keydown", onKeyDown);
return () => {
window.removeEventListener("keydown", onKeyDown);
document.body.style.overflow = prev;
};
}, [open, onClose]);
if (!open) return null;
return createPortal(
<div
style={styles.modalOverlay}
onMouseDown={(e) => {
// Klick außerhalb schließt
if (e.target === e.currentTarget) onClose?.();
}}
>
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}

View File

@@ -188,32 +188,29 @@ export const styles = {
// Modal // Modal
modalOverlay: { modalOverlay: {
position: "fixed", position: "fixed",
inset: 0, top: 0,
background: "rgba(0,0,0,0.78)", // stärker abdunkeln left: 0,
backdropFilter: "blur(6px)", // Hintergrund weich (macht viel aus) right: 0,
bottom: 0,
width: "100vw",
height: "100vh",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
padding: 16, padding: 16,
zIndex: 9999, zIndex: 2147483647, // wirklich ganz oben
animation: "fadeIn 160ms ease-out", background: "rgba(0,0,0,0.72)",
overflowY: "auto", // falls Viewport zu klein overflowY: "auto",
}, },
modalCard: { modalCard: {
width: "100%", width: "min(560px, 100%)",
maxWidth: 560,
borderRadius: 18, borderRadius: 18,
border: `1px solid rgba(233,216,166,0.18)`, border: `1px solid rgba(233,216,166,0.18)`,
background: "linear-gradient(180deg, rgba(20,20,24,0.95), rgba(12,12,14,0.92))", background: "rgba(12,12,14,0.96)",
boxShadow: "0 18px 55px rgba(0,0,0,0.70)", boxShadow: "0 18px 55px rgba(0,0,0,0.70)",
padding: 14, padding: 14,
backdropFilter: "blur(8px)", maxHeight: "calc(100vh - 32px)",
animation: "popIn 160ms ease-out",
color: stylesTokens.textMain,
// neu: damit es nie “kaputt” aussieht
maxHeight: "calc(100dvh - 32px)",
overflow: "auto", overflow: "auto",
}, },
modalHeader: { modalHeader: {