mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 11:07:04 +00:00
feat(theme): persistent light/dark toggle in the titlebar
This commit is contained in:
1 parent
a5b3c0c0e3
commit
9991d318ba
5 files changed
+61
-3
No files matched your search
@@ -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<TiptapEditor | null>(null);
|
||||
const [dock, setDock] = useState(true);
|
||||
@@ -99,6 +102,16 @@ export function EditorView() {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<button className="icon-btn" onClick={toggleTheme} title={theme === "dark" ? "Light mode" : "Dark mode"}>
|
||||
{theme === "dark" ? (
|
||||
<Icon>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M19.1 4.9l-1.4 1.4M6.3 17.7l-1.4 1.4" />
|
||||
</Icon>
|
||||
) : (
|
||||
<Icon d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
||||
)}
|
||||
</button>
|
||||
<button className="icon-btn" data-on={dock} onClick={() => setDock(!dock)} title="Toggle preview">
|
||||
<Icon d="M3 4.5h18v15H3zM14 4.5v15" />
|
||||
</button>
|
||||
|
||||
@@ -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 (
|
||||
<svg
|
||||
width={size}
|
||||
@@ -15,7 +18,7 @@ export function Icon({ d, size = 16 }: IconProps) {
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d={d} />
|
||||
{children ?? <path d={d} />}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -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<ThemeState>((set, get) => ({
|
||||
theme: initialTheme(),
|
||||
toggle: () => {
|
||||
const next: Theme = get().theme === "dark" ? "light" : "dark";
|
||||
applyTheme(next);
|
||||
set({ theme: next });
|
||||
},
|
||||
}));
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user