nandi/oripublic Fork 0
35061753be581c0ba47a3189520d64e7788c183d
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

theme.ts · 87 lines · 2.6 KBTypeScript Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1/**
2 * Colour theme of the app: light (the default) or dark. The choice is kept
3 * apart from the chat and workspace state because it is a per-browser
4 * preference, remembered in localStorage and applied to the document as a
5 * `data-theme` attribute that the stylesheet keys its palettes on.
6 *
7 * @example
8 * toggleTheme(); // light → dark, dark → light
9 * useTheme((t) => t.theme); // inside a component
10 * document.documentElement.dataset.theme; // "dark"
11 */
12
13import { create } from "zustand";
14
15export type Theme = "light" | "dark";
16
17/** Theme used until the user picks one. */
18export const DEFAULT_THEME: Theme = "light";
19/** localStorage key under which the chosen theme is remembered. */
20export const THEME_KEY = "ori.theme";
21
22export interface ThemeState {
23 /** The active theme. */
24 theme: Theme;
25}
26
27/** isTheme narrows an arbitrary value (e.g. read from storage) to a Theme. */
28export function isTheme(value: unknown): value is Theme {
29 return value === "light" || value === "dark";
30}
31
32/** React hook over the theme store. */
33export const useTheme = create<ThemeState>(() => ({ theme: loadTheme() }));
34
35/**
36 * setTheme activates a theme, reflects it on the document and remembers it.
37 *
38 * @example
39 * setTheme("dark");
40 * useTheme.getState().theme; // "dark"
41 */
42export function setTheme(theme: Theme): void {
43 useTheme.setState({ theme });
44 applyTheme(theme);
45 saveTheme(theme);
46}
47
48/** toggleTheme switches between light and dark. */
49export function toggleTheme(): void {
50 setTheme(useTheme.getState().theme === "dark" ? "light" : "dark");
51}
52
53/** resetTheme restores the default theme and forgets the stored one; meant for tests. */
54export function resetTheme(): void {
55 setTheme(DEFAULT_THEME);
56}
57
58/** applyTheme stamps the theme on <html> so the CSS palettes switch. */
59function applyTheme(theme: Theme): void {
60 document.documentElement.dataset.theme = theme;
61}
62
63/**
64 * loadTheme reads the remembered theme. Storage can be missing or throw
65 * (private browsing, blocked site data), and the stored value may be stale,
66 * so any failure or unknown value yields the default.
67 */
68function loadTheme(): Theme {
69 try {
70 const stored = globalThis.localStorage?.getItem(THEME_KEY);
71 return isTheme(stored) ? stored : DEFAULT_THEME;
72 } catch {
73 return DEFAULT_THEME;
74 }
75}
76
77function saveTheme(theme: Theme): void {
78 try {
79 globalThis.localStorage?.setItem(THEME_KEY, theme);
80 } catch {
81 // Storage unavailable: the theme still applies for this page.
82 }
83}
84
85// Stamp the document as soon as the module loads, before React renders, so
86// the first paint already uses the remembered theme.
87applyTheme(useTheme.getState().theme);