From 7888596623f696d857cdc599d1d9afb0feb15571 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 31 Mar 2026 10:23:42 -0700 Subject: [PATCH] refactor(shell): move minimize chevron from tab row to subnav bar The minimize toggle now lives at the right edge of each rApp's subnav bar instead of the tab row. When minimized, both header and tab row slide up and the subnav becomes a thin fixed strip with just the restore chevron. Every rApp already has a subnav (except canvas which has its own chrome). Co-Authored-By: Claude Opus 4.6 --- server/shell.ts | 19 ++++------------- website/public/shell.css | 46 ++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/server/shell.ts b/server/shell.ts index edb4265..a0651e9 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -319,7 +319,6 @@ export function renderShell(opts: ShellOptions): string { - @@ -1543,7 +1542,6 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
-
@@ -1566,15 +1564,6 @@ 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)}'; @@ -1982,6 +1971,7 @@ const INFO_PANEL_CSS = ` const SUBNAV_CSS = ` .rapp-subnav { display: flex; + align-items: center; gap: 0.375rem; padding: 0.5rem 1rem; overflow-x: auto; @@ -2056,11 +2046,10 @@ function renderModuleSubNav(moduleId: string, spaceSlug: string, modules: Module } } - // Don't render if no sub-paths - if (items.length === 0) return ''; - const base = (isSubdomain ?? IS_PRODUCTION) ? `/${escapeAttr(moduleId)}` : `/${escapeAttr(spaceSlug)}/${escapeAttr(moduleId)}`; + const minimizeBtn = ``; + const pills = [ `${escapeHtml(mod.name)}`, ...items.map(it => @@ -2069,7 +2058,7 @@ function renderModuleSubNav(moduleId: string, spaceSlug: string, modules: Module ...(mod.externalApp ? [`Open ${escapeHtml(mod.externalApp.name)} ↗`] : []), ]; - return ` + return ` `; } diff --git a/website/public/shell.css b/website/public/shell.css index c5684dd..70243c7 100644 --- a/website/public/shell.css +++ b/website/public/shell.css @@ -488,13 +488,15 @@ body.rstack-sidebar-open #toolbar { .hover-reveal:active { opacity: 1 !important; } } -/* ── Header minimize toggle ── */ +/* ── Header minimize toggle (lives in .rapp-subnav) ── */ .rapp-minimize-btn { display: flex; align-items: center; justify-content: center; - width: 28px; height: 28px; padding: 0; margin-right: 2px; + width: 24px; height: 24px; padding: 0; + margin-left: auto; /* push to right edge of subnav */ + flex-shrink: 0; background: none; border: 1px solid transparent; border-radius: 6px; - color: var(--rs-text-muted); cursor: pointer; flex-shrink: 0; + color: var(--rs-text-muted); cursor: pointer; transition: color 0.15s, background 0.15s, border-color 0.15s; } .rapp-minimize-btn:hover { @@ -507,25 +509,29 @@ body.rspace-headers-minimized .rapp-minimize-btn svg { transform: rotate(180deg); } -/* Minimized state: slide header up, shrink tab row to thin restore strip */ +/* Minimized state: hide header + tab row, subnav becomes 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; + transform: translateY(-100%); + pointer-events: none; } body.rspace-headers-minimized #app { - padding-top: 24px; + padding-top: 0; } body.rspace-headers-minimized #app.canvas-layout { - padding-top: 24px; + padding-top: 0; } body.rspace-headers-minimized .rapp-subnav { - top: 25px; + position: fixed; + top: 0; left: 0; right: 0; + z-index: 9997; + height: 28px; + padding: 2px 8px; + overflow: hidden; + justify-content: flex-end; } /* Smooth transitions for header minimize/restore */ @@ -533,33 +539,27 @@ body.rspace-headers-minimized .rapp-subnav { 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; + transition: margin-left 0.25s ease, left 0.25s ease, transform 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; + max-height: 0; + overflow: hidden; + transform: none; } body.rspace-headers-minimized .rapp-subnav { - top: auto; + position: sticky; + top: 0; } - .rapp-minimize-btn { display: flex; } /* keep visible on mobile unlike info btn */ }