fix(editor): toolbar never overlaps on a narrow bar; layout switches live in the View menu
Two things went wrong when the window was small and the AI chat docked: 1. Libraries / Output painted over the board selector. The editor zone has min-width: max-content, but its child .editor-toolbar-wrapper is an inline-size query container, and size containment makes it contribute zero intrinsic width — so the floor resolved to nothing, flexbox squeezed the zone under its buttons and the bar never got to wrap. Containment is now off for the in-bar mount (the wrapper keeps it for its standalone mobile mount); the two label collapses that depended on it are re-keyed to the unified-toolbar container at the same thresholds the canvas controls use (1010 / 905px), so the row degrades in step and, when it truly cannot fit, wraps instead. 2. The explorer / Code / Both / Circuit segmented toggle now hides once the shared bar is under 760px (its inline display moved to CSS so a container query can do that). Every switch it had is a checkmarked row in the View menu: File Explorer, and Code / Both / Circuit as a radio group under a View mode label. explorerOpen moves from EditorPage state into useEditorStore so the menu can read it.
This commit is contained in:
parent
51b775997a
commit
f09489f498
|
|
@ -188,12 +188,27 @@ body {
|
|||
|
||||
/* Collapse the Code/Both/Circuit labels to icon-only first (biggest fixed
|
||||
block in the bar) as the shared bar narrows. */
|
||||
.view-mode-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@container unified-toolbar (max-width: 1140px) {
|
||||
.view-mode-toggle .vm-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Narrow bar (small window with the AI chat docked): drop the explorer /
|
||||
Code / Both / Circuit toggle altogether. Every one of its switches is a
|
||||
checkmarked row in the header's View menu, so nothing is lost — and the
|
||||
~140px it frees is what keeps the Run group and the canvas controls on
|
||||
one line instead of overlapping. */
|
||||
@container unified-toolbar (max-width: 760px) {
|
||||
.view-mode-toggle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.unified-toolbar-editor {
|
||||
flex: 1 1 auto;
|
||||
/* Floor the editor zone at its own content width so it can never shrink
|
||||
|
|
@ -212,6 +227,18 @@ body {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
/* The wrapper is an inline-size query container for its standalone (mobile)
|
||||
mount. Inside the shared bar that containment is exactly what let the
|
||||
editor zone shrink under its buttons: size containment makes the wrapper
|
||||
contribute ZERO intrinsic width, so the zone's `min-width: max-content`
|
||||
above resolved to nothing, flexbox squeezed it, and Libraries / Output
|
||||
painted over the board selector instead of the bar wrapping. Turn it off
|
||||
here — the label collapses are keyed to the `unified-toolbar` container
|
||||
in EditorToolbar.css for this mount. */
|
||||
.unified-toolbar-editor .editor-toolbar-wrapper {
|
||||
container-type: normal;
|
||||
}
|
||||
|
||||
.unified-toolbar-editor .editor-toolbar {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useSimulatorStore } from '../../store/useSimulatorStore';
|
||||
import { useOscilloscopeStore } from '../../store/useOscilloscopeStore';
|
||||
import { useEditorStore, type EditorViewMode } from '../../store/useEditorStore';
|
||||
import { LOCALES, LOCALE_META, type Locale } from '../../i18n/config';
|
||||
import { getLocaleFromPath, switchLocale } from '../../i18n/path';
|
||||
import {
|
||||
|
|
@ -78,6 +79,13 @@ export const EditorMenuBar: React.FC = () => {
|
|||
const toggleSerialMonitor = useSimulatorStore((s) => s.toggleSerialMonitor);
|
||||
const scopeOpen = useOscilloscopeStore((s) => s.open);
|
||||
const toggleOscilloscope = useOscilloscopeStore((s) => s.toggleOscilloscope);
|
||||
// Layout rows: the same switches as the toolbar's explorer / Code / Both /
|
||||
// Circuit toggle, which hides on a narrow bar (App.css) — the menu is then
|
||||
// the only way to reach them, so they carry live checkmarks.
|
||||
const explorerOpen = useEditorStore((s) => s.explorerOpen);
|
||||
const toggleExplorer = useEditorStore((s) => s.toggleExplorer);
|
||||
const viewMode = useEditorStore((s) => s.viewMode);
|
||||
const setViewMode = useEditorStore((s) => s.setViewMode);
|
||||
const undo = useSimulatorStore((s) => s.undo);
|
||||
const redo = useSimulatorStore((s) => s.redo);
|
||||
const history = useSimulatorStore((s) => s.history);
|
||||
|
|
@ -182,13 +190,14 @@ export const EditorMenuBar: React.FC = () => {
|
|||
// everything view-shaped lives in the View menu, like the desktop app.
|
||||
const editItems: Item[] = [];
|
||||
|
||||
// File Explorer is rendered as a checkmarked row in the View block below
|
||||
// (it reads the store), not as a plain command here.
|
||||
const viewItems: Item[] = [
|
||||
{ kind: 'command', id: 'sim.compile', label: t('editor.menu.compile', 'Compile'), shortcut: 'Ctrl+B' },
|
||||
{ kind: 'command', id: 'sim.run', label: t('editor.menu.run', 'Run') },
|
||||
{ kind: 'command', id: 'sim.stop', label: t('editor.toolbar.stop', 'Stop') },
|
||||
{ kind: 'command', id: 'sim.resetBoard', label: t('editor.toolbar.reset', 'Reset') },
|
||||
{ kind: 'separator' },
|
||||
{ kind: 'command', id: 'view.toggleExplorer', label: t('editor.menu.toggleExplorer', 'File Explorer') },
|
||||
{ kind: 'command', id: 'view.toggleConsole', label: t('editor.menu.toggleConsole', 'Output Console') },
|
||||
{ kind: 'separator' },
|
||||
{ kind: 'command', id: 'view.reset', label: t('editor.menu.centerView', 'Center canvas view') },
|
||||
|
|
@ -196,6 +205,12 @@ export const EditorMenuBar: React.FC = () => {
|
|||
{ kind: 'command', id: 'view.zoomOut', label: t('editor.canvas.zoomOut', 'Zoom out') },
|
||||
];
|
||||
|
||||
const layoutModes: { key: EditorViewMode; label: string }[] = [
|
||||
{ key: 'code', label: t('editor.shell.code', 'Code') },
|
||||
{ key: 'both', label: t('editor.shell.both', 'Both') },
|
||||
{ key: 'circuit', label: t('editor.shell.circuit', 'Circuit') },
|
||||
];
|
||||
|
||||
const renderLink = (item: Extract<Item, { kind: 'link' }>): React.ReactNode => (
|
||||
<a
|
||||
key={item.href}
|
||||
|
|
@ -269,6 +284,38 @@ export const EditorMenuBar: React.FC = () => {
|
|||
<span className="emb-shortcut">{scopeOpen ? '✓' : ''}</span>
|
||||
</button>
|
||||
<div className="emb-separator" />
|
||||
{/* Layout: explorer pane + Code / Both / Circuit. Mirrors the
|
||||
toolbar's segmented toggle, which App.css hides once the
|
||||
shared bar gets narrow (small window + docked AI chat). */}
|
||||
<div className="emb-section-label">{t('editor.shell.viewMode', 'View mode')}</div>
|
||||
<button
|
||||
role="menuitemcheckbox"
|
||||
aria-checked={explorerOpen}
|
||||
className="emb-item"
|
||||
onClick={() => {
|
||||
setOpen(null);
|
||||
toggleExplorer();
|
||||
}}
|
||||
>
|
||||
<span>{t('editor.menu.toggleExplorer', 'File Explorer')}</span>
|
||||
<span className="emb-shortcut">{explorerOpen ? '✓' : ''}</span>
|
||||
</button>
|
||||
{layoutModes.map((m) => (
|
||||
<button
|
||||
key={m.key}
|
||||
role="menuitemradio"
|
||||
aria-checked={viewMode === m.key}
|
||||
className="emb-item"
|
||||
onClick={() => {
|
||||
setOpen(null);
|
||||
setViewMode(m.key);
|
||||
}}
|
||||
>
|
||||
<span>{m.label}</span>
|
||||
<span className="emb-shortcut">{viewMode === m.key ? '✓' : ''}</span>
|
||||
</button>
|
||||
))}
|
||||
<div className="emb-separator" />
|
||||
</>
|
||||
)}
|
||||
{which === 'account' && (
|
||||
|
|
|
|||
|
|
@ -171,6 +171,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Same two collapses when the toolbar rides inside the shared desktop bar
|
||||
(App.css turns the wrapper's own containment off there so the bar can
|
||||
wrap instead of overlap): key them to the BAR's width, at the thresholds
|
||||
where the canvas controls drop their labels (1010px) and the board
|
||||
selector shrinks (905px), so the whole row degrades in step. */
|
||||
@container unified-toolbar (max-width: 1010px) {
|
||||
.tb-libraries-label {
|
||||
display: none;
|
||||
}
|
||||
.tb-btn-libraries {
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
@container unified-toolbar (max-width: 905px) {
|
||||
.tb-board-pill-label {
|
||||
display: none;
|
||||
}
|
||||
.tb-board-pill {
|
||||
padding: 3px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Import / Export moved to the File menu in the header (EditorMenuBar):
|
||||
their inline buttons and the responsive overflow twins are gone, which
|
||||
is ~70px the row no longer has to find on laptop widths. */
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ export const EditorPage: React.FC = () => {
|
|||
// Lets users hide a pane to give the right-docked chat more room.
|
||||
const viewMode = useEditorStore((s) => s.viewMode);
|
||||
const setViewMode = useEditorStore((s) => s.setViewMode);
|
||||
const explorerOpen = useEditorStore((s) => s.explorerOpen);
|
||||
const setExplorerOpen = useEditorStore((s) => s.setExplorerOpen);
|
||||
const toggleExplorer = useEditorStore((s) => s.toggleExplorer);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const resizingRef = useRef(false);
|
||||
const serialMonitorOpen = useSimulatorStore((s) => s.serialMonitorOpen);
|
||||
|
|
@ -217,7 +220,6 @@ export const EditorPage: React.FC = () => {
|
|||
localStorage.setItem('velxio_star_clicked', '1');
|
||||
setShowStarBanner(false);
|
||||
};
|
||||
const [explorerOpen, setExplorerOpen] = useState(true);
|
||||
const [explorerWidth, setExplorerWidth] = useState(EXPLORER_DEFAULT);
|
||||
const [isMobile, setIsMobile] = useState(
|
||||
() => window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`).matches,
|
||||
|
|
@ -256,7 +258,7 @@ export const EditorPage: React.FC = () => {
|
|||
update(mq);
|
||||
mq.addEventListener('change', update);
|
||||
return () => mq.removeEventListener('change', update);
|
||||
}, []);
|
||||
}, [setExplorerOpen]);
|
||||
|
||||
// Ctrl+S shortcut
|
||||
useEffect(() => {
|
||||
|
|
@ -277,15 +279,13 @@ export const EditorPage: React.FC = () => {
|
|||
const offNew = registerEditorCommand('project.new', () => {
|
||||
void handleNewClick();
|
||||
});
|
||||
const offExplorer = registerEditorCommand('view.toggleExplorer', () =>
|
||||
setExplorerOpen((v) => !v),
|
||||
);
|
||||
const offExplorer = registerEditorCommand('view.toggleExplorer', () => toggleExplorer());
|
||||
return () => {
|
||||
offSave();
|
||||
offNew();
|
||||
offExplorer();
|
||||
};
|
||||
}, [handleSaveClick, handleNewClick]);
|
||||
}, [handleSaveClick, handleNewClick, toggleExplorer]);
|
||||
|
||||
// Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z — canvas undo/redo. Skipped when the
|
||||
// user is typing in any input/textarea/contenteditable so the Monaco
|
||||
|
|
@ -446,7 +446,7 @@ export const EditorPage: React.FC = () => {
|
|||
aria-label={t('editor.shell.viewMode')}
|
||||
className="view-mode-toggle"
|
||||
style={{
|
||||
display: 'flex',
|
||||
// display comes from App.css (flex; none on a narrow bar or mobile).
|
||||
// Never squeezed below its buttons: flexbox used to crush
|
||||
// this block to a third of its width and clip two of them.
|
||||
flexShrink: 0,
|
||||
|
|
@ -460,7 +460,7 @@ export const EditorPage: React.FC = () => {
|
|||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => setExplorerOpen((v) => !v)}
|
||||
onClick={() => toggleExplorer()}
|
||||
aria-pressed={explorerOpen}
|
||||
title={explorerOpen ? t('editor.menu.hideExplorer', 'Hide file explorer') : t('editor.menu.showExplorer', 'Show file explorer')}
|
||||
style={{
|
||||
|
|
@ -663,7 +663,7 @@ export const EditorPage: React.FC = () => {
|
|||
<div style={{ display: 'flex', alignItems: 'stretch', flexShrink: 0 }}>
|
||||
<button
|
||||
className="explorer-toggle-btn"
|
||||
onClick={() => setExplorerOpen((v) => !v)}
|
||||
onClick={() => toggleExplorer()}
|
||||
title={explorerOpen ? t('editor.menu.hideExplorer', 'Hide file explorer') : t('editor.menu.showExplorer', 'Show file explorer')}
|
||||
>
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -152,6 +152,12 @@ interface EditorState {
|
|||
fontSize: number;
|
||||
viewMode: EditorViewMode;
|
||||
setViewMode: (mode: EditorViewMode) => void;
|
||||
/** Desktop file-explorer pane. Lives in the store (not EditorPage state) so
|
||||
* the header's View menu can show its checkmark and toggle it — the
|
||||
* toolbar's own segmented toggle hides on narrow bars. */
|
||||
explorerOpen: boolean;
|
||||
setExplorerOpen: (open: boolean) => void;
|
||||
toggleExplorer: () => void;
|
||||
|
||||
// ── File groups (one per board) ──────────────────────────────────────────
|
||||
/** Map of groupId → WorkspaceFile[]. Stored as plain object for Zustand. */
|
||||
|
|
@ -229,6 +235,9 @@ export const useEditorStore = create<EditorState>((set, get) => ({
|
|||
fontSize: 14,
|
||||
viewMode: 'both',
|
||||
setViewMode: (mode) => set({ viewMode: mode }),
|
||||
explorerOpen: true,
|
||||
setExplorerOpen: (open) => set({ explorerOpen: open }),
|
||||
toggleExplorer: () => set((s) => ({ explorerOpen: !s.explorerOpen })),
|
||||
|
||||
// File groups — initial state has one group for the default Arduino Uno board
|
||||
fileGroups: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue