feat(editor): toolbar rides inside the header — one row, not two
With the marketing nav gone from the editor header, its middle was ~700px of dead space at 1280 while the toolbar occupied a whole second 38px bar. The unified toolbar strip now fills that middle through a new AppHeader editorToolbar slot: one 44px row where there used to be 44+38 — a full row returned to the code and the canvas. The strip keeps its own class names, so everything that keyed on them keeps working untouched: the container queries, the docked-chat padding-right, and the internal flex-wrap. When it truly cannot fit, the strip wraps and the header grows (height: auto on the modifier class) instead of clipping or overlapping; brand and the right-side controls stay pinned to the first line. Inside the header the strip drops its own background and border so it reads as one bar, not a box within a bar. Mobile keeps the previous layout (no strip; the mobile tab bar remains).
This commit is contained in:
parent
b70bb8ab83
commit
ffb4fc0159
|
|
@ -477,3 +477,56 @@ body {
|
|||
@media (prefers-reduced-motion: reduce) {
|
||||
.velxio-ants { animation: none; }
|
||||
}
|
||||
|
||||
/* ── Editor: toolbar riding inside the header ─────────────────────────────
|
||||
With the marketing nav gone from the editor header, its middle was dead
|
||||
space while the toolbar occupied a whole second bar. The strip now fills
|
||||
that middle: one 44px row instead of 44+38 — a full row returned to the
|
||||
code/canvas. The strip keeps its own class names (container queries, the
|
||||
docked-chat padding-right) and wraps internally when it truly cannot
|
||||
fit; the header then grows instead of clipping or overlapping. */
|
||||
.app-header--with-toolbar {
|
||||
height: auto;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.header-editor-toolbar {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.header-editor-toolbar .unified-toolbar {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
/* Skin: it is part of the header now, not a bar of its own. */
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Keep the brand block and the right-side controls pinned to the first
|
||||
line when the strip wraps the header taller. */
|
||||
.app-header--with-toolbar .header-left,
|
||||
.app-header--with-toolbar .header-right {
|
||||
align-self: flex-start;
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-header--with-toolbar .header-content {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* The editor zone / toolbar inside the strip: no own chrome, blend into
|
||||
the header instead of painting a nested dark box with its own border. */
|
||||
.header-editor-toolbar .editor-toolbar,
|
||||
.header-editor-toolbar .unified-toolbar-editor .editor-toolbar {
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
height: 44px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ interface AppHeaderProps {
|
|||
* toolbar is starved of on small screens; the logo still links home.
|
||||
* Same mechanism the Tauri desktop build uses (VITE_DESKTOP). */
|
||||
editorMenu?: React.ReactNode;
|
||||
/** Editor variant: the unified toolbar strip rendered in the header's
|
||||
* middle — the space the marketing nav used to occupy. One row instead
|
||||
* of header + toolbar stacked; the strip wraps internally when narrow
|
||||
* and the header grows to fit (height: auto on the modifier class). */
|
||||
editorToolbar?: React.ReactNode;
|
||||
}
|
||||
|
||||
const SAVE_STATUS_COPY: Record<AutoSaveState['status'], { label: string; color: string }> = {
|
||||
|
|
@ -68,7 +73,7 @@ const AutoSaveIndicator: React.FC<{ state: AutoSaveState }> = ({ state }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const AppHeader: React.FC<AppHeaderProps> = ({ autoSave, editorMenu }) => {
|
||||
export const AppHeader: React.FC<AppHeaderProps> = ({ autoSave, editorMenu, editorToolbar }) => {
|
||||
const location = useLocation();
|
||||
const currentProject = useProjectStore((s) => s.currentProject);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
|
@ -98,7 +103,7 @@ export const AppHeader: React.FC<AppHeaderProps> = ({ autoSave, editorMenu }) =>
|
|||
location.pathname === localize(path) ? ' header-nav-link-active' : '';
|
||||
|
||||
return (
|
||||
<header className="app-header">
|
||||
<header className={"app-header" + (editorToolbar ? ' app-header--with-toolbar' : '')}>
|
||||
<div className="header-content">
|
||||
<div className="header-left">
|
||||
{/* Brand */}
|
||||
|
|
@ -216,6 +221,9 @@ export const AppHeader: React.FC<AppHeaderProps> = ({ autoSave, editorMenu }) =>
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Editor toolbar strip — fills the middle the nav vacated. */}
|
||||
{editorToolbar && <div className="header-editor-toolbar">{editorToolbar}</div>}
|
||||
|
||||
{/* Right: language + share + auth + mobile hamburger */}
|
||||
<div className="header-right">
|
||||
<LanguageSwitcher />
|
||||
|
|
|
|||
|
|
@ -376,62 +376,16 @@ export const EditorPage: React.FC = () => {
|
|||
[explorerWidth],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<AppHeader autoSave={autoSave} editorMenu={!isMobile ? <EditorMenuBar /> : undefined} />
|
||||
|
||||
{/* ── Mobile tab bar (top, above panels) ── */}
|
||||
{isMobile && (
|
||||
<nav className="mobile-tab-bar">
|
||||
<button
|
||||
className={`mobile-tab-btn${mobileView === 'code' ? ' mobile-tab-btn--active' : ''}`}
|
||||
onClick={() => setMobileView('code')}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="16 18 22 12 16 6" />
|
||||
<polyline points="8 6 2 12 8 18" />
|
||||
</svg>
|
||||
<span></> {t('editor.shell.code')}</span>
|
||||
</button>
|
||||
<button
|
||||
className={`mobile-tab-btn${mobileView === 'circuit' ? ' mobile-tab-btn--active' : ''}`}
|
||||
onClick={() => setMobileView('circuit')}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="2" y="7" width="20" height="14" rx="2" />
|
||||
<path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" />
|
||||
<line x1="12" y1="12" x2="12" y2="16" />
|
||||
<line x1="10" y1="14" x2="14" y2="14" />
|
||||
</svg>
|
||||
<span>{t('editor.shell.circuit')}</span>
|
||||
</button>
|
||||
</nav>
|
||||
)}
|
||||
|
||||
{/* ── Unified top toolbar (desktop only) ──
|
||||
Editor controls + canvas controls share a single full-width row so
|
||||
the bar doesn't reflow when the editor/canvas splitter is dragged.
|
||||
The canvas controls (board selector, Serial, Scope, zoom, Add) are
|
||||
portaled into `canvasHeaderSlot` from inside SimulatorCanvas. */}
|
||||
{!isMobile && (
|
||||
/* ── Unified toolbar (desktop) ──
|
||||
Editor controls + canvas controls in one strip; the canvas side is
|
||||
portaled into `canvasHeaderSlot` from SimulatorCanvas. Since the
|
||||
marketing nav left the editor header, the header's middle is empty
|
||||
space — so this strip now rides INSIDE the AppHeader row (via the
|
||||
editorToolbar prop) instead of being a second bar: one 44px row where
|
||||
there used to be 44+38. On narrow widths the strip wraps internally
|
||||
and the header grows; the docked AI chat is avoided by the same
|
||||
padding-right the strip always had. */
|
||||
const unifiedToolbar = !isMobile ? (
|
||||
<div className="unified-toolbar">
|
||||
<button
|
||||
className="explorer-toggle-btn unified-toolbar-explorer-toggle"
|
||||
|
|
@ -524,8 +478,63 @@ export const EditorPage: React.FC = () => {
|
|||
</div>
|
||||
<div className="unified-toolbar-canvas" ref={setCanvasHeaderSlot} />
|
||||
</div>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<AppHeader
|
||||
autoSave={autoSave}
|
||||
editorMenu={!isMobile ? <EditorMenuBar /> : undefined}
|
||||
editorToolbar={unifiedToolbar}
|
||||
/>
|
||||
|
||||
{/* ── Mobile tab bar (top, above panels) ── */}
|
||||
{isMobile && (
|
||||
<nav className="mobile-tab-bar">
|
||||
<button
|
||||
className={`mobile-tab-btn${mobileView === 'code' ? ' mobile-tab-btn--active' : ''}`}
|
||||
onClick={() => setMobileView('code')}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="16 18 22 12 16 6" />
|
||||
<polyline points="8 6 2 12 8 18" />
|
||||
</svg>
|
||||
<span></> {t('editor.shell.code')}</span>
|
||||
</button>
|
||||
<button
|
||||
className={`mobile-tab-btn${mobileView === 'circuit' ? ' mobile-tab-btn--active' : ''}`}
|
||||
onClick={() => setMobileView('circuit')}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="2" y="7" width="20" height="14" rx="2" />
|
||||
<path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" />
|
||||
<line x1="12" y1="12" x2="12" y2="16" />
|
||||
<line x1="10" y1="14" x2="14" y2="14" />
|
||||
</svg>
|
||||
<span>{t('editor.shell.circuit')}</span>
|
||||
</button>
|
||||
</nav>
|
||||
)}
|
||||
|
||||
|
||||
<div className="app-container" ref={containerRef}>
|
||||
{/* ── Editor side ── */}
|
||||
<div
|
||||
|
|
|
|||
Loading…
Reference in New Issue