/** * — Dropdown to switch between rSpace modules. * * Attributes: * current — the active module ID (highlighted) * * Methods: * setModules(list) — provide the list of available modules */ export interface AppSwitcherModule { id: string; name: string; icon: string; description: string; } export class RStackAppSwitcher extends HTMLElement { #shadow: ShadowRoot; #modules: AppSwitcherModule[] = []; constructor() { super(); this.#shadow = this.attachShadow({ mode: "open" }); } static get observedAttributes() { return ["current"]; } get current(): string { return this.getAttribute("current") || ""; } connectedCallback() { this.#render(); } attributeChangedCallback() { this.#render(); } setModules(modules: AppSwitcherModule[]) { this.#modules = modules; this.#render(); } #render() { const current = this.current; const currentMod = this.#modules.find((m) => m.id === current); const label = currentMod ? `${currentMod.icon} ${currentMod.name}` : "🌌 rSpace"; this.#shadow.innerHTML = `
`; const trigger = this.#shadow.getElementById("trigger")!; const menu = this.#shadow.getElementById("menu")!; trigger.addEventListener("click", (e) => { e.stopPropagation(); menu.classList.toggle("open"); }); document.addEventListener("click", () => menu.classList.remove("open")); } #getSpaceSlug(): string { // Read from the space switcher or URL const spaceSwitcher = document.querySelector("rstack-space-switcher"); if (spaceSwitcher) return spaceSwitcher.getAttribute("current") || "personal"; // Fallback: parse from URL (/:space/:module) const parts = window.location.pathname.split("/").filter(Boolean); return parts[0] || "personal"; } static define(tag = "rstack-app-switcher") { if (!customElements.get(tag)) customElements.define(tag, RStackAppSwitcher); } } const STYLES = ` :host { display: contents; } .switcher { position: relative; } .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: rgba(255,255,255,0.08); color: inherit; } :host-context([data-theme="light"]) .trigger { background: rgba(0,0,0,0.05); color: #0f172a; } :host-context([data-theme="dark"]) .trigger { background: rgba(255,255,255,0.08); color: #e2e8f0; } .trigger:hover { background: rgba(255,255,255,0.12); } :host-context([data-theme="light"]) .trigger:hover { background: rgba(0,0,0,0.08); } .caret { font-size: 0.7em; opacity: 0.6; } .menu { position: absolute; top: 100%; left: 0; margin-top: 6px; min-width: 260px; border-radius: 12px; overflow: hidden; box-shadow: 0 8px 30px rgba(0,0,0,0.25); display: none; z-index: 200; } .menu.open { display: block; } :host-context([data-theme="light"]) .menu { background: white; border: 1px solid rgba(0,0,0,0.1); } :host-context([data-theme="dark"]) .menu { background: #1e293b; border: 1px solid rgba(255,255,255,0.1); } .item { display: flex; align-items: center; gap: 12px; padding: 10px 14px; text-decoration: none; transition: background 0.12s; cursor: pointer; } :host-context([data-theme="light"]) .item { color: #374151; } :host-context([data-theme="light"]) .item:hover { background: #f1f5f9; } :host-context([data-theme="light"]) .item.active { background: #e0f2fe; } :host-context([data-theme="dark"]) .item { color: #e2e8f0; } :host-context([data-theme="dark"]) .item:hover { background: rgba(255,255,255,0.05); } :host-context([data-theme="dark"]) .item.active { background: rgba(6,182,212,0.1); } .item-icon { font-size: 1.3rem; width: 28px; text-align: center; flex-shrink: 0; } .item-text { display: flex; flex-direction: column; min-width: 0; } .item-name { font-size: 0.875rem; font-weight: 600; } .item-desc { font-size: 0.75rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } `;