feat: add My Account submenu with dark mode + encrypted backup toggles

Reorganize user dropdown into expandable "My Account" submenu containing
account actions (Add Email, Add Device, Recovery) plus Dark Mode and
Encrypted Backup toggle switches. Move theme toggle from toolbar into
account settings, default to dark mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-02 12:45:11 -08:00
parent a402caacd8
commit 5c21b64a99
2 changed files with 102 additions and 13 deletions

View File

@ -412,12 +412,31 @@ export class RStackIdentity extends HTMLElement {
<div class="dropdown-header">${displayName}</div>
${notifsHTML}
<div class="dropdown-divider"></div>
<button class="dropdown-item submenu-toggle" data-action="toggle-account">
👤 My Account <span class="submenu-arrow"></span>
</button>
<div class="submenu" id="account-submenu">
<button class="dropdown-item submenu-item" data-action="add-email"> Add Email</button>
<button class="dropdown-item submenu-item" data-action="add-device">📱 Add Second Device</button>
<button class="dropdown-item submenu-item" data-action="add-recovery">🛡 Add Social Recovery</button>
<div class="dropdown-divider"></div>
<div class="dropdown-item submenu-item toggle-row">
🌙 Dark Mode
<label class="toggle-switch">
<input type="checkbox" id="theme-toggle" />
<span class="toggle-slider"></span>
</label>
</div>
<div class="dropdown-item submenu-item toggle-row">
🔒 Encrypted Backup
<label class="toggle-switch">
<input type="checkbox" id="backup-toggle" />
<span class="toggle-slider"></span>
</label>
</div>
</div>
<button class="dropdown-item" data-action="my-spaces">🌐 My Spaces</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item" data-action="add-email"> Add Email</button>
<button class="dropdown-item" data-action="add-device">📱 Add Second Device</button>
<button class="dropdown-item" data-action="add-recovery">🛡 Add Social Recovery</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item dropdown-item--danger" data-action="signout">🚪 Sign Out</button>
</div>
</div>
@ -468,6 +487,13 @@ export class RStackIdentity extends HTMLElement {
el.addEventListener("click", (e) => {
e.stopPropagation();
const action = (el as HTMLElement).dataset.action;
if (action === "toggle-account") {
const submenu = this.#shadow.getElementById("account-submenu")!;
const arrow = (el as HTMLElement).querySelector(".submenu-arrow")!;
submenu.classList.toggle("open");
arrow.textContent = submenu.classList.contains("open") ? "▾" : "▸";
return;
}
dropdown.classList.remove("open");
if (action === "signout") {
clearSession();
@ -486,6 +512,34 @@ export class RStackIdentity extends HTMLElement {
}
});
});
// Theme toggle
const themeToggle = this.#shadow.getElementById("theme-toggle") as HTMLInputElement;
if (themeToggle) {
const currentTheme = localStorage.getItem("canvas-theme") || "dark";
themeToggle.checked = currentTheme === "dark";
themeToggle.addEventListener("change", (e) => {
e.stopPropagation();
const newTheme = themeToggle.checked ? "dark" : "light";
localStorage.setItem("canvas-theme", newTheme);
document.body.setAttribute("data-theme", newTheme);
document.querySelectorAll(".rstack-header, .rstack-tab-row").forEach(el => el.setAttribute("data-theme", newTheme));
this.dispatchEvent(new CustomEvent("theme-change", { bubbles: true, composed: true, detail: { theme: newTheme } }));
this.#render();
});
}
// Backup toggle
const backupToggle = this.#shadow.getElementById("backup-toggle") as HTMLInputElement;
if (backupToggle) {
backupToggle.checked = localStorage.getItem("encryptid_backup_enabled") === "true";
backupToggle.addEventListener("change", (e) => {
e.stopPropagation();
const enabled = backupToggle.checked;
localStorage.setItem("encryptid_backup_enabled", enabled ? "true" : "false");
this.dispatchEvent(new CustomEvent("backup-toggle", { bubbles: true, composed: true, detail: { enabled } }));
});
}
} else {
this.#shadow.innerHTML = `
<style>${STYLES}</style>
@ -1268,6 +1322,45 @@ const STYLES = `
.notif-btn--approve:hover:not(:disabled) { opacity: 0.85; }
.notif-btn--deny { background: rgba(239,68,68,0.15); color: #ef4444; }
.notif-btn--deny:hover:not(:disabled) { background: rgba(239,68,68,0.25); }
/* Submenu accordion */
.submenu {
max-height: 0; overflow: hidden;
transition: max-height 0.2s ease-out;
}
.submenu.open { max-height: 300px; }
.submenu-item { padding-left: 32px !important; font-size: 0.825rem; }
.submenu-toggle { position: relative; }
.submenu-arrow {
position: absolute; right: 16px;
font-size: 0.7rem; transition: transform 0.2s;
}
/* Toggle switch */
.toggle-row {
display: flex; align-items: center;
justify-content: space-between; cursor: default;
}
.toggle-switch {
position: relative; width: 36px; height: 20px;
display: inline-block; flex-shrink: 0;
}
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
position: absolute; inset: 0; border-radius: 10px;
background: rgba(255,255,255,0.15); cursor: pointer;
transition: background 0.2s;
}
.toggle-slider::before {
content: ""; position: absolute;
width: 16px; height: 16px; border-radius: 50%;
left: 2px; bottom: 2px; background: white;
transition: transform 0.2s;
}
.toggle-switch input:checked + .toggle-slider { background: #059669; }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(16px); }
.user.light .toggle-slider { background: rgba(0,0,0,0.15); }
.user.light .toggle-switch input:checked + .toggle-slider { background: #059669; }
`;
const MODAL_STYLES = `

View File

@ -1140,7 +1140,6 @@
<button id="new-feed" title="New Feed from another layer">🔄 Feed</button>
<button id="toggle-memory" title="Forgotten rSpaces">💭 Memory</button>
<button id="toggle-hide-forgotten" title="Hide forgotten items">👁 Hide Faded</button>
<button id="toggle-theme" title="Toggle dark mode">🌙 Dark</button>
<span class="toolbar-sep"></span>
@ -1257,24 +1256,21 @@
if (tb) tb.setModules(moduleList);
}).catch(() => {});
// ── Dark mode toggle ──
// ── Dark mode (default dark, toggled from My Account dropdown) ──
{
const savedTheme = localStorage.getItem("canvas-theme") || "light";
const themeBtn = document.getElementById("toggle-theme");
const savedTheme = localStorage.getItem("canvas-theme") || "dark";
function applyTheme(theme) {
document.body.setAttribute("data-theme", theme);
document.querySelector(".rstack-header")?.setAttribute("data-theme", theme);
document.querySelector(".rstack-tab-row")?.setAttribute("data-theme", theme);
if (themeBtn) themeBtn.textContent = theme === "dark" ? "☀️ Light" : "🌙 Dark";
}
applyTheme(savedTheme);
themeBtn?.addEventListener("click", () => {
const next = document.body.getAttribute("data-theme") === "dark" ? "light" : "dark";
applyTheme(next);
localStorage.setItem("canvas-theme", next);
// Listen for theme changes from rstack-identity component
document.addEventListener("theme-change", (e) => {
applyTheme(e.detail?.theme || "dark");
});
}