fix(dashboard): portal NewAgentDialog so it escapes the AgentsView stacking context
On mobile the dialog was rendering with its top hidden behind the in-page \"Agents\" header. The dialog wasn't actually positioned wrong — its overlay is `position: fixed; inset: 0`. The problem was that NewAgentDialog rendered as a child of `.agents-view` (which has `overflow: hidden` and `position: relative`), and an ancestor stacking context combined with the relative+overflow parent prevented the fixed overlay from escaping above the page header in the painter's order. Render the dialog through `createPortal(..., document.body)` so the overlay attaches at the document root, escaping every parent stacking context. This is the standard React modal pattern. All 9639 dashboard tests still pass — React Testing Library queries traverse portals transparently. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import "./NewAgentDialog.css";
|
import "./NewAgentDialog.css";
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
import type { Agent, AgentCapability, ModelInfo, AgentGenerationSpec, PluginRuntimeInfo } from "../api";
|
import type { Agent, AgentCapability, ModelInfo, AgentGenerationSpec, PluginRuntimeInfo } from "../api";
|
||||||
import { createAgent, fetchAgents, fetchModels, updateGlobalSettings } from "../api";
|
import { createAgent, fetchAgents, fetchModels, updateGlobalSettings } from "../api";
|
||||||
import * as apiModule from "../api";
|
import * as apiModule from "../api";
|
||||||
@@ -358,7 +359,13 @@ export function NewAgentDialog({ isOpen, onClose, onCreated, projectId }: NewAge
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
// Render through a portal to document.body so the overlay escapes any
|
||||||
|
// ancestor stacking context / `overflow: hidden`. Without this, `position:
|
||||||
|
// fixed` on the overlay was being trapped under .agents-view, so the
|
||||||
|
// dialog rendered with its top hidden behind the in-page Agents header on
|
||||||
|
// mobile (the header isn't taller than the dialog top — it's just stacked
|
||||||
|
// above it because the dialog couldn't escape its container).
|
||||||
|
return createPortal(
|
||||||
<div className="agent-dialog-overlay" onClick={(e) => { if (e.target === e.currentTarget) handleClose(); }}>
|
<div className="agent-dialog-overlay" onClick={(e) => { if (e.target === e.currentTarget) handleClose(); }}>
|
||||||
<div className="agent-dialog" role="dialog" aria-modal="true" aria-label="Create new agent">
|
<div className="agent-dialog" role="dialog" aria-modal="true" aria-label="Create new agent">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -778,6 +785,7 @@ export function NewAgentDialog({ isOpen, onClose, onCreated, projectId }: NewAge
|
|||||||
onGenerated={handleGenerated}
|
onGenerated={handleGenerated}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>,
|
||||||
|
document.body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user