From e7687dfe7440c8243c3d431f3b2f0f01b9b6ca6f Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 4 Mar 2026 11:50:46 -0800 Subject: [PATCH] feat: icon-only toolbar, relocate zoom/feed/history/hide-forgotten - Toolbar buttons show emoji-only with hover tooltips (data-tip ::after) - Recent Changes moved to header bar as clock icon button - Zoom + Feed View moved to bottom-left corner tools pill - Hide Forgotten moved to identity dropdown under theme toggle - Mobile: 44px touch targets, tap-highlight suppression, proper sizing - Memory panel z-index raised above header for correct stacking Co-Authored-By: Claude Opus 4.6 --- website/canvas.html | 345 +++++++++++++++++++++++++++++---------- website/public/shell.css | 8 +- 2 files changed, 265 insertions(+), 88 deletions(-) diff --git a/website/canvas.html b/website/canvas.html index 2bb097c..d831bc9 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -28,6 +28,7 @@ height: 100%; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + -webkit-tap-highlight-color: transparent; } #toolbar { @@ -38,7 +39,7 @@ flex-direction: column; align-items: stretch; gap: 4px; - padding: 8px 6px; + padding: 6px 4px; background: var(--rs-toolbar-bg); border-radius: 12px; box-shadow: var(--rs-shadow-md); @@ -223,6 +224,46 @@ color: white; } + /* Icon-only toolbar: show only emoji, clip text */ + .toolbar-group-toggle { + width: 42px; + overflow: hidden; + text-overflow: clip; + text-align: center; + padding-left: 0; + padding-right: 0; + position: relative; + } + + /* Tooltip on hover */ + .toolbar-group-toggle[data-tip]::after { + content: attr(data-tip); + position: absolute; + left: calc(100% + 8px); + top: 50%; + transform: translateY(-50%); + padding: 4px 10px; + border-radius: 6px; + background: var(--rs-toolbar-panel-bg); + color: var(--rs-toolbar-btn-text); + font-size: 12px; + white-space: nowrap; + box-shadow: var(--rs-shadow-md); + opacity: 0; + pointer-events: none; + transition: opacity 0.15s; + z-index: 1002; + } + + .toolbar-group-toggle[data-tip]:hover::after { + opacity: 1; + } + + /* Hide tooltip when group is open (dropdown is showing) */ + .toolbar-group.open > .toolbar-group-toggle[data-tip]::after { + display: none; + } + /* Collapse/expand toggle — small pill at bottom of toolbar */ #toolbar-collapse { padding: 4px 0 !important; @@ -482,6 +523,87 @@ 50% { opacity: 0.5; } } + /* ── Corner tools (zoom + feed) — bottom-left ── */ + #canvas-corner-tools { + position: fixed; + bottom: 16px; + left: 12px; + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + padding: 4px; + background: var(--rs-toolbar-bg); + border-radius: 12px; + box-shadow: var(--rs-shadow-md); + z-index: 1000; + } + + .corner-btn { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border: none; + border-radius: 8px; + background: transparent; + color: var(--rs-toolbar-btn-text); + cursor: pointer; + transition: background 0.15s, color 0.15s; + touch-action: manipulation; + } + + .corner-btn:hover { + background: var(--rs-toolbar-btn-hover); + } + + .corner-btn.active { + background: var(--rs-accent); + color: white; + } + + .corner-btn svg { + pointer-events: none; + } + + .corner-sep { + width: 24px; + height: 1px; + background: var(--rs-toolbar-sep); + margin: 2px 0; + flex-shrink: 0; + } + + /* ── Header history button ── */ + .canvas-header-history { + display: flex; + align-items: center; + justify-content: center; + padding: 6px; + border: none; + border-radius: 8px; + background: transparent; + color: var(--rs-text-secondary); + cursor: pointer; + transition: background 0.15s, color 0.15s; + touch-action: manipulation; + } + + .canvas-header-history:hover { + background: var(--rs-bg-hover, rgba(255,255,255,0.1)); + color: var(--rs-text-primary); + } + + .canvas-header-history.active { + background: var(--rs-accent); + color: white; + } + + .canvas-header-history svg { + pointer-events: none; + } + /* Memory layer panel */ #memory-panel { position: fixed; @@ -492,7 +614,7 @@ background: var(--rs-bg-surface); border-radius: 12px; box-shadow: var(--rs-shadow-lg); - z-index: 1001; + z-index: 10000; /* above header (9999) */ display: none; overflow: hidden; } @@ -1367,26 +1489,9 @@ touch-action: manipulation; } - /* Always-visible zoom strip */ + /* Hide old mobile zoom — replaced by #canvas-corner-tools */ #mobile-zoom { - display: flex; - position: fixed; - bottom: 24px; - left: 16px; - gap: 4px; - z-index: 1002; - } - - #mobile-zoom button { - width: 40px; - height: 40px; - border: none; - border-radius: 50%; - background: white; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - font-size: 16px; - cursor: pointer; - touch-action: manipulation; + display: none; } /* Hide desktop toolbar, show as column overlay when toggled */ @@ -1420,6 +1525,12 @@ padding: 12px 14px; font-size: 14px; min-height: 44px; + overflow: visible; + } + + /* Hide tooltips on mobile — full text shown */ + #toolbar .toolbar-group-toggle[data-tip]::after { + display: none; } #toolbar .toolbar-dropdown { @@ -1486,6 +1597,25 @@ #bottom-toolbar .tool-sep { margin: 0 2px; } + + /* Corner tools: horizontal on mobile, 44px touch targets */ + #canvas-corner-tools { + bottom: 8px; + left: 8px; + flex-direction: row; + padding: 4px 6px; + } + + #canvas-corner-tools .corner-btn { + width: 44px; + height: 44px; + } + + #canvas-corner-tools .corner-sep { + width: 1px; + height: 24px; + margin: 0 2px; + } } /* ── Feed sort bar ── */ @@ -1523,34 +1653,6 @@ color: #e2e8f0; } - /* ── Feed toggle button ── */ - #feed-toggle { - padding: 7px 10px; - border: none; - border-radius: 8px; - background: #f1f5f9; - cursor: pointer; - font-size: 13px; - transition: background 0.2s; - white-space: nowrap; - text-align: left; - touch-action: manipulation; - } - #feed-toggle:hover { background: #e2e8f0; } - #feed-toggle.active { - background: #14b8a6; - color: white; - } - body[data-theme="dark"] #feed-toggle { - background: #334155; - color: #e2e8f0; - } - body[data-theme="dark"] #feed-toggle:hover { background: #475569; } - body[data-theme="dark"] #feed-toggle.active { - background: #14b8a6; - color: white; - } - @media (max-width: 768px) { #feed-sort-bar { top: 64px; @@ -1720,7 +1822,7 @@
- +
@@ -1729,7 +1831,7 @@
- +
@@ -1745,7 +1847,7 @@
- +
@@ -1756,7 +1858,7 @@
- +
@@ -1776,7 +1878,7 @@
- +
@@ -1784,7 +1886,7 @@
- +
@@ -1795,7 +1897,7 @@
- +
@@ -1809,23 +1911,6 @@
- - - - - - - - -
- -
- - - -
-
-
@@ -1873,6 +1958,26 @@
+ +
+ + + + + +
+ + + +

