revert: restore app switcher to left sidebar
Reverts 4420d9c — the FAB change was applied to the wrong component.
The intended target is the rSpace canvas toolbar, not the app switcher.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f26f7e14bd
commit
8321a9015a
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* <rstack-app-switcher> — Bottom-right FAB that expands upward to switch between rSpace modules.
|
||||
* <rstack-app-switcher> — Collapsible left sidebar to switch between rSpace modules.
|
||||
*
|
||||
* Attributes:
|
||||
* current — the active module ID (highlighted)
|
||||
|
|
@ -170,6 +170,8 @@ export class RStackAppSwitcher extends HTMLElement {
|
|||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
// Clean up body class and outside-click listener on removal
|
||||
document.body.classList.remove("rstack-sidebar-open");
|
||||
if (this.#outsideClickHandler) {
|
||||
document.removeEventListener("pointerdown", this.#outsideClickHandler);
|
||||
this.#outsideClickHandler = null;
|
||||
|
|
@ -353,16 +355,19 @@ export class RStackAppSwitcher extends HTMLElement {
|
|||
const currentMod = this.#modules.find((m) => m.id === current);
|
||||
const badgeInfo = currentMod ? MODULE_BADGES[currentMod.id] : null;
|
||||
|
||||
const fabContent = badgeInfo
|
||||
? `<span class="fab-badge" style="background:${badgeInfo.color}">${badgeInfo.badge}</span>`
|
||||
: `<span class="fab-badge rstack-gradient">r✨</span>`;
|
||||
const triggerContent = badgeInfo
|
||||
? `<span class="trigger-badge" style="background:${badgeInfo.color}">${badgeInfo.badge}</span> ${currentMod!.name}`
|
||||
: currentMod
|
||||
? `${currentMod.icon} ${currentMod.name}`
|
||||
: `<span class="trigger-badge rstack-gradient">r✨</span> rSpace`;
|
||||
|
||||
this.#shadow.innerHTML = `
|
||||
<style>${STYLES}</style>
|
||||
<div class="switcher">
|
||||
<button class="fab-trigger" id="trigger" title="Switch rApp">${fabContent}</button>
|
||||
<div class="panel-backdrop" id="backdrop"></div>
|
||||
<div class="panel" id="sidebar">
|
||||
<button class="trigger" id="trigger">${triggerContent} <span class="caret">▾</span></button>
|
||||
<div class="sidebar-backdrop" id="backdrop"></div>
|
||||
<div class="sidebar" id="sidebar">
|
||||
<button class="collapse-btn" id="collapse" title="Close sidebar">‹</button>
|
||||
${this.#renderGroupedModules(current)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -370,23 +375,29 @@ export class RStackAppSwitcher extends HTMLElement {
|
|||
|
||||
const trigger = this.#shadow.getElementById("trigger")!;
|
||||
const sidebar = this.#shadow.getElementById("sidebar")!;
|
||||
const collapse = this.#shadow.getElementById("collapse")!;
|
||||
const backdrop = this.#shadow.getElementById("backdrop")!;
|
||||
|
||||
// Sync panel DOM with tracked state (survives re-renders)
|
||||
// Sync sidebar DOM with tracked state (survives re-renders)
|
||||
if (this.#isOpen) {
|
||||
sidebar.classList.add("open");
|
||||
backdrop.classList.add("visible");
|
||||
document.body.classList.add("rstack-sidebar-open");
|
||||
} else {
|
||||
document.body.classList.remove("rstack-sidebar-open");
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
this.#isOpen = true;
|
||||
sidebar.classList.add("open");
|
||||
backdrop.classList.add("visible");
|
||||
document.body.classList.add("rstack-sidebar-open");
|
||||
};
|
||||
const close = () => {
|
||||
this.#isOpen = false;
|
||||
sidebar.classList.remove("open");
|
||||
backdrop.classList.remove("visible");
|
||||
document.body.classList.remove("rstack-sidebar-open");
|
||||
};
|
||||
const toggle = () => {
|
||||
if (this.#isOpen) close(); else open();
|
||||
|
|
@ -397,6 +408,7 @@ export class RStackAppSwitcher extends HTMLElement {
|
|||
toggle();
|
||||
});
|
||||
|
||||
collapse.addEventListener("click", () => close());
|
||||
backdrop.addEventListener("click", () => close());
|
||||
|
||||
// Close sidebar when clicking outside (on main content)
|
||||
|
|
@ -577,75 +589,75 @@ const STYLES = `
|
|||
|
||||
.switcher { position: relative; }
|
||||
|
||||
/* ── FAB trigger (bottom-right) ── */
|
||||
.fab-trigger {
|
||||
position: fixed;
|
||||
bottom: 24px; right: 24px;
|
||||
width: 48px; height: 48px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--rs-bg-surface);
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.25), 0 0 0 1px var(--rs-border);
|
||||
cursor: pointer;
|
||||
z-index: 10002;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
animation: fab-pulse 3s ease-in-out infinite;
|
||||
.trigger {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 6px 14px; border-radius: 8px; border: none;
|
||||
font-size: 0.9rem; font-weight: 600; cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
background: var(--rs-btn-secondary-bg); color: var(--rs-text-primary);
|
||||
white-space: nowrap; min-width: 0; flex-shrink: 1;
|
||||
overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.fab-trigger:hover {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.3), 0 0 0 2px var(--rs-accent, #6366f1);
|
||||
animation: none;
|
||||
}
|
||||
.fab-trigger:active { transform: scale(0.95); }
|
||||
.trigger:hover, .trigger:active { background: var(--rs-bg-hover); }
|
||||
|
||||
@keyframes fab-pulse {
|
||||
0%, 100% { box-shadow: 0 2px 12px rgba(0,0,0,0.25), 0 0 0 1px var(--rs-border); }
|
||||
50% { box-shadow: 0 2px 12px rgba(0,0,0,0.25), 0 0 0 3px rgba(99,102,241,0.25); }
|
||||
.trigger-badge {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 22px; height: 22px; border-radius: 5px;
|
||||
font-size: 0.65rem; font-weight: 900; color: #1e293b;
|
||||
line-height: 1; flex-shrink: 0; white-space: nowrap;
|
||||
}
|
||||
|
||||
.fab-badge {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
width: 32px; height: 32px; border-radius: 8px;
|
||||
font-size: 0.8rem; font-weight: 900; color: #1e293b;
|
||||
line-height: 1; white-space: nowrap;
|
||||
}
|
||||
.fab-badge.rstack-gradient {
|
||||
.trigger-badge.rstack-gradient {
|
||||
background: linear-gradient(135deg, #67e8f9, #c4b5fd, #fda4af);
|
||||
}
|
||||
|
||||
/* ── Backdrop ── */
|
||||
.panel-backdrop {
|
||||
.caret { font-size: 0.7em; opacity: 0.6; }
|
||||
|
||||
/* ── Sidebar backdrop (mobile) ── */
|
||||
.sidebar-backdrop {
|
||||
display: none; position: fixed; inset: 0; z-index: 10000;
|
||||
background: rgba(0,0,0,0.4); opacity: 0; transition: opacity 0.25s ease;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.panel-backdrop.visible { display: block; opacity: 1; }
|
||||
.sidebar-backdrop.visible { display: block; opacity: 1; }
|
||||
}
|
||||
|
||||
/* ── Upward panel (above FAB) ── */
|
||||
.panel {
|
||||
/* ── Sidebar panel ── */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
bottom: 80px; right: 24px;
|
||||
top: 56px; left: 0; bottom: 0;
|
||||
width: 280px;
|
||||
max-height: calc(100vh - 120px);
|
||||
overflow-y: auto;
|
||||
touch-action: pan-y;
|
||||
z-index: 10001;
|
||||
background: var(--rs-bg-surface);
|
||||
border: 1px solid var(--rs-border);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
border-right: 1px solid var(--rs-border);
|
||||
box-shadow: var(--rs-shadow-lg);
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
.panel.open {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Collapse button */
|
||||
.collapse-btn {
|
||||
position: absolute;
|
||||
top: 8px; right: 8px;
|
||||
width: 28px; height: 28px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--rs-border);
|
||||
background: var(--rs-bg-surface);
|
||||
color: var(--rs-text-secondary);
|
||||
font-size: 1rem; line-height: 1;
|
||||
cursor: pointer;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
z-index: 1;
|
||||
}
|
||||
.collapse-btn:hover, .collapse-btn:active {
|
||||
background: var(--rs-bg-hover);
|
||||
color: var(--rs-text-primary);
|
||||
}
|
||||
|
||||
/* rStack header */
|
||||
|
|
@ -654,7 +666,6 @@ a.rstack-header {
|
|||
padding: 12px 14px; border-bottom: 1px solid var(--rs-border-subtle);
|
||||
text-decoration: none; color: inherit; cursor: pointer;
|
||||
transition: background 0.12s;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
a.rstack-header:hover, a.rstack-header:active { background: var(--rs-bg-hover); }
|
||||
.rstack-badge {
|
||||
|
|
@ -672,7 +683,6 @@ a.rstack-header:hover, a.rstack-header:active { background: var(--rs-bg-hover);
|
|||
.rstack-footer {
|
||||
padding: 10px 14px; text-align: center;
|
||||
border-top: 1px solid var(--rs-border-subtle);
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
.rstack-footer a {
|
||||
font-size: 0.7rem; opacity: 0.4; text-decoration: none; color: inherit;
|
||||
|
|
@ -779,20 +789,8 @@ a.rstack-header:hover, a.rstack-header:active { background: var(--rs-bg-hover);
|
|||
}
|
||||
.catalog-btn--remove:hover { border-color: #ef4444; background: rgba(239,68,68,0.1); }
|
||||
|
||||
/* ── Mobile (< 640px) ── */
|
||||
/* Mobile: sidebar overlays instead of pushing */
|
||||
@media (max-width: 640px) {
|
||||
.fab-trigger {
|
||||
width: 40px; height: 40px;
|
||||
bottom: 16px; right: 16px;
|
||||
}
|
||||
.fab-badge {
|
||||
width: 26px; height: 26px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.panel {
|
||||
bottom: 64px; right: 16px;
|
||||
width: calc(100vw - 32px);
|
||||
max-height: calc(100vh - 100px);
|
||||
}
|
||||
.sidebar { box-shadow: 4px 0 20px rgba(0,0,0,0.3); }
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -422,7 +422,33 @@ body.rspace-banner-visible #app {
|
|||
opacity: 0.88;
|
||||
}
|
||||
|
||||
/* (sidebar push offsets removed — app switcher is now a floating FAB panel) */
|
||||
/* ── Sidebar push offsets ── */
|
||||
|
||||
rstack-user-dashboard,
|
||||
.rspace-iframe-wrap,
|
||||
#toolbar {
|
||||
transition: margin-left 0.25s ease, left 0.25s ease;
|
||||
}
|
||||
|
||||
body.rstack-sidebar-open .rstack-tab-row {
|
||||
left: 280px;
|
||||
}
|
||||
|
||||
body.rstack-sidebar-open #app {
|
||||
margin-left: 280px;
|
||||
}
|
||||
|
||||
body.rstack-sidebar-open rstack-user-dashboard {
|
||||
left: 280px;
|
||||
}
|
||||
|
||||
body.rstack-sidebar-open .rspace-iframe-wrap {
|
||||
left: 280px;
|
||||
}
|
||||
|
||||
body.rstack-sidebar-open #toolbar {
|
||||
left: 292px; /* 280px sidebar + 12px original margin */
|
||||
}
|
||||
|
||||
/* ── Mobile adjustments ── */
|
||||
|
||||
|
|
@ -503,6 +529,12 @@ body.rspace-banner-visible #app {
|
|||
/* Hide settings/history on mobile — accessible via identity dropdown */
|
||||
.rstack-header__settings-btn,
|
||||
.rstack-header__history-btn { display: none; }
|
||||
/* Sidebar overlays on mobile — no push offsets */
|
||||
body.rstack-sidebar-open .rstack-tab-row { left: 0; }
|
||||
body.rstack-sidebar-open #app { margin-left: 0; }
|
||||
body.rstack-sidebar-open rstack-user-dashboard { left: 0; }
|
||||
body.rstack-sidebar-open .rspace-iframe-wrap { left: 0; }
|
||||
body.rstack-sidebar-open #toolbar { left: 12px; }
|
||||
.rapp-nav__btn, .rapp-nav__back { min-height: 36px; }
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue