From fe7157ffe19a56642cc6a5b1c339a7a433bb70b2 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sun, 22 Mar 2026 13:59:44 -0700 Subject: [PATCH] fix(shell): position settings/history panels below their buttons Both panels now use position:fixed with JS-computed coordinates from getBoundingClientRect(), ensuring they open directly below their respective header buttons and right-aligned to the button edge. History panel clamps left edge to prevent off-screen overflow. Also adds missing click handlers for settings-btn and history-btn in canvas.html (previously only wired in shell.ts module pages). Co-Authored-By: Claude Opus 4.6 --- shared/components/rstack-history-panel.ts | 27 ++++++++++++++++++---- shared/components/rstack-space-settings.ts | 17 ++++++++++---- website/canvas.html | 10 ++++++++ 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/shared/components/rstack-history-panel.ts b/shared/components/rstack-history-panel.ts index 20148ac..ebeb244 100644 --- a/shared/components/rstack-history-panel.ts +++ b/shared/components/rstack-history-panel.ts @@ -71,6 +71,7 @@ export class RStackHistoryPanel extends HTMLElement { this._open = true; this._refreshHistory(); this._render(); + this._positionPanel(); document.getElementById("history-btn")?.classList.add("active"); document.addEventListener("click", this._clickOutsideHandler, true); } @@ -87,6 +88,27 @@ export class RStackHistoryPanel extends HTMLElement { if (this._open) this.close(); else this.open(); } + /** Position the panel below the history button, right-aligned to button. */ + private _positionPanel() { + const panel = this.shadowRoot?.querySelector(".panel") as HTMLElement | null; + const btn = document.getElementById("history-btn"); + if (!panel || !btn) return; + const rect = btn.getBoundingClientRect(); + panel.style.top = `${rect.bottom + 6}px`; + // Right-align to button's right edge, but clamp so left edge stays on screen + const rightOffset = window.innerWidth - rect.right; + panel.style.right = `${rightOffset}px`; + panel.style.left = "auto"; + // After layout, check if panel overflows left edge + requestAnimationFrame(() => { + const panelRect = panel.getBoundingClientRect(); + if (panelRect.left < 8) { + panel.style.right = "auto"; + panel.style.left = "8px"; + } + }); + } + setDoc(doc: Automerge.Doc) { this._doc = doc; if (this._open) { @@ -417,9 +439,7 @@ const PANEL_CSS = ` } .panel { - position: absolute; - top: 100%; - right: 0; + position: fixed; width: min(420px, 92vw); max-height: calc(100vh - 72px); background: var(--rs-bg-surface, #1e293b); @@ -432,7 +452,6 @@ const PANEL_CSS = ` animation: dropDown 0.2s ease; color: var(--rs-text-primary, #e2e8f0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - margin-top: 6px; } @keyframes dropDown { diff --git a/shared/components/rstack-space-settings.ts b/shared/components/rstack-space-settings.ts index bd54a7e..eeec2da 100644 --- a/shared/components/rstack-space-settings.ts +++ b/shared/components/rstack-space-settings.ts @@ -100,6 +100,7 @@ export class RStackSpaceSettings extends HTMLElement { this._loadData(); this._loadModuleConfig(); this._render(); + this._positionPanel(); document.addEventListener("click", this._clickOutsideHandler, true); } @@ -113,6 +114,17 @@ export class RStackSpaceSettings extends HTMLElement { if (this._open) this.close(); else this.open(); } + /** Position the panel below the settings button, right-aligned. */ + private _positionPanel() { + const panel = this.shadowRoot?.querySelector(".panel") as HTMLElement | null; + const btn = document.getElementById("settings-btn"); + if (!panel || !btn) return; + const rect = btn.getBoundingClientRect(); + panel.style.top = `${rect.bottom + 6}px`; + panel.style.right = `${window.innerWidth - rect.right}px`; + panel.style.left = "auto"; + } + private async _loadData() { if (!this._space) return; @@ -668,9 +680,7 @@ const PANEL_CSS = ` } .panel { - position: absolute; - top: 100%; - right: 0; + position: fixed; width: min(380px, 90vw); max-height: calc(100vh - 72px); background: var(--rs-bg-surface); @@ -683,7 +693,6 @@ const PANEL_CSS = ` animation: dropDown 0.2s ease; color: var(--rs-text-primary); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - margin-top: 6px; } @keyframes dropDown { diff --git a/website/canvas.html b/website/canvas.html index e6ba1dd..8a427ac 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -2532,6 +2532,16 @@ RStackSpaceSettings.define(); RStackHistoryPanel.define(); + // ── Settings & history panel toggle (same as shell.ts) ── + document.getElementById("settings-btn")?.addEventListener("click", () => { + document.querySelector("rstack-history-panel")?.close(); + document.querySelector("rstack-space-settings")?.toggle(); + }); + document.getElementById("history-btn")?.addEventListener("click", () => { + document.querySelector("rstack-space-settings")?.close(); + document.querySelector("rstack-history-panel")?.toggle(); + }); + // Reload space list when user signs in/out document.addEventListener("auth-change", () => { const sw = document.querySelector("rstack-space-switcher");