💭 Recent Changes

@@ -3933,11 +4038,7 @@ title: btn.title || btn.textContent.trim(), }; }); - // Add direct sidebar buttons too - ["toggle-memory", "toggle-hide-forgotten", "feed-toggle"].forEach(id => { - const btn = document.getElementById(id); - if (btn) sidebarToolRegistry[id] = { id, label: btn.textContent.trim(), title: btn.title || btn.textContent.trim() }; - }); + // Note: toggle-memory, toggle-hide-forgotten, feed-toggle moved out of toolbar function trackRecentTool(toolId) { if (!toolId || !sidebarToolRegistry[toolId]) return; @@ -4375,6 +4476,20 @@ } }); + // ── Inject history button into header ── + { + const headerRight = document.querySelector('.rstack-header__right'); + if (headerRight) { + const historyBtn = document.createElement('button'); + historyBtn.id = 'toggle-memory'; + historyBtn.className = 'canvas-header-history'; + historyBtn.title = 'Recent Changes'; + historyBtn.innerHTML = ``; + const identity = headerRight.querySelector('rstack-identity'); + headerRight.insertBefore(historyBtn, identity); + } + } + // Memory panel — browse and remember forgotten shapes const memoryPanel = document.getElementById("memory-panel"); const memoryList = document.getElementById("memory-list"); @@ -4503,17 +4618,73 @@ if (isOpen) renderMemoryPanel(); }); - // Hide Faded toggle — hides all forgotten shapes from the canvas + // Hide Forgotten toggle — hides all forgotten shapes from the canvas + // Injected into rstack-identity dropdown under theme toggle const hideForgottenBtn = document.getElementById("toggle-hide-forgotten"); - if (localStorage.getItem("rspace_hide_forgotten") === "1") { - canvasContent.classList.add("hide-forgotten"); - hideForgottenBtn.classList.add("active"); - } - hideForgottenBtn.addEventListener("click", () => { + const isHiddenInit = localStorage.getItem("rspace_hide_forgotten") === "1"; + if (isHiddenInit) canvasContent.classList.add("hide-forgotten"); + + function toggleHideForgotten() { const hidden = canvasContent.classList.toggle("hide-forgotten"); - hideForgottenBtn.classList.toggle("active", hidden); localStorage.setItem("rspace_hide_forgotten", hidden ? "1" : "0"); - }); + // Sync checkbox if it exists in dropdown + const identityEl = document.querySelector('rstack-identity'); + if (identityEl?.shadowRoot) { + const cb = identityEl.shadowRoot.getElementById('hide-forgotten-toggle'); + if (cb) cb.checked = hidden; + } + } + + // Inject "Hide Forgotten" into identity dropdown after theme row + { + const identityEl = document.querySelector('rstack-identity'); + function injectHideForgotten() { + const sr = identityEl?.shadowRoot; + if (!sr) return; + const themeRow = sr.querySelector('.dropdown-theme-row'); + if (!themeRow || sr.getElementById('hide-forgotten-row')) return; + + const divider = document.createElement('div'); + divider.className = 'dropdown-divider'; + + const row = document.createElement('div'); + row.id = 'hide-forgotten-row'; + row.className = 'dropdown-theme-row'; + row.style.cssText = 'justify-content: space-between; padding: 6px 16px; cursor: pointer;'; + row.innerHTML = ` + 👁 Hide Forgotten + + `; + + // Insert after theme row's next sibling (the divider after theme) + const nextDiv = themeRow.nextElementSibling; + if (nextDiv) { + nextDiv.after(row); + nextDiv.after(divider); + } + + const cb = sr.getElementById('hide-forgotten-toggle'); + if (cb) { + cb.addEventListener('click', (e) => e.stopPropagation()); + cb.addEventListener('change', () => toggleHideForgotten()); + } + } + + // Identity renders asynchronously — observe until dropdown is ready + if (identityEl) { + const obs = new MutationObserver(() => { + injectHideForgotten(); + if (identityEl.shadowRoot?.getElementById('hide-forgotten-row')) obs.disconnect(); + }); + obs.observe(identityEl, { childList: true, subtree: true }); + // Also try immediately in case it's already rendered + setTimeout(injectHideForgotten, 500); + setTimeout(injectHideForgotten, 2000); + } + } // Refresh panel when shapes change state via remote sync sync.addEventListener("shape-forgotten", () => { @@ -4587,7 +4758,7 @@ const btn = e.target.closest("button"); if (!btn) return; // Keep open for connect, memory, group toggles, collapse, whiteboard tools - const keepOpen = ["toggle-memory", "toggle-hide-forgotten", "toggle-theme", "zoom-in", "zoom-out", "reset-view", "toolbar-collapse"]; + const keepOpen = ["toggle-theme", "toolbar-collapse"]; if (btn.classList.contains("toolbar-group-toggle")) return; if (!keepOpen.includes(btn.id)) { toolbarEl.classList.remove("mobile-open"); diff --git a/website/public/shell.css b/website/public/shell.css index ec3af93..8778123 100644 --- a/website/public/shell.css +++ b/website/public/shell.css @@ -293,7 +293,8 @@ body { .rstack-tab-row, #app, -.rspace-iframe-wrap { +.rspace-iframe-wrap, +#toolbar { transition: margin-left 0.25s ease, left 0.25s ease; } @@ -309,6 +310,10 @@ body.rstack-sidebar-open .rspace-iframe-wrap { left: 280px; } +body.rstack-sidebar-open #toolbar { + left: 292px; /* 280px sidebar + 12px original margin */ +} + /* ── Mobile adjustments ── */ @media (max-width: 640px) { @@ -375,6 +380,7 @@ body.rstack-sidebar-open .rspace-iframe-wrap { body.rstack-sidebar-open .rstack-tab-row { left: 0; } body.rstack-sidebar-open #app { margin-left: 0; } body.rstack-sidebar-open .rspace-iframe-wrap { left: 0; } + body.rstack-sidebar-open #toolbar { left: 12px; } } @media (max-width: 480px) {