Fixes light dark mode toggle closes #429 #434

Merged
carla merged 3 commits from bug/429_light_dark_mode into main 2026-02-23 16:10:24 +01:00
4 changed files with 66 additions and 25 deletions
Showing only changes of commit cbed65de66 - Show all commits

View file

@ -24,7 +24,7 @@
@plugin "../vendor/daisyui-theme" {
name: "dark";
default: false;
prefersdark: true;
prefersdark: false;
color-scheme: "dark";
--color-base-100: oklch(30.33% 0.016 252.42);
--color-base-200: oklch(25.26% 0.014 253.1);

View file

@ -15,20 +15,56 @@
</script>
<script>
(() => {
const setTheme = (theme) => {
if (theme === "system") {
localStorage.removeItem("phx:theme");
document.documentElement.removeAttribute("data-theme");
} else {
localStorage.setItem("phx:theme", theme);
document.documentElement.setAttribute("data-theme", theme);
}
const mq = window.matchMedia("(prefers-color-scheme: dark)");
const systemTheme = () => (mq.matches ? "dark" : "light");
// Single source of truth:
// - localStorage["phx:theme"] = "light" | "dark" (explicit override)
// - missing key => "system"
const storedTheme = () => localStorage.getItem("phx:theme") || "system";
const effectiveTheme = (t) => (t === "system" ? systemTheme() : t);
const applyThemeNow = (t) => {
document.documentElement.setAttribute("data-theme", effectiveTheme(t));
};
if (!document.documentElement.hasAttribute("data-theme")) {
setTheme(localStorage.getItem("phx:theme") || "system");
}
window.addEventListener("storage", (e) => e.key === "phx:theme" && setTheme(e.newValue || "system"));
const syncToggle = () => {
const eff = effectiveTheme(storedTheme());
document.querySelectorAll("[data-theme-toggle]").forEach((el) => {
el.checked = eff === "dark";
});
};
const setTheme = (t) => {
if (t === "system") localStorage.removeItem("phx:theme");
else localStorage.setItem("phx:theme", t);
applyThemeNow(t);
syncToggle(); // if toggle exists already
};
// 1) Apply theme ASAP to match system on first paint
applyThemeNow(storedTheme());
// 2) Sync toggle once DOM is ready (fixes initial "light" toggle)
document.addEventListener("DOMContentLoaded", syncToggle);
// 3) If toggle appears later (LiveView render), sync immediately
const obs = new MutationObserver(() => {
if (document.querySelector("[data-theme-toggle]")) syncToggle();
});
obs.observe(document.documentElement, { childList: true, subtree: true });
window.addEventListener("phx:set-theme", ({ detail: { theme } }) => setTheme(theme));
window.addEventListener("storage", (e) => e.key === "phx:theme" && setTheme(e.newValue || "system"));
mq.addEventListener("change", () => {
if (localStorage.getItem("phx:theme") === null) {
applyThemeNow("system");
syncToggle();
}
});
})();
</script>
</head>

View file

@ -248,12 +248,17 @@ defmodule MvWeb.Layouts.Sidebar do
aria-label={gettext("Toggle dark mode")}
>
<.icon name="hero-sun" class="size-5" aria-hidden="true" />
<div id="theme-toggle" phx-update="ignore">
<input
id="theme-toggle-input"
type="checkbox"
value="dark"
class="toggle toggle-sm theme-controller focus:outline-none"
class="toggle toggle-sm focus:outline-none"
data-theme-toggle
onchange="window.dispatchEvent(new CustomEvent('phx:set-theme',{detail:{theme:this.checked?'dark':'light'}}))"
aria-label={gettext("Toggle dark mode")}
/>
</div>
<.icon name="hero-moon" class="size-5" aria-hidden="true" />
</label>
"""

View file

@ -132,7 +132,7 @@ defmodule MvWeb.Layouts.SidebarTest do
refute html =~ ~s(role="menuitem")
# Footer section should not be rendered
refute html =~ "theme-controller"
refute html =~ "data-theme-toggle"
refute html =~ "locale-select"
end
@ -253,8 +253,8 @@ defmodule MvWeb.Layouts.SidebarTest do
# Check for language selector form
assert html =~ ~s(action="/set_locale")
# Check for theme toggle
assert has_class?(html, "theme-controller")
# Check for theme toggle (using data attribute instead of class)
assert html =~ "data-theme-toggle"
# Check for user menu/avatar
assert has_class?(html, "avatar")
@ -536,7 +536,7 @@ defmodule MvWeb.Layouts.SidebarTest do
assert html =~ ~s(role="group")
# Footer section
assert html =~ "theme-controller"
assert html =~ "data-theme-toggle"
assert html =~ ~s(action="/set_locale")
# Check that critical navigation exists (at least /members)
@ -694,8 +694,8 @@ defmodule MvWeb.Layouts.SidebarTest do
test "renders theme toggle" do
html = render_sidebar(authenticated_assigns())
# Toggle is always visible
assert has_class?(html, "theme-controller")
# Toggle is always visible (using data attribute instead of class)
assert html =~ "data-theme-toggle"
assert html =~ "hero-sun"
assert html =~ "hero-moon"
end