fix(dialoge): Dialoge an den body haengen, damit sie nicht in Karten haengen bleiben

Die Einblendanimationen laufen mit animation-fill-mode: both. Danach behaelt
das Element den Wert des letzten Keyframes, bei transform also eine
Einheitsmatrix statt "none". Das genuegt, um einen Bezugsrahmen fuer
position: fixed zu erzeugen: Ein Dialog unterhalb einer solchen Karte hat
sein Overlay nur noch ueber der Karte, deren overflow: hidden schneidet ihn
ab. Sichtbar beim Bearbeiten eines Budgettopfs in der Projektansicht - das
Fenster klebte an der Detailkarte und die Schaltflaechen waren abgeschnitten.

Modal und ConfirmDialog rendern jetzt per createPortal in document.body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-09-18 11:21:00 +02:00
co-authored by Claude Opus 5
parent ef7171496f
commit 133fca9ef6
+15 -2
View File
@@ -1,6 +1,19 @@
import React, { useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
import { Icon } from './Icon';
/**
* Dialoge haengen immer direkt am <body>.
*
* Grund: Die Einblendanimationen laufen mit "animation-fill-mode: both". Danach
* behaelt das Element den Wert des letzten Keyframes - bei transform also eine
* Einheitsmatrix statt "none". Das reicht, um einen Bezugsrahmen fuer
* position: fixed zu erzeugen. Steht ein Dialog im Baum unter so einer Karte,
* misst sein Overlay nur noch die Karte und deren overflow: hidden schneidet
* ihn ab (z. B. Budgettopf bearbeiten in der Projektansicht).
*/
const toBody = (node: React.ReactElement) => (typeof document === 'undefined' ? node : createPortal(node, document.body));
interface ModalProps {
title: string;
subtitle?: React.ReactNode;
@@ -54,7 +67,7 @@ function useDismiss(onClose: () => void) {
*/
export const Modal: React.FC<ModalProps> = ({ title, subtitle, onClose, children, footer, size = 'md' }) => {
const dismiss = useDismiss(onClose);
return (
return toBody(
<div
{...dismiss}
className="animate-fade fixed inset-0 z-[100] flex items-end justify-center bg-ink-950/50 backdrop-blur-sm sm:items-center sm:p-4"
@@ -104,7 +117,7 @@ export const ConfirmDialog: React.FC<ConfirmProps> = ({
onCancel,
}) => {
const dismiss = useDismiss(onCancel);
return (
return toBody(
<div {...dismiss} className="animate-fade fixed inset-0 z-[130] flex items-center justify-center bg-ink-950/50 p-4 backdrop-blur-sm">
<div className="animate-pop w-full max-w-sm rounded-2xl border border-ink-200 bg-surface p-7 text-center shadow-lift">
<div