fix(canvas): collapse the Camera/Mic buttons to icon-only when the toolbar is cramped

With the AI chat docked, the canvas header ran out of room and the Camera
and Microphone buttons overlapped the icons before them (visible as
"…Camera" colliding on a XIAO-S3-Sense board). Serial and Scope already
collapse to icon-only via the `@container unified-toolbar` query
(font-size: 0 hides the label, the fixed-size SVG stays), but Camera/Mic
carried their layout in INLINE styles, and an inline font-size beats the
class rule so the query could never reach them.

Move the two toggles' static layout into a shared `.canvas-cam-mic-btn`
class (only state-driven styles — background, border, colour, the pulse
animation — stay inline) and add that class to the same collapse rules as
Serial, in both the container query and the mobile media query. Also drop
the header button gap from 8px to 4px once the toolbar is narrow so all
four (Serial/Camera/Mic/Scope) pack in without overlapping.

Side effect: the toggles now share Serial's 28px height, so the toolbar row
lines up (they were a touch shorter before).
This commit is contained in:
David Montero Crespo 2026-08-11 20:01:56 +02:00
parent b8c2b56df2
commit fb8086c205
3 changed files with 48 additions and 16 deletions

View File

@ -111,11 +111,14 @@ export const CameraToggle: React.FC<CameraToggleProps> = ({ boardId, maxFrameByt
// starving without frames. Never while the sim is stopped — the
// button is not asking for anything then. Solid border once
// streaming or asking.
className={
// Static layout is in .canvas-cam-mic-btn so the toolbar @container
// query can collapse the label to icon-only (an inline font-size would
// beat it). Only state-driven styles stay inline below.
className={`canvas-cam-mic-btn${
running && boardId && status !== 'streaming' && status !== 'requesting'
? 'velxio-btn-ants'
: undefined
}
? ' velxio-btn-ants'
: ''
}`}
style={{
// backgroundColor, NOT the background shorthand: inline shorthand
// would wipe the class's background-image and kill the ants.
@ -123,13 +126,7 @@ export const CameraToggle: React.FC<CameraToggleProps> = ({ boardId, maxFrameByt
border: `1px solid ${
isOn ? '#3fb950' : status === 'requesting' ? '#555' : 'transparent'
}`,
borderRadius: 4,
padding: '4px 10px',
display: 'flex',
alignItems: 'center',
gap: 6,
color,
fontSize: 13,
cursor: boardId ? 'pointer' : 'not-allowed',
// Inline only while requesting. 'none' here would OVERRIDE the
// .velxio-btn-ants class animation (inline beats class), which is

View File

@ -62,18 +62,16 @@ export const MicrophoneToggle: React.FC<MicrophoneToggleProps> = ({ boardId }) =
onClick={handleClick}
disabled={!boardId || status === 'requesting'}
title={tooltip}
// Static layout is in .canvas-cam-mic-btn so the toolbar @container
// query can collapse the label to icon-only (an inline font-size would
// beat it). Only state-driven styles stay inline.
className="canvas-cam-mic-btn"
style={{
backgroundColor: isOn ? 'rgba(63,185,80,0.15)' : 'transparent',
border: `1px solid ${
isOn ? '#3fb950' : status === 'requesting' ? '#555' : 'transparent'
}`,
borderRadius: 4,
padding: '4px 10px',
display: 'flex',
alignItems: 'center',
gap: 6,
color,
fontSize: 13,
cursor: boardId ? 'pointer' : 'not-allowed',
animation:
status === 'requesting' ? 'velxio-pulse 1s ease-in-out infinite' : undefined,

View File

@ -105,6 +105,24 @@
cursor: not-allowed;
}
/* ── Camera / Microphone toggles ─────────────────────
Static layout for the CameraToggle / MicrophoneToggle buttons. It lives
in a class (not inline) so the same @container query that collapses the
Serial/Scope buttons to icon-only can reach these too — an inline
font-size would beat the class and the label could never collapse.
State-driven styles (background, border, colour, pulse animation) stay
inline on the button. */
.canvas-cam-mic-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 0 10px;
height: 28px;
border-radius: 4px;
font-size: 13px;
white-space: nowrap;
}
/* ── Serial Monitor button ───────────────────────── */
.canvas-serial-btn {
display: flex;
@ -383,6 +401,12 @@
height: 18px;
}
.canvas-cam-mic-btn {
padding: 0 6px;
gap: 0;
font-size: 0; /* icon-only on narrow viewports too */
}
.add-component-btn {
padding: 0 8px;
font-size: 0; /* hide text, show icon only */
@ -426,6 +450,12 @@
editor toolbar and view-mode toggle collapse at, so the whole bar degrades
together and nothing overlaps. */
@container unified-toolbar (max-width: 1010px) {
/* Pack the toolbar buttons tighter once the AI chat steals horizontal
room, so Serial/Camera/Mic/Scope stop overlapping. */
.canvas-header-left,
.canvas-header-right {
gap: 4px;
}
.canvas-serial-btn {
padding: 0 8px;
font-size: 0; /* hide "Serial" / "Scope" text, keep the icon */
@ -434,6 +464,13 @@
width: 18px;
height: 18px;
}
/* Same collapse as Serial: hide the "Camera" / "Mic" label, keep the icon.
gap: 0 drops the now-empty icon-to-label space. */
.canvas-cam-mic-btn {
padding: 0 8px;
gap: 0;
font-size: 0;
}
.add-component-btn {
padding: 0 9px;
font-size: 0; /* hide "Add" text, keep the icon */