From 1255cee8e677ad71fef753221e5c9d3ce22be5a5 Mon Sep 17 00:00:00 2001 From: plenarius Date: Mon, 20 Apr 2026 22:08:41 -0400 Subject: [PATCH] feat: add dark/light theme toggle with Layonara color palette --- packages/frontend/src/hooks/useTheme.ts | 39 +++++++++++++++++++++ packages/frontend/src/layouts/IDELayout.tsx | 12 ++++++- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 packages/frontend/src/hooks/useTheme.ts diff --git a/packages/frontend/src/hooks/useTheme.ts b/packages/frontend/src/hooks/useTheme.ts new file mode 100644 index 0000000..cc83849 --- /dev/null +++ b/packages/frontend/src/hooks/useTheme.ts @@ -0,0 +1,39 @@ +import { useState, useEffect, useCallback } from "react"; + +type Theme = "dark" | "light"; + +const STORAGE_KEY = "forge-theme"; + +function getInitialTheme(): Theme { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === "light" || stored === "dark") return stored; + } catch { + // localStorage unavailable + } + return "dark"; +} + +export function useTheme() { + const [theme, setTheme] = useState(getInitialTheme); + + useEffect(() => { + const root = document.documentElement; + if (theme === "light") { + root.classList.add("light"); + } else { + root.classList.remove("light"); + } + try { + localStorage.setItem(STORAGE_KEY, theme); + } catch { + // localStorage unavailable + } + }, [theme]); + + const toggleTheme = useCallback(() => { + setTheme((t) => (t === "dark" ? "light" : "dark")); + }, []); + + return { theme, toggleTheme } as const; +} diff --git a/packages/frontend/src/layouts/IDELayout.tsx b/packages/frontend/src/layouts/IDELayout.tsx index 040411f..5bf38c9 100644 --- a/packages/frontend/src/layouts/IDELayout.tsx +++ b/packages/frontend/src/layouts/IDELayout.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; import { Outlet, Link, useLocation } from "react-router-dom"; import { Terminal } from "../components/terminal/Terminal"; import { useWebSocket } from "../hooks/useWebSocket"; +import { useTheme } from "../hooks/useTheme"; const NAV_ITEMS = [ { path: "/", label: "Home" }, @@ -17,6 +18,7 @@ export function IDELayout({ sidebar }: { sidebar?: React.ReactNode }) { const [upstreamBehind, setUpstreamBehind] = useState(0); const location = useLocation(); const { subscribe } = useWebSocket(); + const { theme, toggleTheme } = useTheme(); useEffect(() => { return subscribe("git:upstream-update", (event) => { @@ -48,7 +50,7 @@ export function IDELayout({ sidebar }: { sidebar?: React.ReactNode }) { > Layonara Forge -