@wrksz/themes 0.2.1 → 0.3.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 +60 -2
- package/dist/index.d.ts +9 -3
- package/dist/index.js +131 -74
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ export function ThemeToggle() {
|
|
|
58
58
|
| `themes` | `string[]` | `["light", "dark"]` | Available themes |
|
|
59
59
|
| `defaultTheme` | `string` | `"system"` | Theme used when no preference is stored |
|
|
60
60
|
| `forcedTheme` | `string` | - | Force a specific theme, ignoring user preference |
|
|
61
|
+
| `initialTheme` | `string` | - | Server-provided theme that overrides storage on mount. User can still call `setTheme` to change it |
|
|
61
62
|
| `enableSystem` | `boolean` | `true` | Detect system preference via `prefers-color-scheme` |
|
|
62
63
|
| `enableColorScheme` | `boolean` | `true` | Set native `color-scheme` CSS property |
|
|
63
64
|
| `attribute` | `string \| string[]` | `"class"` | HTML attribute(s) to set on target element (`"class"`, `"data-theme"`, etc.) |
|
|
@@ -66,6 +67,7 @@ export function ThemeToggle() {
|
|
|
66
67
|
| `storageKey` | `string` | `"theme"` | Key used for storage |
|
|
67
68
|
| `storage` | `"localStorage" \| "sessionStorage" \| "none"` | `"localStorage"` | Where to persist the theme |
|
|
68
69
|
| `disableTransitionOnChange` | `boolean` | `false` | Disable CSS transitions when switching themes |
|
|
70
|
+
| `followSystem` | `boolean` | `false` | Always follow system preference changes, even after `setTheme` was called |
|
|
69
71
|
| `themeColor` | `string \| Record<string, string>` | - | Update `<meta name="theme-color">` on theme change |
|
|
70
72
|
| `nonce` | `string` | - | CSP nonce for the inline script |
|
|
71
73
|
| `onThemeChange` | `(theme: string) => void` | - | Called whenever the resolved theme changes |
|
|
@@ -131,16 +133,27 @@ const { theme, setTheme } = useTheme<AppTheme>();
|
|
|
131
133
|
</ThemeProvider>
|
|
132
134
|
```
|
|
133
135
|
|
|
134
|
-
###
|
|
136
|
+
### Multiple classes per theme
|
|
137
|
+
|
|
138
|
+
Map a theme to multiple CSS classes by using a space-separated value:
|
|
135
139
|
|
|
136
140
|
```tsx
|
|
137
141
|
<ThemeProvider
|
|
138
|
-
|
|
142
|
+
themes={["light", "dark", "dim"]}
|
|
143
|
+
value={{ light: "light", dark: "dark high-contrast", dim: "dark dim" }}
|
|
139
144
|
>
|
|
140
145
|
{children}
|
|
141
146
|
</ThemeProvider>
|
|
142
147
|
```
|
|
143
148
|
|
|
149
|
+
### Meta theme-color (Safari / PWA)
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<ThemeProvider themeColor={{ light: "#ffffff", dark: "#0a0a0a" }}>
|
|
153
|
+
{children}
|
|
154
|
+
</ThemeProvider>
|
|
155
|
+
```
|
|
156
|
+
|
|
144
157
|
Works with CSS variables too:
|
|
145
158
|
|
|
146
159
|
```tsx
|
|
@@ -167,6 +180,48 @@ Works with CSS variables too:
|
|
|
167
180
|
</ThemeProvider>
|
|
168
181
|
```
|
|
169
182
|
|
|
183
|
+
### Server-provided theme
|
|
184
|
+
|
|
185
|
+
Use `initialTheme` to initialize from a server-side source (database, session, cookie) on every mount, overriding any locally stored value. The user can still call `setTheme` to change it - use `onThemeChange` to persist the change back.
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// app/layout.tsx (server component)
|
|
189
|
+
export default async function RootLayout({ children }) {
|
|
190
|
+
const userTheme = await getUserTheme(); // "light" | "dark" | null
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<html lang="en" suppressHydrationWarning>
|
|
194
|
+
<body>
|
|
195
|
+
<ThemeProvider
|
|
196
|
+
initialTheme={userTheme ?? undefined}
|
|
197
|
+
onThemeChange={saveUserTheme}
|
|
198
|
+
>
|
|
199
|
+
{children}
|
|
200
|
+
</ThemeProvider>
|
|
201
|
+
</body>
|
|
202
|
+
</html>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Nested provider in a Client Component
|
|
208
|
+
|
|
209
|
+
`ThemeProvider` renders an inline `<script>` and must be used in a Server Component. For nested providers inside Client Components, use `ClientThemeProvider` instead:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
"use client";
|
|
213
|
+
|
|
214
|
+
import { ClientThemeProvider } from "@wrksz/themes";
|
|
215
|
+
|
|
216
|
+
export function AdminShell({ children }: { children: React.ReactNode }) {
|
|
217
|
+
return (
|
|
218
|
+
<ClientThemeProvider forcedTheme="dark">
|
|
219
|
+
{children}
|
|
220
|
+
</ClientThemeProvider>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
170
225
|
### Class on body instead of html
|
|
171
226
|
|
|
172
227
|
```tsx
|
|
@@ -182,9 +237,12 @@ Works with CSS variables too:
|
|
|
182
237
|
| React 19 script warning | Yes | Fixed (RSC split) |
|
|
183
238
|
| `__name` minification bug | Yes | Fixed |
|
|
184
239
|
| React 19 Activity/cacheComponents stale theme | Yes | Fixed (`useSyncExternalStore`) |
|
|
240
|
+
| Multiple classes per theme | No | Yes (`value` map with spaces) |
|
|
241
|
+
| Nested providers | No | Yes (per-instance store) |
|
|
185
242
|
| `sessionStorage` support | No | Yes |
|
|
186
243
|
| Disable storage | No | Yes (`storage: "none"`) |
|
|
187
244
|
| `meta theme-color` support | No | Yes (`themeColor` prop) |
|
|
245
|
+
| Server-provided theme | No | Yes (`initialTheme` prop) |
|
|
188
246
|
| Generic types | No | Yes (`useTheme<AppTheme>()`) |
|
|
189
247
|
| Zero runtime dependencies | Yes | Yes |
|
|
190
248
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
1
2
|
import { ReactNode } from "react";
|
|
2
3
|
type DefaultTheme = "light" | "dark" | "system";
|
|
3
4
|
type Attribute = "class" | `data-${string}`;
|
|
@@ -35,6 +36,10 @@ type ThemeProviderProps<Themes extends string = DefaultTheme> = {
|
|
|
35
36
|
onThemeChange?: (theme: Themes) => void;
|
|
36
37
|
/** Colors for meta theme-color tag, per theme or a single value */
|
|
37
38
|
themeColor?: ThemeColor;
|
|
39
|
+
/** Always follow system preference changes, even after setTheme was called */
|
|
40
|
+
followSystem?: boolean;
|
|
41
|
+
/** Server-provided theme that overrides storage on mount (e.g. from a database). User can still call setTheme to change it. */
|
|
42
|
+
initialTheme?: Themes | "system";
|
|
38
43
|
};
|
|
39
44
|
type ThemeContextValue<Themes extends string = DefaultTheme> = {
|
|
40
45
|
/** Current theme (may be "system") */
|
|
@@ -50,7 +55,8 @@ type ThemeContextValue<Themes extends string = DefaultTheme> = {
|
|
|
50
55
|
/** Set theme */
|
|
51
56
|
setTheme: (theme: Themes | "system" | ((current: Themes | "system" | undefined) => Themes | "system")) => void;
|
|
52
57
|
};
|
|
58
|
+
declare function ClientThemeProvider<Themes extends string = DefaultTheme>({ children, themes, forcedTheme, enableSystem, defaultTheme, attribute, value: valueMap, target, disableTransitionOnChange, storage, storageKey, enableColorScheme, themeColor, followSystem, onThemeChange, initialTheme }: ThemeProviderProps<Themes>): ReactElement;
|
|
53
59
|
declare function useTheme<Themes extends string = DefaultTheme>(): ThemeContextValue<Themes>;
|
|
54
|
-
import { ReactElement } from "react";
|
|
55
|
-
declare function ThemeProvider<Themes extends string = DefaultTheme>({ children, themes, forcedTheme, enableSystem, defaultTheme, attribute, value: valueMap, target, disableTransitionOnChange, storage, storageKey, enableColorScheme, nonce, onThemeChange, themeColor }: ThemeProviderProps<Themes>):
|
|
56
|
-
export { useTheme, ValueObject, ThemeProviderProps, ThemeProvider, ThemeContextValue, ThemeColor, StorageType, DefaultTheme, Attribute };
|
|
60
|
+
import { ReactElement as ReactElement2 } from "react";
|
|
61
|
+
declare function ThemeProvider<Themes extends string = DefaultTheme>({ children, themes, forcedTheme, enableSystem, defaultTheme, attribute, value: valueMap, target, disableTransitionOnChange, storage, storageKey, enableColorScheme, nonce, onThemeChange, themeColor, followSystem, initialTheme }: ThemeProviderProps<Themes>): ReactElement2;
|
|
62
|
+
export { useTheme, ValueObject, ThemeProviderProps, ThemeProvider, ThemeContextValue, ThemeColor, StorageType, DefaultTheme, ClientThemeProvider, Attribute };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
// src/client-provider.tsx
|
|
3
|
+
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
4
|
+
|
|
2
5
|
// src/context.ts
|
|
3
6
|
import { createContext, useContext } from "react";
|
|
4
7
|
var ThemeContext = createContext(undefined);
|
|
@@ -9,43 +12,43 @@ function useTheme() {
|
|
|
9
12
|
}
|
|
10
13
|
return ctx;
|
|
11
14
|
}
|
|
12
|
-
// src/client-provider.tsx
|
|
13
|
-
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
14
15
|
|
|
15
16
|
// src/store.ts
|
|
16
|
-
var state = { theme: undefined, systemTheme: undefined };
|
|
17
|
-
var listeners = new Set;
|
|
18
|
-
function emit() {
|
|
19
|
-
for (const listener of listeners)
|
|
20
|
-
listener();
|
|
21
|
-
}
|
|
22
17
|
var SERVER_SNAPSHOT = { theme: undefined, systemTheme: undefined };
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
getSnapshot() {
|
|
31
|
-
return state;
|
|
32
|
-
},
|
|
33
|
-
getServerSnapshot() {
|
|
34
|
-
return SERVER_SNAPSHOT;
|
|
35
|
-
},
|
|
36
|
-
setTheme(theme) {
|
|
37
|
-
if (state.theme === theme)
|
|
38
|
-
return;
|
|
39
|
-
state = { ...state, theme };
|
|
40
|
-
emit();
|
|
41
|
-
},
|
|
42
|
-
setSystemTheme(systemTheme) {
|
|
43
|
-
if (state.systemTheme === systemTheme)
|
|
44
|
-
return;
|
|
45
|
-
state = { ...state, systemTheme };
|
|
46
|
-
emit();
|
|
18
|
+
function createThemeStore() {
|
|
19
|
+
let state = { theme: undefined, systemTheme: undefined };
|
|
20
|
+
const listeners = new Set;
|
|
21
|
+
function emit() {
|
|
22
|
+
for (const listener of listeners)
|
|
23
|
+
listener();
|
|
47
24
|
}
|
|
48
|
-
|
|
25
|
+
return {
|
|
26
|
+
subscribe(listener) {
|
|
27
|
+
listeners.add(listener);
|
|
28
|
+
return () => {
|
|
29
|
+
listeners.delete(listener);
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
getSnapshot() {
|
|
33
|
+
return state;
|
|
34
|
+
},
|
|
35
|
+
getServerSnapshot() {
|
|
36
|
+
return SERVER_SNAPSHOT;
|
|
37
|
+
},
|
|
38
|
+
setTheme(theme) {
|
|
39
|
+
if (state.theme === theme)
|
|
40
|
+
return;
|
|
41
|
+
state = { ...state, theme };
|
|
42
|
+
emit();
|
|
43
|
+
},
|
|
44
|
+
setSystemTheme(systemTheme) {
|
|
45
|
+
if (state.systemTheme === systemTheme)
|
|
46
|
+
return;
|
|
47
|
+
state = { ...state, systemTheme };
|
|
48
|
+
emit();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
49
52
|
|
|
50
53
|
// src/client-provider.tsx
|
|
51
54
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
@@ -81,10 +84,15 @@ function ClientThemeProvider({
|
|
|
81
84
|
storageKey = "theme",
|
|
82
85
|
enableColorScheme = true,
|
|
83
86
|
themeColor,
|
|
84
|
-
|
|
87
|
+
followSystem = false,
|
|
88
|
+
onThemeChange,
|
|
89
|
+
initialTheme
|
|
85
90
|
}) {
|
|
86
91
|
const resolvedDefault = defaultTheme ?? (enableSystem ? "system" : "light");
|
|
87
|
-
const
|
|
92
|
+
const storeRef = useRef(createThemeStore());
|
|
93
|
+
const store = storeRef.current;
|
|
94
|
+
const { getSnapshot, setTheme: setStoreTheme, setSystemTheme: setStoreSystemTheme } = store;
|
|
95
|
+
const { theme, systemTheme } = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
|
|
88
96
|
const resolvedTheme = forcedTheme ?? (theme === "system" || theme === undefined ? systemTheme : theme);
|
|
89
97
|
const onThemeChangeRef = useRef(onThemeChange);
|
|
90
98
|
useEffect(() => {
|
|
@@ -111,9 +119,9 @@ function ClientThemeProvider({
|
|
|
111
119
|
}
|
|
112
120
|
for (const attr of attrs) {
|
|
113
121
|
if (attr === "class") {
|
|
114
|
-
const toRemove = themes.
|
|
122
|
+
const toRemove = themes.flatMap((t) => (valueMap?.[t] ?? t).split(" "));
|
|
115
123
|
el.classList.remove(...toRemove);
|
|
116
|
-
el.classList.add(attrValue);
|
|
124
|
+
el.classList.add(...attrValue.split(" "));
|
|
117
125
|
} else {
|
|
118
126
|
el.setAttribute(attr, attrValue);
|
|
119
127
|
}
|
|
@@ -134,37 +142,78 @@ function ClientThemeProvider({
|
|
|
134
142
|
themeColor
|
|
135
143
|
]);
|
|
136
144
|
useEffect(() => {
|
|
145
|
+
const mq = enableSystem ? window.matchMedia("(prefers-color-scheme: dark)") : null;
|
|
146
|
+
const sys = mq ? mq.matches ? "dark" : "light" : undefined;
|
|
147
|
+
if (sys)
|
|
148
|
+
setStoreSystemTheme(sys);
|
|
137
149
|
if (forcedTheme) {
|
|
138
|
-
|
|
139
|
-
|
|
150
|
+
setStoreTheme(forcedTheme);
|
|
151
|
+
applyToDom(forcedTheme);
|
|
152
|
+
} else if (initialTheme) {
|
|
153
|
+
setStoreTheme(initialTheme);
|
|
154
|
+
applyToDom(initialTheme === "system" ? sys ?? "light" : initialTheme);
|
|
155
|
+
try {
|
|
156
|
+
if (storage !== "none") {
|
|
157
|
+
const s = storage === "localStorage" ? localStorage : sessionStorage;
|
|
158
|
+
s.setItem(storageKey, initialTheme);
|
|
159
|
+
}
|
|
160
|
+
} catch {}
|
|
161
|
+
} else {
|
|
162
|
+
let stored = null;
|
|
163
|
+
try {
|
|
164
|
+
if (storage !== "none") {
|
|
165
|
+
const s = storage === "localStorage" ? localStorage : sessionStorage;
|
|
166
|
+
stored = s.getItem(storageKey);
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
const initial = stored && themes.includes(stored) ? stored : resolvedDefault;
|
|
170
|
+
setStoreTheme(initial);
|
|
171
|
+
applyToDom(initial === "system" ? sys ?? "light" : initial);
|
|
140
172
|
}
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
if (storage !== "none") {
|
|
144
|
-
const store = storage === "localStorage" ? localStorage : sessionStorage;
|
|
145
|
-
stored = store.getItem(storageKey);
|
|
146
|
-
}
|
|
147
|
-
} catch {}
|
|
148
|
-
const initial = stored && themes.includes(stored) ? stored : resolvedDefault;
|
|
149
|
-
themeStore.setTheme(initial);
|
|
150
|
-
}, [forcedTheme, resolvedDefault, storage, storageKey, themes]);
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
if (!enableSystem)
|
|
173
|
+
if (!mq)
|
|
153
174
|
return;
|
|
154
|
-
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
155
|
-
themeStore.setSystemTheme(mq.matches ? "dark" : "light");
|
|
156
175
|
const handler = (e) => {
|
|
157
176
|
const next = e.matches ? "dark" : "light";
|
|
158
|
-
|
|
159
|
-
const current =
|
|
160
|
-
if (current === "system" || current === undefined) {
|
|
177
|
+
setStoreSystemTheme(next);
|
|
178
|
+
const current = getSnapshot().theme;
|
|
179
|
+
if (current === "system" || current === undefined || followSystem) {
|
|
180
|
+
if (followSystem) {
|
|
181
|
+
setStoreTheme("system");
|
|
182
|
+
}
|
|
161
183
|
applyToDom(next);
|
|
162
184
|
onThemeChangeRef.current?.(next);
|
|
163
185
|
}
|
|
164
186
|
};
|
|
165
187
|
mq.addEventListener("change", handler);
|
|
166
188
|
return () => mq.removeEventListener("change", handler);
|
|
167
|
-
}, [
|
|
189
|
+
}, [
|
|
190
|
+
forcedTheme,
|
|
191
|
+
initialTheme,
|
|
192
|
+
resolvedDefault,
|
|
193
|
+
storage,
|
|
194
|
+
storageKey,
|
|
195
|
+
themes,
|
|
196
|
+
enableSystem,
|
|
197
|
+
followSystem,
|
|
198
|
+
applyToDom,
|
|
199
|
+
getSnapshot,
|
|
200
|
+
setStoreTheme,
|
|
201
|
+
setStoreSystemTheme
|
|
202
|
+
]);
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
const handler = () => {
|
|
205
|
+
const { theme: theme2, systemTheme: systemTheme2 } = getSnapshot();
|
|
206
|
+
const resolved = forcedTheme ?? (theme2 === "system" || theme2 === undefined ? systemTheme2 : theme2);
|
|
207
|
+
if (resolved)
|
|
208
|
+
applyToDom(resolved);
|
|
209
|
+
};
|
|
210
|
+
window.addEventListener("pageshow", handler);
|
|
211
|
+
window.addEventListener("popstate", handler);
|
|
212
|
+
return () => {
|
|
213
|
+
window.removeEventListener("pageshow", handler);
|
|
214
|
+
window.removeEventListener("popstate", handler);
|
|
215
|
+
};
|
|
216
|
+
}, [applyToDom, forcedTheme, getSnapshot]);
|
|
168
217
|
useEffect(() => {
|
|
169
218
|
if (storage === "none")
|
|
170
219
|
return;
|
|
@@ -173,30 +222,30 @@ function ClientThemeProvider({
|
|
|
173
222
|
return;
|
|
174
223
|
if (themes.includes(e.newValue)) {
|
|
175
224
|
const newTheme = e.newValue;
|
|
176
|
-
const resolved = newTheme === "system" ?
|
|
177
|
-
|
|
225
|
+
const resolved = newTheme === "system" ? getSnapshot().systemTheme ?? "light" : newTheme;
|
|
226
|
+
setStoreTheme(newTheme);
|
|
178
227
|
applyToDom(resolved);
|
|
179
228
|
}
|
|
180
229
|
};
|
|
181
230
|
window.addEventListener("storage", handler);
|
|
182
231
|
return () => window.removeEventListener("storage", handler);
|
|
183
|
-
}, [storage, storageKey, themes, applyToDom]);
|
|
232
|
+
}, [storage, storageKey, themes, applyToDom, getSnapshot, setStoreTheme]);
|
|
184
233
|
const setTheme = useCallback((next) => {
|
|
185
234
|
if (forcedTheme)
|
|
186
235
|
return;
|
|
187
|
-
const current =
|
|
236
|
+
const current = getSnapshot().theme;
|
|
188
237
|
const newTheme = typeof next === "function" ? next(current) : next;
|
|
189
|
-
const resolved = newTheme === "system" ?
|
|
190
|
-
|
|
238
|
+
const resolved = newTheme === "system" ? getSnapshot().systemTheme ?? "light" : newTheme;
|
|
239
|
+
setStoreTheme(newTheme);
|
|
191
240
|
applyToDom(resolved);
|
|
192
241
|
onThemeChangeRef.current?.(resolved);
|
|
193
242
|
try {
|
|
194
243
|
if (storage !== "none") {
|
|
195
|
-
const
|
|
196
|
-
|
|
244
|
+
const store2 = storage === "localStorage" ? localStorage : sessionStorage;
|
|
245
|
+
store2.setItem(storageKey, newTheme);
|
|
197
246
|
}
|
|
198
247
|
} catch {}
|
|
199
|
-
}, [applyToDom, forcedTheme, storage, storageKey]);
|
|
248
|
+
}, [applyToDom, forcedTheme, storage, storageKey, getSnapshot, setStoreTheme]);
|
|
200
249
|
const contextValue = {
|
|
201
250
|
theme: forcedTheme ?? theme,
|
|
202
251
|
resolvedTheme,
|
|
@@ -210,12 +259,13 @@ function ClientThemeProvider({
|
|
|
210
259
|
children
|
|
211
260
|
}, undefined, false, undefined, this);
|
|
212
261
|
}
|
|
213
|
-
|
|
214
262
|
// src/script.ts
|
|
215
|
-
function themeScript(storageKey, attribute, defaultTheme, enableSystem, enableColorScheme, forcedTheme, themes, value, target, storage, themeColors) {
|
|
263
|
+
function themeScript(storageKey, attribute, defaultTheme, enableSystem, enableColorScheme, forcedTheme, themes, value, target, storage, themeColors, initialTheme) {
|
|
216
264
|
let theme;
|
|
217
265
|
if (forcedTheme) {
|
|
218
266
|
theme = forcedTheme;
|
|
267
|
+
} else if (initialTheme && themes.includes(initialTheme)) {
|
|
268
|
+
theme = initialTheme;
|
|
219
269
|
} else {
|
|
220
270
|
let stored = null;
|
|
221
271
|
try {
|
|
@@ -236,9 +286,9 @@ function themeScript(storageKey, attribute, defaultTheme, enableSystem, enableCo
|
|
|
236
286
|
const attrs = Array.isArray(attribute) ? attribute : [attribute];
|
|
237
287
|
for (const attr of attrs) {
|
|
238
288
|
if (attr === "class") {
|
|
239
|
-
const toRemove = themes.
|
|
289
|
+
const toRemove = themes.flatMap((t) => (value?.[t] || t).split(" "));
|
|
240
290
|
el.classList.remove(...toRemove);
|
|
241
|
-
el.classList.add(attrValue);
|
|
291
|
+
el.classList.add(...attrValue.split(" "));
|
|
242
292
|
} else {
|
|
243
293
|
el.setAttribute(attr, attrValue);
|
|
244
294
|
}
|
|
@@ -272,7 +322,8 @@ function getScript(config) {
|
|
|
272
322
|
JSON.stringify(config.value ?? null),
|
|
273
323
|
JSON.stringify(config.target),
|
|
274
324
|
JSON.stringify(config.storage),
|
|
275
|
-
JSON.stringify(config.themeColors ?? null)
|
|
325
|
+
JSON.stringify(config.themeColors ?? null),
|
|
326
|
+
JSON.stringify(config.initialTheme ?? null)
|
|
276
327
|
].join(",");
|
|
277
328
|
return `(${fn})(${args})`;
|
|
278
329
|
}
|
|
@@ -295,7 +346,9 @@ function ThemeProvider({
|
|
|
295
346
|
enableColorScheme = true,
|
|
296
347
|
nonce,
|
|
297
348
|
onThemeChange,
|
|
298
|
-
themeColor
|
|
349
|
+
themeColor,
|
|
350
|
+
followSystem = false,
|
|
351
|
+
initialTheme
|
|
299
352
|
}) {
|
|
300
353
|
const resolvedDefault = defaultTheme ?? (enableSystem ? "system" : "light");
|
|
301
354
|
return /* @__PURE__ */ jsxDEV2(Fragment, {
|
|
@@ -313,7 +366,8 @@ function ThemeProvider({
|
|
|
313
366
|
value: valueMap,
|
|
314
367
|
target,
|
|
315
368
|
storage,
|
|
316
|
-
themeColors: themeColor
|
|
369
|
+
themeColors: themeColor,
|
|
370
|
+
initialTheme
|
|
317
371
|
})
|
|
318
372
|
},
|
|
319
373
|
nonce
|
|
@@ -331,7 +385,9 @@ function ThemeProvider({
|
|
|
331
385
|
storageKey,
|
|
332
386
|
enableColorScheme,
|
|
333
387
|
themeColor,
|
|
388
|
+
followSystem,
|
|
334
389
|
onThemeChange,
|
|
390
|
+
initialTheme,
|
|
335
391
|
children
|
|
336
392
|
}, undefined, false, undefined, this)
|
|
337
393
|
]
|
|
@@ -339,5 +395,6 @@ function ThemeProvider({
|
|
|
339
395
|
}
|
|
340
396
|
export {
|
|
341
397
|
useTheme,
|
|
342
|
-
ThemeProvider
|
|
398
|
+
ThemeProvider,
|
|
399
|
+
ClientThemeProvider
|
|
343
400
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrksz/themes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A modern, fully-featured theme management library for Next.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"lint": "biome check src",
|
|
18
18
|
"lint:fix": "biome check --write src",
|
|
19
19
|
"format": "biome format --write src",
|
|
20
|
+
"test": "bun test",
|
|
20
21
|
"prepare": "[ \"$CI\" = \"true\" ] || lefthook install"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
"@types/react": "^19.2.14",
|
|
26
27
|
"@types/react-dom": "^19.2.3",
|
|
27
28
|
"bunup": "^0.16.31",
|
|
29
|
+
"happy-dom": "^20.8.4",
|
|
28
30
|
"lefthook": "^2.1.4",
|
|
29
31
|
"react": "^19.2.4",
|
|
30
32
|
"react-dom": "^19.2.4",
|