From 9991d318ba502d7556160521763e6f247ae9013f Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 20 Jun 2026 18:46:43 -0400 Subject: [PATCH] feat(theme): persistent light/dark toggle in the titlebar --- index.html | 10 ++++++++++ src/components/EditorView.tsx | 13 +++++++++++++ src/components/Icon.tsx | 9 ++++++--- src/store/useTheme.ts | 16 ++++++++++++++++ src/theme.ts | 16 ++++++++++++++++ 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/store/useTheme.ts create mode 100644 src/theme.ts diff --git a/index.html b/index.html index 58f06d7..b3f7687 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,16 @@ + margin diff --git a/src/components/EditorView.tsx b/src/components/EditorView.tsx index 3c38e9e..b5173be 100644 --- a/src/components/EditorView.tsx +++ b/src/components/EditorView.tsx @@ -7,6 +7,7 @@ import { Settings } from "./Settings"; import { Editor } from "../editor/Editor"; import { FloatingToolbar } from "../editor/FloatingToolbar"; import { useBook } from "../store/useBook"; +import { useTheme } from "../store/useTheme"; import type { Book } from "../model/book"; import { saveBook } from "../library"; import { isDesktop } from "../ipc"; @@ -22,6 +23,8 @@ export function EditorView() { const setChapterContent = useBook((s) => s.setChapterContent); const setChapterTitle = useBook((s) => s.setChapterTitle); const markSaved = useBook((s) => s.markSaved); + const theme = useTheme((s) => s.theme); + const toggleTheme = useTheme((s) => s.toggle); const [editor, setEditor] = useState(null); const [dock, setDock] = useState(true); @@ -99,6 +102,16 @@ export function EditorView() { )} + diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index dbcd7ff..bc60a57 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -1,9 +1,12 @@ +import type { ReactNode } from "react"; + interface IconProps { - d: string; + d?: string; size?: number; + children?: ReactNode; } -export function Icon({ d, size = 16 }: IconProps) { +export function Icon({ d, size = 16, children }: IconProps) { return ( - + {children ?? } ); } diff --git a/src/store/useTheme.ts b/src/store/useTheme.ts new file mode 100644 index 0000000..e96818f --- /dev/null +++ b/src/store/useTheme.ts @@ -0,0 +1,16 @@ +import { create } from "zustand"; +import { applyTheme, initialTheme, type Theme } from "../theme"; + +interface ThemeState { + theme: Theme; + toggle: () => void; +} + +export const useTheme = create((set, get) => ({ + theme: initialTheme(), + toggle: () => { + const next: Theme = get().theme === "dark" ? "light" : "dark"; + applyTheme(next); + set({ theme: next }); + }, +})); diff --git a/src/theme.ts b/src/theme.ts new file mode 100644 index 0000000..9f3eab0 --- /dev/null +++ b/src/theme.ts @@ -0,0 +1,16 @@ +export type Theme = "light" | "dark"; + +const KEY = "margin-theme"; + +export function initialTheme(): Theme { + const attr = document.documentElement.getAttribute("data-theme"); + if (attr === "light" || attr === "dark") return attr; + const saved = localStorage.getItem(KEY); + if (saved === "light" || saved === "dark") return saved; + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +} + +export function applyTheme(theme: Theme) { + document.documentElement.setAttribute("data-theme", theme); + localStorage.setItem(KEY, theme); +}