fix(editor): portal toolbar modals to body — container queries broke their overlays

The unified-toolbar responsiveness work gave .unified-toolbar
container-type: inline-size, which makes it the containing block for
position:fixed descendants. The three modals rendered inside the toolbar
(library manager, install-libraries, circuit verification) shrank to the
toolbar's 89px box: pinned to the top, header and close button clipped
offscreen. createPortal(document.body) makes them immune to ancestor
containment. The library manager also gets a fixed tall height
(min(86vh, 1000px)) instead of a bouncy 80vh cap.
This commit is contained in:
David Montero Crespo 2026-08-15 08:19:30 +02:00
parent 63a91a08ef
commit c550b29f9e
4 changed files with 24 additions and 7 deletions

View File

@ -9,6 +9,7 @@
* The component is dumb: it just renders the verification result it's
* given. The caller (EditorToolbar) decides when to show it.
*/
import { createPortal } from 'react-dom';
import type { CircuitWarning, VerificationResult } from '../../simulation/verify/circuitVerifier';
interface Props {
@ -24,7 +25,9 @@ const SEVERITY_COPY: Record<CircuitWarning['severity'], { label: string; color:
export const CircuitVerificationModal: React.FC<Props> = ({ result, onCancel, onRunAnyway }) => {
const items: CircuitWarning[] = [...result.errors, ...result.warnings];
return (
// Portaled to <body>: see LibraryManagerModal - the toolbar's
// container-type would otherwise shrink this fixed overlay.
return createPortal(
<div style={styles.overlay} onClick={onCancel}>
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
<h2 style={styles.title}>Circuit verification</h2>
@ -62,7 +65,8 @@ export const CircuitVerificationModal: React.FC<Props> = ({ result, onCancel, on
</button>
</div>
</div>
</div>
</div>,
document.body,
);
};

View File

@ -1,4 +1,5 @@
import React, { useState, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { installLibrary } from '../../services/libraryService';
import { useSimulatorStore } from '../../store/useSimulatorStore';
@ -129,7 +130,10 @@ export const InstallLibrariesModal: React.FC<InstallLibrariesModalProps> = ({
const installedCount = items.filter((i) => i.status === 'done').length;
const allDone = items.length > 0 && pendingCount === 0 && !running;
return (
// Portaled to <body>: the toolbar ancestor has container-type
// (container queries), which turns it into the containing block for
// position:fixed and shrank the overlay to the toolbar's box.
return createPortal(
<div className="ilib-overlay" onClick={running ? undefined : onClose}>
<div className="ilib-modal" onClick={(e) => e.stopPropagation()}>
{/* Header */}
@ -280,6 +284,7 @@ export const InstallLibrariesModal: React.FC<InstallLibrariesModalProps> = ({
)}
</div>
</div>
</div>
</div>,
document.body,
);
};

View File

@ -28,7 +28,10 @@
border-radius: 8px;
width: 680px;
max-width: 95vw;
max-height: 80vh;
/* Fixed tall height (not just a cap): the list is the modal's whole
point, so give it the room instead of jumping sizes per search. */
height: min(86vh, 1000px);
max-height: 92vh;
display: flex;
flex-direction: column;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import {
searchLibraries,
@ -279,7 +280,10 @@ export const LibraryManagerModal: React.FC<LibraryManagerModalProps> = ({ isOpen
if (!isOpen) return null;
const browsing = !searchQuery.trim();
return (
// Portaled to <body>: the toolbar ancestor has container-type
// (container queries), which turns it into the containing block for
// position:fixed and shrank the overlay to the toolbar's box.
return createPortal(
<div className="lib-modal-overlay" onClick={onClose}>
<div className="lib-modal" onClick={(e) => e.stopPropagation()}>
{/* Header — the pro custom-upload button injects into .lib-modal-header */}
@ -459,6 +463,7 @@ export const LibraryManagerModal: React.FC<LibraryManagerModalProps> = ({ isOpen
</div>
</div>
</div>
</div>
</div>,
document.body,
);
};