theme-watcher 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,6 +40,8 @@ Props:
40
40
  - `storageKey?: string` default: `"theme-watcher"`
41
41
  - `attribute?: "data-theme" | "class"` default: `"data-theme"`
42
42
  - `defaultTheme?: "light" | "dark" | "system"` default: `"system"`
43
+ - `enableColorScheme?: boolean` default: `true`
44
+ - `variables?: { light?: Record<string, string>; dark?: Record<string, string> }`
43
45
 
44
46
  ### `useTheme()`
45
47
 
@@ -57,6 +59,32 @@ Returns:
57
59
  - Cross-tab updates are synced through `storage` events.
58
60
  - Package is ESM-only.
59
61
 
62
+ ## CSS variable support
63
+
64
+ ```tsx
65
+ <ThemeWatcher
66
+ variables={{
67
+ light: {
68
+ "--background": "#ffffff",
69
+ "--foreground": "#111111"
70
+ },
71
+ dark: {
72
+ "--background": "#111111",
73
+ "--foreground": "#ffffff"
74
+ }
75
+ }}
76
+ />
77
+ ```
78
+
79
+ Then in CSS:
80
+
81
+ ```css
82
+ body {
83
+ background: var(--background);
84
+ color: var(--foreground);
85
+ }
86
+ ```
87
+
60
88
  ## Development
61
89
 
62
90
  This repo uses Bun for package management and scripts, but the published package has no Bun runtime dependency.
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ type Theme = "light" | "dark";
2
2
  type ThemePreference = Theme | "system";
3
3
  type ThemeSource = "prop" | "storage" | "default" | "system";
4
4
  type ThemeAttribute = "data-theme" | "class";
5
+ type ThemeVariables = Partial<Record<Theme, Record<string, string>>>;
5
6
  interface ThemeState {
6
7
  theme: ThemePreference;
7
8
  resolvedTheme: Theme;
@@ -12,13 +13,15 @@ interface ThemeWatcherProps {
12
13
  storageKey?: string;
13
14
  attribute?: ThemeAttribute;
14
15
  defaultTheme?: ThemePreference;
16
+ variables?: ThemeVariables;
17
+ enableColorScheme?: boolean;
15
18
  }
16
19
  interface ThemeApi extends ThemeState {
17
20
  set: (theme: ThemePreference) => void;
18
21
  get: () => ThemePreference;
19
22
  }
20
23
 
21
- declare function ThemeWatcher({ theme, storageKey, attribute, defaultTheme }: ThemeWatcherProps): null;
24
+ declare function ThemeWatcher({ theme, storageKey, attribute, defaultTheme, variables, enableColorScheme }: ThemeWatcherProps): null;
22
25
 
23
26
  declare function useTheme(): ThemeApi;
24
27
 
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ import { useEffect } from "react";
5
5
  var DEFAULT_CONFIG = {
6
6
  storageKey: "theme-watcher",
7
7
  attribute: "data-theme",
8
- defaultTheme: "system"
8
+ defaultTheme: "system",
9
+ enableColorScheme: true
9
10
  };
10
11
  var VALID_PREFERENCES = /* @__PURE__ */ new Set(["light", "dark", "system"]);
11
12
  var config = { ...DEFAULT_CONFIG };
@@ -71,6 +72,29 @@ function applyDomTheme(resolvedTheme) {
71
72
  }
72
73
  root.setAttribute("data-theme", resolvedTheme);
73
74
  }
75
+ function applyThemeVariables(resolvedTheme) {
76
+ if (!isBrowserReady()) return;
77
+ const root = document.documentElement;
78
+ const lightVars = config.variables?.light ?? {};
79
+ const darkVars = config.variables?.dark ?? {};
80
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(lightVars), ...Object.keys(darkVars)]);
81
+ for (const key of allKeys) {
82
+ root.style.removeProperty(key);
83
+ }
84
+ const nextVars = config.variables?.[resolvedTheme];
85
+ if (!nextVars) return;
86
+ for (const [key, value] of Object.entries(nextVars)) {
87
+ root.style.setProperty(key, value);
88
+ }
89
+ }
90
+ function applyColorScheme(resolvedTheme) {
91
+ if (!isBrowserReady()) return;
92
+ if (!config.enableColorScheme) {
93
+ document.documentElement.style.removeProperty("color-scheme");
94
+ return;
95
+ }
96
+ document.documentElement.style.setProperty("color-scheme", resolvedTheme);
97
+ }
74
98
  function emit() {
75
99
  for (const callback of subscribers) {
76
100
  callback();
@@ -80,6 +104,8 @@ function recompute() {
80
104
  const stored = readStoredTheme();
81
105
  state = resolveTheme(controlledTheme, stored, config.defaultTheme);
82
106
  applyDomTheme(state.resolvedTheme);
107
+ applyThemeVariables(state.resolvedTheme);
108
+ applyColorScheme(state.resolvedTheme);
83
109
  emit();
84
110
  }
85
111
  function handleMediaChange() {
@@ -137,6 +163,8 @@ function setTheme(theme) {
137
163
  writeStoredTheme(theme);
138
164
  state = resolveTheme(controlledTheme, theme, config.defaultTheme);
139
165
  applyDomTheme(state.resolvedTheme);
166
+ applyThemeVariables(state.resolvedTheme);
167
+ applyColorScheme(state.resolvedTheme);
140
168
  emit();
141
169
  }
142
170
  function getTheme() {
@@ -158,11 +186,13 @@ function ThemeWatcher({
158
186
  theme,
159
187
  storageKey = "theme-watcher",
160
188
  attribute = "data-theme",
161
- defaultTheme = "system"
189
+ defaultTheme = "system",
190
+ variables,
191
+ enableColorScheme = true
162
192
  }) {
163
193
  useEffect(() => {
164
- return mountWatcher({ storageKey, attribute, defaultTheme }, theme);
165
- }, [theme, storageKey, attribute, defaultTheme]);
194
+ return mountWatcher({ storageKey, attribute, defaultTheme, variables, enableColorScheme }, theme);
195
+ }, [theme, storageKey, attribute, defaultTheme, variables, enableColorScheme]);
166
196
  return null;
167
197
  }
168
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theme-watcher",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Plug-and-play theme watcher for React SPAs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",