ux(canvas): zoom controls float on the canvas' bottom-right corner

The canvas header runs out of room when the AI chat panel docks on the
right — the board selector clipped and the zoom cluster was squeezed out
entirely. The −/%/+ cluster now floats on the circuit canvas' bottom-right
corner (always free), with stopPropagation so a click never starts a pan.
This commit is contained in:
David Montero Crespo 2026-08-07 23:53:48 +02:00
parent cd83813a9b
commit f9a9657b5e
2 changed files with 82 additions and 59 deletions

View File

@ -264,6 +264,18 @@
height: 28px; height: 28px;
} }
/* Floating variant: pinned to the canvas' bottom-right corner (the header
got too tight once the AI chat panel docks on the right). Sits above the
canvas layers; dialogs/modals portal to <body> so they stay on top. */
.zoom-controls--floating {
position: absolute;
right: 14px;
bottom: 14px;
z-index: 60;
height: 32px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}
.zoom-btn { .zoom-btn {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -2860,65 +2860,9 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
</div> </div>
<div className="canvas-header-right"> <div className="canvas-header-right">
{/* Zoom controls */} {/* Zoom moved to a floating cluster on the canvas'
<div className="zoom-controls"> bottom-right corner — the header gets too tight when the
<button AI chat panel is docked. */}
className="zoom-btn"
onClick={() =>
handleWheel({
deltaY: 100,
clientX: 0,
clientY: 0,
preventDefault: () => {},
} as any)
}
title={t('editor.canvas.zoomOut')}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
>
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
<button
className="zoom-level"
onClick={handleResetView}
title={t('editor.canvas.resetView')}
>
{Math.round(zoom * 100)}%
</button>
<button
className="zoom-btn"
onClick={() =>
handleWheel({
deltaY: -100,
clientX: 0,
clientY: 0,
preventDefault: () => {},
} as any)
}
title={t('editor.canvas.zoomIn')}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
</div>
{/* Component count */} {/* Component count */}
<span <span
@ -3224,6 +3168,73 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
} }
return null; return null;
})()} })()}
{/* Floating zoom controls — pinned to the canvas' bottom-right
corner (they lived in the canvas header, which gets too tight
when the AI chat panel is docked; the corner is always free).
stopPropagation so a click never starts a canvas pan/selection. */}
<div
className="zoom-controls zoom-controls--floating"
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
<button
className="zoom-btn"
onClick={() =>
handleWheel({
deltaY: 100,
clientX: 0,
clientY: 0,
preventDefault: () => {},
} as any)
}
title={t('editor.canvas.zoomOut')}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
>
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
<button
className="zoom-level"
onClick={handleResetView}
title={t('editor.canvas.resetView')}
>
{Math.round(zoom * 100)}%
</button>
<button
className="zoom-btn"
onClick={() =>
handleWheel({
deltaY: -100,
clientX: 0,
clientY: 0,
preventDefault: () => {},
} as any)
}
title={t('editor.canvas.zoomIn')}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
</div>
</div> </div>
</div> </div>