From 52ace41ac4bc86be02a88dd6bae2ba3ae835a67f Mon Sep 17 00:00:00 2001 From: nessi Date: Fri, 6 Feb 2026 12:43:24 +0100 Subject: [PATCH] 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. --- frontend/src/components/AdminPanel.jsx | 135 +++++++++++++----------- frontend/src/components/ModalPortal.jsx | 40 +++++++ frontend/src/styles/styles.js | 27 +++-- 3 files changed, 127 insertions(+), 75 deletions(-) create mode 100644 frontend/src/components/ModalPortal.jsx diff --git a/frontend/src/components/AdminPanel.jsx b/frontend/src/components/AdminPanel.jsx index 7b53847..12ef778 100644 --- a/frontend/src/components/AdminPanel.jsx +++ b/frontend/src/components/AdminPanel.jsx @@ -2,7 +2,19 @@ import React, { useEffect, useState } from "react"; import { api } from "../api/client"; import { styles } from "../styles/styles"; 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() { const [users, setUsers] = useState([]); @@ -112,71 +124,74 @@ export default function AdminPanel() { ))} - {open && ( -
-
e.stopPropagation()}> -
-
- Neuen User anlegen -
- -
- -
- setDisplayName(e.target.value)} - placeholder="Name (z.B. Sascha)" - style={styles.input} - autoFocus - /> - - setEmail(e.target.value)} - placeholder="Email" - style={styles.input} - /> - - setPassword(e.target.value)} - placeholder="Initial Passwort" - type="password" - style={styles.input} - /> - - - - {msg &&
{msg}
} - -
- -
-
- Tipp: Name wird in TopBar & Siegeranzeige genutzt. +
+ setDisplayName(e.target.value)} + placeholder="Name (z.B. Sascha)" + style={styles.input} + autoFocus + /> + + setEmail(e.target.value)} + placeholder="Email" + style={styles.input} + /> + + setPassword(e.target.value)} + placeholder="Initial Passwort" + type="password" + style={styles.input} + /> + + + + {msg &&
{msg}
} + +
+ + +
+ +
+ Tipp: Name wird in TopBar & Siegeranzeige genutzt. +
-
-
- )} +
, + document.body + ) + } ); } diff --git a/frontend/src/components/ModalPortal.jsx b/frontend/src/components/ModalPortal.jsx new file mode 100644 index 0000000..8c02f08 --- /dev/null +++ b/frontend/src/components/ModalPortal.jsx @@ -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( +
{ + // Klick außerhalb schließt + if (e.target === e.currentTarget) onClose?.(); + }} + > +
e.stopPropagation()}> + {children} +
+
, + document.body + ); +} diff --git a/frontend/src/styles/styles.js b/frontend/src/styles/styles.js index d6dff21..3573531 100644 --- a/frontend/src/styles/styles.js +++ b/frontend/src/styles/styles.js @@ -188,32 +188,29 @@ export const styles = { // Modal modalOverlay: { position: "fixed", - inset: 0, - background: "rgba(0,0,0,0.78)", // stärker abdunkeln - backdropFilter: "blur(6px)", // Hintergrund weich (macht viel aus) + top: 0, + left: 0, + right: 0, + bottom: 0, + width: "100vw", + height: "100vh", display: "flex", alignItems: "center", justifyContent: "center", padding: 16, - zIndex: 9999, - animation: "fadeIn 160ms ease-out", - overflowY: "auto", // falls Viewport zu klein + zIndex: 2147483647, // wirklich ganz oben + background: "rgba(0,0,0,0.72)", + overflowY: "auto", }, modalCard: { - width: "100%", - maxWidth: 560, + width: "min(560px, 100%)", borderRadius: 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)", padding: 14, - backdropFilter: "blur(8px)", - animation: "popIn 160ms ease-out", - color: stylesTokens.textMain, - - // neu: damit es nie “kaputt” aussieht - maxHeight: "calc(100dvh - 32px)", + maxHeight: "calc(100vh - 32px)", overflow: "auto", }, modalHeader: {