theme-watcher 0.1.0 → 0.1.2
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 +31 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +41 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,8 +38,10 @@ Mount once near your app root.
|
|
|
38
38
|
Props:
|
|
39
39
|
- `theme?: "light" | "dark"` controlled override
|
|
40
40
|
- `storageKey?: string` default: `"theme-watcher"`
|
|
41
|
-
- `attribute?: "data-theme" | "class"` default: `"
|
|
41
|
+
- `attribute?: "data-theme" | "class" | "both"` default: `"both"`
|
|
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
|
|
|
@@ -48,6 +50,7 @@ Returns:
|
|
|
48
50
|
- `resolvedTheme` active applied theme (`"light" | "dark"`)
|
|
49
51
|
- `source` where the current value came from (`"prop" | "storage" | "default" | "system"`)
|
|
50
52
|
- `set(theme)` set and persist preference
|
|
53
|
+
- `setTheme(theme)` alias for compatibility with next-themes-style usage
|
|
51
54
|
- `get()` get persisted preference
|
|
52
55
|
|
|
53
56
|
## Behavior notes
|
|
@@ -55,8 +58,35 @@ Returns:
|
|
|
55
58
|
- Priority order: `theme prop` -> `localStorage` -> `defaultTheme` -> system theme.
|
|
56
59
|
- `system` mode updates live when `prefers-color-scheme` changes.
|
|
57
60
|
- Cross-tab updates are synced through `storage` events.
|
|
61
|
+
- By default, it applies both `data-theme` and `html.dark` so Tailwind/shadcn and CSS-var setups both work.
|
|
58
62
|
- Package is ESM-only.
|
|
59
63
|
|
|
64
|
+
## CSS variable support
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<ThemeWatcher
|
|
68
|
+
variables={{
|
|
69
|
+
light: {
|
|
70
|
+
"--background": "#ffffff",
|
|
71
|
+
"--foreground": "#111111"
|
|
72
|
+
},
|
|
73
|
+
dark: {
|
|
74
|
+
"--background": "#111111",
|
|
75
|
+
"--foreground": "#ffffff"
|
|
76
|
+
}
|
|
77
|
+
}}
|
|
78
|
+
/>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Then in CSS:
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
body {
|
|
85
|
+
background: var(--background);
|
|
86
|
+
color: var(--foreground);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
60
90
|
## Development
|
|
61
91
|
|
|
62
92
|
This repo uses Bun for package management and scripts, but the published package has no Bun runtime dependency.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
type Theme = "light" | "dark";
|
|
2
2
|
type ThemePreference = Theme | "system";
|
|
3
3
|
type ThemeSource = "prop" | "storage" | "default" | "system";
|
|
4
|
-
type ThemeAttribute = "data-theme" | "class";
|
|
4
|
+
type ThemeAttribute = "data-theme" | "class" | "both";
|
|
5
|
+
type ThemeVariables = Partial<Record<Theme, Record<string, string>>>;
|
|
5
6
|
interface ThemeState {
|
|
6
7
|
theme: ThemePreference;
|
|
7
8
|
resolvedTheme: Theme;
|
|
@@ -12,13 +13,16 @@ 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;
|
|
21
|
+
setTheme: (theme: ThemePreference) => void;
|
|
18
22
|
get: () => ThemePreference;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
declare function ThemeWatcher({ theme, storageKey, attribute, defaultTheme }: ThemeWatcherProps): null;
|
|
25
|
+
declare function ThemeWatcher({ theme, storageKey, attribute, defaultTheme, variables, enableColorScheme }: ThemeWatcherProps): null;
|
|
22
26
|
|
|
23
27
|
declare function useTheme(): ThemeApi;
|
|
24
28
|
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,9 @@ import { useEffect } from "react";
|
|
|
4
4
|
// src/theme-store.ts
|
|
5
5
|
var DEFAULT_CONFIG = {
|
|
6
6
|
storageKey: "theme-watcher",
|
|
7
|
-
attribute: "
|
|
8
|
-
defaultTheme: "system"
|
|
7
|
+
attribute: "both",
|
|
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 };
|
|
@@ -64,13 +65,38 @@ function resolveTheme(propTheme, stored, defaultTheme) {
|
|
|
64
65
|
function applyDomTheme(resolvedTheme) {
|
|
65
66
|
if (!isBrowserReady()) return;
|
|
66
67
|
const root = document.documentElement;
|
|
67
|
-
if (config.attribute === "class") {
|
|
68
|
+
if (config.attribute === "class" || config.attribute === "both") {
|
|
68
69
|
root.classList.toggle("dark", resolvedTheme === "dark");
|
|
69
|
-
root.classList.
|
|
70
|
+
root.classList.remove("light");
|
|
71
|
+
}
|
|
72
|
+
if (config.attribute === "class") {
|
|
70
73
|
return;
|
|
71
74
|
}
|
|
72
75
|
root.setAttribute("data-theme", resolvedTheme);
|
|
73
76
|
}
|
|
77
|
+
function applyThemeVariables(resolvedTheme) {
|
|
78
|
+
if (!isBrowserReady()) return;
|
|
79
|
+
const root = document.documentElement;
|
|
80
|
+
const lightVars = config.variables?.light ?? {};
|
|
81
|
+
const darkVars = config.variables?.dark ?? {};
|
|
82
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(lightVars), ...Object.keys(darkVars)]);
|
|
83
|
+
for (const key of allKeys) {
|
|
84
|
+
root.style.removeProperty(key);
|
|
85
|
+
}
|
|
86
|
+
const nextVars = config.variables?.[resolvedTheme];
|
|
87
|
+
if (!nextVars) return;
|
|
88
|
+
for (const [key, value] of Object.entries(nextVars)) {
|
|
89
|
+
root.style.setProperty(key, value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function applyColorScheme(resolvedTheme) {
|
|
93
|
+
if (!isBrowserReady()) return;
|
|
94
|
+
if (!config.enableColorScheme) {
|
|
95
|
+
document.documentElement.style.removeProperty("color-scheme");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
document.documentElement.style.setProperty("color-scheme", resolvedTheme);
|
|
99
|
+
}
|
|
74
100
|
function emit() {
|
|
75
101
|
for (const callback of subscribers) {
|
|
76
102
|
callback();
|
|
@@ -80,6 +106,8 @@ function recompute() {
|
|
|
80
106
|
const stored = readStoredTheme();
|
|
81
107
|
state = resolveTheme(controlledTheme, stored, config.defaultTheme);
|
|
82
108
|
applyDomTheme(state.resolvedTheme);
|
|
109
|
+
applyThemeVariables(state.resolvedTheme);
|
|
110
|
+
applyColorScheme(state.resolvedTheme);
|
|
83
111
|
emit();
|
|
84
112
|
}
|
|
85
113
|
function handleMediaChange() {
|
|
@@ -137,6 +165,8 @@ function setTheme(theme) {
|
|
|
137
165
|
writeStoredTheme(theme);
|
|
138
166
|
state = resolveTheme(controlledTheme, theme, config.defaultTheme);
|
|
139
167
|
applyDomTheme(state.resolvedTheme);
|
|
168
|
+
applyThemeVariables(state.resolvedTheme);
|
|
169
|
+
applyColorScheme(state.resolvedTheme);
|
|
140
170
|
emit();
|
|
141
171
|
}
|
|
142
172
|
function getTheme() {
|
|
@@ -157,12 +187,14 @@ function getServerSnapshot() {
|
|
|
157
187
|
function ThemeWatcher({
|
|
158
188
|
theme,
|
|
159
189
|
storageKey = "theme-watcher",
|
|
160
|
-
attribute = "
|
|
161
|
-
defaultTheme = "system"
|
|
190
|
+
attribute = "both",
|
|
191
|
+
defaultTheme = "system",
|
|
192
|
+
variables,
|
|
193
|
+
enableColorScheme = true
|
|
162
194
|
}) {
|
|
163
195
|
useEffect(() => {
|
|
164
|
-
return mountWatcher({ storageKey, attribute, defaultTheme }, theme);
|
|
165
|
-
}, [theme, storageKey, attribute, defaultTheme]);
|
|
196
|
+
return mountWatcher({ storageKey, attribute, defaultTheme, variables, enableColorScheme }, theme);
|
|
197
|
+
}, [theme, storageKey, attribute, defaultTheme, variables, enableColorScheme]);
|
|
166
198
|
return null;
|
|
167
199
|
}
|
|
168
200
|
|
|
@@ -173,6 +205,7 @@ function useTheme() {
|
|
|
173
205
|
return {
|
|
174
206
|
...state2,
|
|
175
207
|
set: setTheme,
|
|
208
|
+
setTheme,
|
|
176
209
|
get: getTheme
|
|
177
210
|
};
|
|
178
211
|
}
|