feat: color-coded visibility badges and contextual space display names

- Visibility badges now color-coded: green (public), yellow (permissioned),
  red (private) with matching card border tints
- Public spaces show eye icon instead of lock
- Private spaces display as "username's (you)rSpace" instead of raw name
- Applied consistently across space switcher dropdown and My Spaces modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-01 10:42:06 -08:00
parent aeb9247f96
commit c7674ac526
2 changed files with 40 additions and 11 deletions

View File

@ -926,23 +926,36 @@ export class RStackIdentity extends HTMLElement {
overlay.addEventListener("click", (e) => { if (e.target === overlay) close(); });
};
const visIcon = (v: string) =>
v === "members_only" ? "🔒" : v === "authenticated" ? "🔑" : "🔓";
const visInfo = (v: string) =>
v === "members_only" ? { icon: "🔒", cls: "vis-private", label: "private" }
: v === "authenticated" ? { icon: "🔑", cls: "vis-permissioned", label: "permissioned" }
: { icon: "👁", cls: "vis-public", label: "public" };
const displayName = (s: any) => {
const v = s.visibility || "public_read";
if (v === "members_only") {
const username = getUsername();
return username ? `${username}'s (you)rSpace` : "(you)rSpace";
}
return `${(s.name || s.slug).replace(/</g, "&lt;")} rSpace`;
};
const renderSpaces = (spaces: any[]) => {
const yourSpaces = spaces.filter((s) => s.role);
const publicSpaces = spaces.filter((s) => !s.role && s.accessible);
const cardHTML = (s: any) => `
<button class="space-card" data-slug="${s.slug}">
const cardHTML = (s: any) => {
const vis = visInfo(s.visibility || "public_read");
return `
<button class="space-card ${vis.cls}" data-slug="${s.slug}">
<div class="space-card-initial">${(s.name || s.slug).charAt(0).toUpperCase()}</div>
<div class="space-card-name">${(s.name || s.slug).replace(/</g, "&lt;")}</div>
<div class="space-card-name">${displayName(s)}</div>
<div class="space-card-meta">
<span class="space-vis">${visIcon(s.visibility)} ${s.visibility.replace(/_/g, " ")}</span>
<span class="space-vis ${vis.cls}">${vis.icon} ${vis.label}</span>
${s.role ? `<span class="space-role">${s.role}</span>` : ""}
</div>
</button>
`;
`;};
const yourSection = yourSpaces.length
? `<div class="spaces-section-label">Your Spaces</div>
@ -1290,6 +1303,12 @@ const SPACES_STYLES = `
font-size: 0.7rem; color: #94a3b8; background: rgba(255,255,255,0.06);
padding: 2px 8px; border-radius: 10px;
}
.space-vis.vis-public { background: rgba(52,211,153,0.15); color: #34d399; }
.space-vis.vis-private { background: rgba(248,113,113,0.15); color: #f87171; }
.space-vis.vis-permissioned { background: rgba(251,191,36,0.15); color: #fbbf24; }
.space-card.vis-public { border-color: rgba(52,211,153,0.3); }
.space-card.vis-private { border-color: rgba(248,113,113,0.3); }
.space-card.vis-permissioned { border-color: rgba(251,191,36,0.3); }
.space-role {
font-size: 0.7rem; color: #06b6d4; background: rgba(6,182,212,0.1);
padding: 2px 8px; border-radius: 10px; font-weight: 600;

View File

@ -118,7 +118,17 @@ export class RStackSpaceSwitcher extends HTMLElement {
const v = s.visibility || "public_read";
if (v === "members_only") return { cls: "vis-private", label: "🔒" };
if (v === "authenticated") return { cls: "vis-permissioned", label: "🔑" };
return { cls: "vis-public", label: "🔓" };
return { cls: "vis-public", label: "👁" };
}
/** Format display name based on visibility type */
#displayName(s: SpaceInfo): string {
const v = s.visibility || "public_read";
if (v === "members_only") {
const username = getUsername();
return username ? `${username}'s (you)rSpace` : "(you)rSpace";
}
return `${s.name} rSpace`;
}
#renderMenu(menu: HTMLElement, current: string) {
@ -178,7 +188,7 @@ export class RStackSpaceSwitcher extends HTMLElement {
<div class="item-row ${vis.cls} ${s.slug === current ? "active" : ""}">
<a class="item" href="${rspaceNavUrl(s.slug, moduleId)}">
<span class="item-icon">${s.icon || "🌐"}</span>
<span class="item-name">${s.name}</span>
<span class="item-name">${this.#displayName(s)}</span>
<span class="item-vis ${vis.cls}">${vis.label}</span>
</a>${canEdit ? `<button class="item-gear" data-edit-slug="${s.slug}" data-edit-name="${s.name.replace(/"/g, "&quot;")}" title="Edit Space">⚙</button>` : ""}
</div>`;
@ -197,7 +207,7 @@ export class RStackSpaceSwitcher extends HTMLElement {
<a class="item ${vis.cls} ${s.slug === current ? "active" : ""}"
href="${rspaceNavUrl(s.slug, moduleId)}">
<span class="item-icon">${s.icon || "🌐"}</span>
<span class="item-name">${s.name}</span>
<span class="item-name">${this.#displayName(s)}</span>
<span class="item-vis ${vis.cls}">${vis.label}</span>
</a>`;
})
@ -215,7 +225,7 @@ export class RStackSpaceSwitcher extends HTMLElement {
return `
<div class="item item--discover ${vis.cls}">
<span class="item-icon">${s.icon || "🌐"}</span>
<span class="item-name">${s.name}</span>
<span class="item-name">${this.#displayName(s)}</span>
<span class="item-vis ${vis.cls}">${vis.label}</span>
${pending
? `<span class="item-badge item-badge--pending">Requested</span>`