feat: always-visible tab close buttons + rApp dropdown shows open apps
- Tab close (×) buttons now visible at 35% opacity instead of hidden, brightening on hover so users can see they're clickable - [+] dropdown now shows all rApps including already-open ones - Already-open rApps shown dimmed with a cyan dot indicator - Clicking an open rApp surfaces it (switches tab) instead of duplicating Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b0eebc4cbc
commit
29d49c7b26
|
|
@ -282,21 +282,20 @@ export class RStackTabBar extends HTMLElement {
|
|||
const existingModuleIds = new Set(this.#layers.map(l => l.moduleId));
|
||||
|
||||
// Use server module list if available, fall back to MODULE_BADGES keys
|
||||
const availableModules: Array<{ id: string; name: string; icon: string; description: string }> =
|
||||
const allModules: Array<{ id: string; name: string; icon: string; description: string }> =
|
||||
this.#modules.length > 0
|
||||
? this.#modules.filter(m => !existingModuleIds.has(m.id))
|
||||
? this.#modules
|
||||
: Object.keys(MODULE_BADGES)
|
||||
.filter(id => !existingModuleIds.has(id))
|
||||
.map(id => ({ id, name: id, icon: "", description: "" }));
|
||||
|
||||
if (availableModules.length === 0) {
|
||||
return `<div class="add-menu" id="add-menu"><div class="add-menu-empty">All rApps added</div></div>`;
|
||||
if (allModules.length === 0) {
|
||||
return `<div class="add-menu" id="add-menu"><div class="add-menu-empty">No rApps available</div></div>`;
|
||||
}
|
||||
|
||||
// Group by category
|
||||
const groups = new Map<string, typeof availableModules>();
|
||||
const uncategorized: typeof availableModules = [];
|
||||
for (const m of availableModules) {
|
||||
const groups = new Map<string, typeof allModules>();
|
||||
const uncategorized: typeof allModules = [];
|
||||
for (const m of allModules) {
|
||||
const cat = MODULE_CATEGORIES[m.id];
|
||||
if (cat) {
|
||||
if (!groups.has(cat)) groups.set(cat, []);
|
||||
|
|
@ -311,29 +310,33 @@ export class RStackTabBar extends HTMLElement {
|
|||
const items = groups.get(cat);
|
||||
if (!items || items.length === 0) continue;
|
||||
html += `<div class="add-menu-category">${cat}</div>`;
|
||||
html += items.map(m => this.#renderAddMenuItem(m)).join("");
|
||||
html += items.map(m => this.#renderAddMenuItem(m, existingModuleIds.has(m.id))).join("");
|
||||
}
|
||||
if (uncategorized.length > 0) {
|
||||
html += `<div class="add-menu-category">Other</div>`;
|
||||
html += uncategorized.map(m => this.#renderAddMenuItem(m)).join("");
|
||||
html += uncategorized.map(m => this.#renderAddMenuItem(m, existingModuleIds.has(m.id))).join("");
|
||||
}
|
||||
|
||||
return `<div class="add-menu" id="add-menu">${html}</div>`;
|
||||
}
|
||||
|
||||
#renderAddMenuItem(m: { id: string; name: string; icon: string; description: string }): string {
|
||||
#renderAddMenuItem(m: { id: string; name: string; icon: string; description: string }, isOpen: boolean): string {
|
||||
const badge = MODULE_BADGES[m.id];
|
||||
const badgeHtml = badge
|
||||
? `<span class="add-menu-badge" style="background:${badge.color}">${badge.badge}</span>`
|
||||
: `<span class="add-menu-icon">${m.icon}</span>`;
|
||||
|
||||
const openClass = isOpen ? " add-menu-item--open" : "";
|
||||
const openIndicator = isOpen ? `<span class="add-menu-open-dot" title="Already open">●</span>` : "";
|
||||
|
||||
return `
|
||||
<button class="add-menu-item" data-add-module="${m.id}">
|
||||
<button class="add-menu-item${openClass}" data-add-module="${m.id}" data-module-open="${isOpen}">
|
||||
${badgeHtml}
|
||||
<div class="add-menu-text">
|
||||
<span class="add-menu-name">${m.name} <span class="add-menu-emoji">${m.icon}</span></span>
|
||||
${m.description ? `<span class="add-menu-desc">${m.description}</span>` : ""}
|
||||
</div>
|
||||
${openIndicator}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
|
@ -615,11 +618,25 @@ export class RStackTabBar extends HTMLElement {
|
|||
item.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const moduleId = item.dataset.addModule!;
|
||||
const isOpen = item.dataset.moduleOpen === "true";
|
||||
this.#addMenuOpen = false;
|
||||
this.dispatchEvent(new CustomEvent("layer-add", {
|
||||
detail: { moduleId },
|
||||
bubbles: true,
|
||||
}));
|
||||
|
||||
if (isOpen) {
|
||||
// Surface existing tab instead of adding a duplicate
|
||||
const existing = this.#layers.find(l => l.moduleId === moduleId);
|
||||
if (existing) {
|
||||
this.setAttribute("active", existing.id);
|
||||
this.dispatchEvent(new CustomEvent("layer-switch", {
|
||||
detail: { layerId: existing.id, moduleId },
|
||||
bubbles: true,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
this.dispatchEvent(new CustomEvent("layer-add", {
|
||||
detail: { moduleId },
|
||||
bubbles: true,
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -984,12 +1001,12 @@ const STYLES = `
|
|||
color: inherit;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
opacity: 0.35;
|
||||
transition: opacity 0.15s, background 0.15s;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
.tab:hover .tab-close { opacity: 0.5; }
|
||||
.tab:hover .tab-close { opacity: 0.6; }
|
||||
.tab-close:hover { opacity: 1 !important; background: rgba(239,68,68,0.2); color: #ef4444; }
|
||||
|
||||
/* ── Drag states ── */
|
||||
|
|
@ -1128,6 +1145,17 @@ const STYLES = `
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.add-menu-item--open {
|
||||
opacity: 0.55;
|
||||
}
|
||||
.add-menu-open-dot {
|
||||
margin-left: auto;
|
||||
font-size: 0.5rem;
|
||||
color: #22d3ee;
|
||||
flex-shrink: 0;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.add-menu-empty {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
|
|
|
|||
Loading…
Reference in New Issue