feat: move dark/light mode toggle to user profile dropdown
Replaced the dark mode checkbox in the My Account modal with a sun/moon toggle directly in the profile dropdown menu. Sun on the left, moon on the right, with a colored slider (amber for light, indigo for dark). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7a11936483
commit
f5260e0817
|
|
@ -342,6 +342,15 @@ export class RStackIdentity extends HTMLElement {
|
||||||
<button class="dropdown-item" data-action="my-account">👤 My Account</button>
|
<button class="dropdown-item" data-action="my-account">👤 My Account</button>
|
||||||
<button class="dropdown-item" data-action="my-spaces">🌐 My Spaces</button>
|
<button class="dropdown-item" data-action="my-spaces">🌐 My Spaces</button>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
|
<div class="dropdown-theme-row">
|
||||||
|
<span class="theme-icon">☀️</span>
|
||||||
|
<label class="theme-toggle">
|
||||||
|
<input type="checkbox" id="dropdown-theme-toggle" ${(localStorage.getItem("canvas-theme") || "dark") === "dark" ? "checked" : ""} />
|
||||||
|
<span class="theme-slider"></span>
|
||||||
|
</label>
|
||||||
|
<span class="theme-icon">🌙</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
<button class="dropdown-item dropdown-item--danger" data-action="signout">🚪 Sign Out</button>
|
<button class="dropdown-item dropdown-item--danger" data-action="signout">🚪 Sign Out</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -374,6 +383,17 @@ export class RStackIdentity extends HTMLElement {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dropdownTheme = this.#shadow.getElementById("dropdown-theme-toggle") as HTMLInputElement;
|
||||||
|
if (dropdownTheme) {
|
||||||
|
dropdownTheme.addEventListener("click", (e) => e.stopPropagation());
|
||||||
|
dropdownTheme.addEventListener("change", () => {
|
||||||
|
const newTheme = dropdownTheme.checked ? "dark" : "light";
|
||||||
|
localStorage.setItem("canvas-theme", newTheme);
|
||||||
|
document.documentElement.setAttribute("data-theme", newTheme);
|
||||||
|
this.dispatchEvent(new CustomEvent("theme-change", { bubbles: true, composed: true, detail: { theme: newTheme } }));
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.#shadow.innerHTML = `
|
this.#shadow.innerHTML = `
|
||||||
<style>${STYLES}</style>
|
<style>${STYLES}</style>
|
||||||
|
|
@ -676,17 +696,7 @@ export class RStackIdentity extends HTMLElement {
|
||||||
}</div>
|
}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="account-section account-section--inline">
|
<div class="error" id="acct-error"></div>
|
||||||
<div class="account-section-header">
|
|
||||||
<span>🌙 Dark Mode</span>
|
|
||||||
<label class="toggle-switch">
|
|
||||||
<input type="checkbox" id="acct-theme-toggle" ${isDark ? "checked" : ""} />
|
|
||||||
<span class="toggle-slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="error" id="acct-error"></div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
attachListeners();
|
attachListeners();
|
||||||
|
|
@ -1150,18 +1160,6 @@ export class RStackIdentity extends HTMLElement {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dark Mode toggle
|
|
||||||
const themeToggle = overlay.querySelector("#acct-theme-toggle") as HTMLInputElement;
|
|
||||||
if (themeToggle) {
|
|
||||||
themeToggle.addEventListener("change", (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const newTheme = themeToggle.checked ? "dark" : "light";
|
|
||||||
localStorage.setItem("canvas-theme", newTheme);
|
|
||||||
document.documentElement.setAttribute("data-theme", newTheme);
|
|
||||||
this.dispatchEvent(new CustomEvent("theme-change", { bubbles: true, composed: true, detail: { theme: newTheme } }));
|
|
||||||
this.#render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
|
|
@ -1368,6 +1366,30 @@ const STYLES = `
|
||||||
.dropdown-divider { height: 1px; margin: 4px 0; }
|
.dropdown-divider { height: 1px; margin: 4px 0; }
|
||||||
.dropdown-divider { background: var(--rs-border-subtle); }
|
.dropdown-divider { background: var(--rs-border-subtle); }
|
||||||
|
|
||||||
|
/* Theme toggle in dropdown */
|
||||||
|
.dropdown-theme-row {
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
gap: 10px; padding: 8px 16px;
|
||||||
|
}
|
||||||
|
.theme-icon { font-size: 0.9rem; line-height: 1; }
|
||||||
|
.theme-toggle {
|
||||||
|
position: relative; width: 40px; height: 22px;
|
||||||
|
display: inline-block; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.theme-toggle input { opacity: 0; width: 0; height: 0; }
|
||||||
|
.theme-slider {
|
||||||
|
position: absolute; inset: 0; border-radius: 11px;
|
||||||
|
background: #fbbf24; cursor: pointer; transition: background 0.25s;
|
||||||
|
}
|
||||||
|
.theme-slider::before {
|
||||||
|
content: ""; position: absolute;
|
||||||
|
width: 18px; height: 18px; border-radius: 50%;
|
||||||
|
left: 2px; bottom: 2px; background: white;
|
||||||
|
transition: transform 0.25s;
|
||||||
|
}
|
||||||
|
.theme-toggle input:checked + .theme-slider { background: #6366f1; }
|
||||||
|
.theme-toggle input:checked + .theme-slider::before { transform: translateX(18px); }
|
||||||
|
|
||||||
/* Avatar wrapper + notification badge */
|
/* Avatar wrapper + notification badge */
|
||||||
.avatar-wrap { position: relative; }
|
.avatar-wrap { position: relative; }
|
||||||
.notif-badge {
|
.notif-badge {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue