From 5c35080e22210e99c381bbec3151b6aa585d4d84 Mon Sep 17 00:00:00 2001 From: Bobban Rydh Date: Sat, 19 Sep 2026 01:51:59 +0200 Subject: [PATCH] Add a dark mode toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Personal, per-browser preference (localStorage), not an admin-wide Display setting like date/time format — every role can pick their own. Sets data-bs-theme on to hook into Tabler's built-in dark variant, applied via an inline script in index.html before React mounts so there's no light-mode flash on load. Toggle button lives in the sidebar footer next to Sign out. Co-Authored-By: Claude Sonnet 5 --- web/index.html | 14 ++++++++++++++ web/src/layout/AppShell.tsx | 18 ++++++++++++++++++ web/src/utils/theme.ts | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 web/src/utils/theme.ts diff --git a/web/index.html b/web/index.html index a5fa766..60151f2 100644 --- a/web/index.html +++ b/web/index.html @@ -4,6 +4,20 @@ Homelab Manager +
diff --git a/web/src/layout/AppShell.tsx b/web/src/layout/AppShell.tsx index 4d36ae2..39eddd1 100644 --- a/web/src/layout/AppShell.tsx +++ b/web/src/layout/AppShell.tsx @@ -18,8 +18,11 @@ import { IconStethoscope, IconSettings, IconMenu2, + IconSun, + IconMoon, } from "@tabler/icons-react"; import type { CurrentUser } from "../api/client"; +import { getTheme, setTheme, type Theme } from "../utils/theme"; interface NavItem { to: string; @@ -51,10 +54,17 @@ const roleRank: Record = { viewer: 0, operator: 1, export default function AppShell({ user, children }: { user: CurrentUser; children: ReactNode }) { const [sidebarOpen, setSidebarOpen] = useState(false); + const [theme, setThemeState] = useState(() => getTheme()); const visibleItems = NAV_ITEMS.filter( (item) => !item.minRole || roleRank[user.role] >= roleRank[item.minRole], ); + function toggleTheme() { + const next: Theme = theme === "dark" ? "light" : "dark"; + setTheme(next); + setThemeState(next); + } + return (