From 36ae954da4331becc0aeef527bd1b8e64da906a3 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 30 Mar 2026 23:35:12 -0700 Subject: [PATCH] feat(shell): add header minimize toggle for more viewport space Adds a [^] chevron button at the right end of the tab row that collapses all header bars into a thin 24px restore strip. State persists via localStorage across page reloads. Works on both desktop (fixed) and mobile (sticky) layouts. Co-Authored-By: Claude Opus 4.6 --- server/shell.ts | 19 ++++++++++ website/public/shell.css | 78 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/server/shell.ts b/server/shell.ts index 2de60d0..94c8104 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -318,6 +318,7 @@ export function renderShell(opts: ShellOptions): string { + @@ -352,6 +353,14 @@ export function renderShell(opts: ShellOptions): string { window.__rspaceInstallPrompt = () => { e.prompt(); return e.userChoice; }; window.dispatchEvent(new CustomEvent("rspace-install-available")); }); + // ── Header minimize toggle ── + if (localStorage.getItem('rspace_headers_minimized') === '1') { + document.body.classList.add('rspace-headers-minimized'); + } + document.getElementById('header-minimize-btn')?.addEventListener('click', () => { + const minimized = document.body.classList.toggle('rspace-headers-minimized'); + localStorage.setItem('rspace_headers_minimized', minimized ? '1' : '0'); + }); // ── Settings panel toggle (close history first) ── document.getElementById('settings-btn')?.addEventListener('click', () => { document.querySelector('rstack-history-panel')?.close(); @@ -1533,6 +1542,7 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
+
@@ -1555,6 +1565,15 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string { import '/shell.js'; document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON}); + // ── Header minimize toggle ── + if (localStorage.getItem('rspace_headers_minimized') === '1') { + document.body.classList.add('rspace-headers-minimized'); + } + document.getElementById('header-minimize-btn')?.addEventListener('click', () => { + const minimized = document.body.classList.toggle('rspace-headers-minimized'); + localStorage.setItem('rspace_headers_minimized', minimized ? '1' : '0'); + }); + const tabBar = document.querySelector('rstack-tab-bar'); const spaceSlug = '${escapeAttr(spaceSlug)}'; const currentModuleId = '${escapeAttr(moduleId)}'; diff --git a/website/public/shell.css b/website/public/shell.css index e54136c..c5684dd 100644 --- a/website/public/shell.css +++ b/website/public/shell.css @@ -326,8 +326,6 @@ body { /* ── Sidebar push offsets ── */ -.rstack-tab-row, -#app, rstack-user-dashboard, .rspace-iframe-wrap, #toolbar { @@ -489,3 +487,79 @@ body.rstack-sidebar-open #toolbar { .hover-reveal { opacity: 0.5 !important; } .hover-reveal:active { opacity: 1 !important; } } + +/* ── Header minimize toggle ── */ + +.rapp-minimize-btn { + display: flex; align-items: center; justify-content: center; + width: 28px; height: 28px; padding: 0; margin-right: 2px; + background: none; border: 1px solid transparent; border-radius: 6px; + color: var(--rs-text-muted); cursor: pointer; flex-shrink: 0; + transition: color 0.15s, background 0.15s, border-color 0.15s; +} +.rapp-minimize-btn:hover { + color: var(--rs-text-primary); background: var(--rs-bg-hover); border-color: var(--rs-border); +} +.rapp-minimize-btn svg { + transition: transform 0.25s ease; +} +body.rspace-headers-minimized .rapp-minimize-btn svg { + transform: rotate(180deg); +} + +/* Minimized state: slide header up, shrink tab row to thin restore strip */ +body.rspace-headers-minimized .rstack-header { + transform: translateY(-100%); + pointer-events: none; +} +body.rspace-headers-minimized .rstack-tab-row { + top: 0; + height: 24px; + overflow: hidden; + justify-content: flex-end; +} +body.rspace-headers-minimized #app { + padding-top: 24px; +} +body.rspace-headers-minimized #app.canvas-layout { + padding-top: 24px; +} +body.rspace-headers-minimized .rapp-subnav { + top: 25px; +} + +/* Smooth transitions for header minimize/restore */ +.rstack-header { + transition: transform 0.25s ease; +} +.rstack-tab-row { + transition: margin-left 0.25s ease, left 0.25s ease, top 0.25s ease, height 0.25s ease; +} +#app { + transition: margin-left 0.25s ease, left 0.25s ease, padding-top 0.25s ease; +} +.rapp-subnav { + transition: top 0.25s ease; +} + +/* Mobile: minimized state adjustments (sticky, not fixed) */ +@media (max-width: 640px) { + body.rspace-headers-minimized .rstack-header { + transform: translateY(-100%); + max-height: 0; + overflow: hidden; + padding: 0; + border: 0; + } + body.rspace-headers-minimized .rstack-tab-row { + top: auto; + height: 24px; + } + body.rspace-headers-minimized #app { + padding-top: 0; + } + body.rspace-headers-minimized .rapp-subnav { + top: auto; + } + .rapp-minimize-btn { display: flex; } /* keep visible on mobile unlike info btn */ +}