@wrksz/themes 0.3.0 → 0.4.0
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 +166 -4
- package/dist/index.d.ts +29 -3
- package/dist/index.js +62 -16
- package/package.json +2 -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,9 +67,9 @@ 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 |
|
|
69
|
-
| `followSystem` | `boolean` | `false` | Always follow system preference changes, even after `setTheme` was called |
|
|
70
|
+
| `followSystem` | `boolean` | `false` | Always follow system preference changes, even after `setTheme` was called. Also ignores stored value on mount in favor of current system preference |
|
|
70
71
|
| `themeColor` | `string \| Record<string, string>` | - | Update `<meta name="theme-color">` on theme change |
|
|
71
|
-
| `nonce` | `string` | - | CSP nonce for the inline script |
|
|
72
|
+
| `nonce` | `string` | - | CSP nonce for the inline script (`ThemeProvider` only - `ClientThemeProvider` renders no script) |
|
|
72
73
|
| `onThemeChange` | `(theme: string) => void` | - | Called whenever the resolved theme changes |
|
|
73
74
|
|
|
74
75
|
### `useTheme`
|
|
@@ -132,16 +133,27 @@ const { theme, setTheme } = useTheme<AppTheme>();
|
|
|
132
133
|
</ThemeProvider>
|
|
133
134
|
```
|
|
134
135
|
|
|
135
|
-
###
|
|
136
|
+
### Multiple classes per theme
|
|
137
|
+
|
|
138
|
+
Map a theme to multiple CSS classes by using a space-separated value:
|
|
136
139
|
|
|
137
140
|
```tsx
|
|
138
141
|
<ThemeProvider
|
|
139
|
-
|
|
142
|
+
themes={["light", "dark", "dim"]}
|
|
143
|
+
value={{ light: "light", dark: "dark high-contrast", dim: "dark dim" }}
|
|
140
144
|
>
|
|
141
145
|
{children}
|
|
142
146
|
</ThemeProvider>
|
|
143
147
|
```
|
|
144
148
|
|
|
149
|
+
### Meta theme-color (Safari / PWA)
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<ThemeProvider themeColor={{ light: "#ffffff", dark: "#0a0a0a" }}>
|
|
153
|
+
{children}
|
|
154
|
+
</ThemeProvider>
|
|
155
|
+
```
|
|
156
|
+
|
|
145
157
|
Works with CSS variables too:
|
|
146
158
|
|
|
147
159
|
```tsx
|
|
@@ -168,6 +180,153 @@ Works with CSS variables too:
|
|
|
168
180
|
</ThemeProvider>
|
|
169
181
|
```
|
|
170
182
|
|
|
183
|
+
### Different theme per section (scoped theming)
|
|
184
|
+
|
|
185
|
+
Apply the theme to a specific element instead of `<html>` using the `target` prop. This lets different sections of your app have independent themes simultaneously.
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// app/landing/layout.tsx
|
|
189
|
+
export default function LandingLayout({ children }) {
|
|
190
|
+
return (
|
|
191
|
+
<ThemeProvider forcedTheme="dark" target="#landing-root" storage="none">
|
|
192
|
+
<div id="landing-root">{children}</div>
|
|
193
|
+
</ThemeProvider>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// app/dashboard/layout.tsx
|
|
198
|
+
export default function DashboardLayout({ children }) {
|
|
199
|
+
return (
|
|
200
|
+
<ThemeProvider forcedTheme="light" target="#dashboard-root" storage="none">
|
|
201
|
+
<div id="dashboard-root">{children}</div>
|
|
202
|
+
</ThemeProvider>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```css
|
|
208
|
+
/* scope your CSS variables to the target element */
|
|
209
|
+
#landing-root { --bg: #0a0a0a; --fg: #fafafa; }
|
|
210
|
+
#dashboard-root { --bg: #ffffff; --fg: #0a0a0a; }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Use `storage="none"` when the theme is forced - there's nothing to persist.
|
|
214
|
+
|
|
215
|
+
### Server-provided theme
|
|
216
|
+
|
|
217
|
+
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.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
// app/layout.tsx (server component)
|
|
221
|
+
export default async function RootLayout({ children }) {
|
|
222
|
+
const userTheme = await getUserTheme(); // "light" | "dark" | null
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<html lang="en" suppressHydrationWarning>
|
|
226
|
+
<body>
|
|
227
|
+
<ThemeProvider
|
|
228
|
+
initialTheme={userTheme ?? undefined}
|
|
229
|
+
onThemeChange={saveUserTheme}
|
|
230
|
+
>
|
|
231
|
+
{children}
|
|
232
|
+
</ThemeProvider>
|
|
233
|
+
</body>
|
|
234
|
+
</html>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Nested provider in a Client Component
|
|
240
|
+
|
|
241
|
+
`ThemeProvider` renders an inline `<script>` and must be used in a Server Component. For nested providers inside Client Components, use `ClientThemeProvider` instead:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
"use client";
|
|
245
|
+
|
|
246
|
+
import { ClientThemeProvider } from "@wrksz/themes";
|
|
247
|
+
|
|
248
|
+
export function AdminShell({ children }: { children: React.ReactNode }) {
|
|
249
|
+
return (
|
|
250
|
+
<ClientThemeProvider forcedTheme="dark">
|
|
251
|
+
{children}
|
|
252
|
+
</ClientThemeProvider>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### `useThemeValue`
|
|
258
|
+
|
|
259
|
+
Returns the value from a map that matches the current resolved theme. Returns `undefined` before the theme resolves on the client.
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
"use client";
|
|
263
|
+
|
|
264
|
+
import { useThemeValue } from "@wrksz/themes";
|
|
265
|
+
|
|
266
|
+
// strings
|
|
267
|
+
const label = useThemeValue({ light: "Switch to dark", dark: "Switch to light" });
|
|
268
|
+
|
|
269
|
+
// CSS values
|
|
270
|
+
const bg = useThemeValue({ light: "#ffffff", dark: "#0a0a0a", purple: "#6633ff" });
|
|
271
|
+
|
|
272
|
+
// any type
|
|
273
|
+
const icon = useThemeValue({ light: <SunIcon />, dark: <MoonIcon /> });
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Theme-aware images
|
|
277
|
+
|
|
278
|
+
Showing different images per theme has a hydration mismatch problem - `resolvedTheme` is always `undefined` on the server. Use the built-in `ThemedImage` component which shows a transparent placeholder until the theme resolves on the client:
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { ThemedImage } from "@wrksz/themes";
|
|
282
|
+
|
|
283
|
+
<ThemedImage
|
|
284
|
+
src={{ light: "/logo-light.png", dark: "/logo-dark.png" }}
|
|
285
|
+
alt="Logo"
|
|
286
|
+
width={200}
|
|
287
|
+
height={50}
|
|
288
|
+
/>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Works with any custom themes too:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
<ThemedImage
|
|
295
|
+
src={{
|
|
296
|
+
light: "/logo-light.png",
|
|
297
|
+
dark: "/logo-dark.png",
|
|
298
|
+
purple: "/logo-purple.png",
|
|
299
|
+
}}
|
|
300
|
+
alt="Logo"
|
|
301
|
+
width={200}
|
|
302
|
+
height={50}
|
|
303
|
+
/>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
For custom themes or `next/image`, use `resolvedTheme` directly with a fallback:
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
"use client";
|
|
310
|
+
|
|
311
|
+
import Image from "next/image";
|
|
312
|
+
import { useTheme } from "@wrksz/themes";
|
|
313
|
+
|
|
314
|
+
export function Logo() {
|
|
315
|
+
const { resolvedTheme } = useTheme();
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<Image
|
|
319
|
+
src={resolvedTheme === "dark" ? "/logo-dark.png" : "/logo-light.png"}
|
|
320
|
+
alt="Logo"
|
|
321
|
+
width={200}
|
|
322
|
+
height={50}
|
|
323
|
+
// avoids layout shift while theme is resolving
|
|
324
|
+
style={{ visibility: resolvedTheme ? "visible" : "hidden" }}
|
|
325
|
+
/>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
171
330
|
### Class on body instead of html
|
|
172
331
|
|
|
173
332
|
```tsx
|
|
@@ -183,9 +342,12 @@ Works with CSS variables too:
|
|
|
183
342
|
| React 19 script warning | Yes | Fixed (RSC split) |
|
|
184
343
|
| `__name` minification bug | Yes | Fixed |
|
|
185
344
|
| React 19 Activity/cacheComponents stale theme | Yes | Fixed (`useSyncExternalStore`) |
|
|
345
|
+
| Multiple classes per theme | No | Yes (`value` map with spaces) |
|
|
346
|
+
| Nested providers | No | Yes (per-instance store) |
|
|
186
347
|
| `sessionStorage` support | No | Yes |
|
|
187
348
|
| Disable storage | No | Yes (`storage: "none"`) |
|
|
188
349
|
| `meta theme-color` support | No | Yes (`themeColor` prop) |
|
|
350
|
+
| Server-provided theme | No | Yes (`initialTheme` prop) |
|
|
189
351
|
| Generic types | No | Yes (`useTheme<AppTheme>()`) |
|
|
190
352
|
| Zero runtime dependencies | Yes | Yes |
|
|
191
353
|
|
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}`;
|
|
@@ -37,6 +38,8 @@ type ThemeProviderProps<Themes extends string = DefaultTheme> = {
|
|
|
37
38
|
themeColor?: ThemeColor;
|
|
38
39
|
/** Always follow system preference changes, even after setTheme was called */
|
|
39
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";
|
|
40
43
|
};
|
|
41
44
|
type ThemeContextValue<Themes extends string = DefaultTheme> = {
|
|
42
45
|
/** Current theme (may be "system") */
|
|
@@ -52,7 +55,30 @@ type ThemeContextValue<Themes extends string = DefaultTheme> = {
|
|
|
52
55
|
/** Set theme */
|
|
53
56
|
setTheme: (theme: Themes | "system" | ((current: Themes | "system" | undefined) => Themes | "system")) => void;
|
|
54
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;
|
|
55
59
|
declare function useTheme<Themes extends string = DefaultTheme>(): ThemeContextValue<Themes>;
|
|
56
|
-
import { ReactElement } from "react";
|
|
57
|
-
declare function ThemeProvider<Themes extends string = DefaultTheme>({ children, themes, forcedTheme, enableSystem, defaultTheme, attribute, value: valueMap, target, disableTransitionOnChange, storage, storageKey, enableColorScheme, nonce, onThemeChange, themeColor, followSystem }: ThemeProviderProps<Themes>):
|
|
58
|
-
|
|
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
|
+
import { ImgHTMLAttributes, ReactElement as ReactElement3 } from "react";
|
|
63
|
+
type ThemedImageProps = Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt"> & {
|
|
64
|
+
/** Map of theme name to image source */
|
|
65
|
+
src: Record<string, string>;
|
|
66
|
+
/**
|
|
67
|
+
* Shown before the theme resolves on the client.
|
|
68
|
+
* Defaults to a transparent 1x1 GIF to avoid hydration mismatch.
|
|
69
|
+
*/
|
|
70
|
+
fallback?: string;
|
|
71
|
+
/** Alt text (required for accessibility) */
|
|
72
|
+
alt: string;
|
|
73
|
+
};
|
|
74
|
+
declare function ThemedImage({ src, fallback, alt,...props }: ThemedImageProps): ReactElement3;
|
|
75
|
+
/**
|
|
76
|
+
* Returns the value from the map that corresponds to the current resolved theme.
|
|
77
|
+
* Returns `undefined` if the theme hasn't resolved yet (e.g. during SSR).
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* const label = useThemeValue({ light: "Switch to dark", dark: "Switch to light" });
|
|
81
|
+
* const color = useThemeValue({ light: "#fff", dark: "#000", purple: "#1a0a2e" });
|
|
82
|
+
*/
|
|
83
|
+
declare function useThemeValue<T>(map: Record<string, T>): T | undefined;
|
|
84
|
+
export { useThemeValue, useTheme, ValueObject, ThemedImageProps, ThemedImage, 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,8 +12,6 @@ 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
17
|
var SERVER_SNAPSHOT = { theme: undefined, systemTheme: undefined };
|
|
@@ -84,7 +85,8 @@ function ClientThemeProvider({
|
|
|
84
85
|
enableColorScheme = true,
|
|
85
86
|
themeColor,
|
|
86
87
|
followSystem = false,
|
|
87
|
-
onThemeChange
|
|
88
|
+
onThemeChange,
|
|
89
|
+
initialTheme
|
|
88
90
|
}) {
|
|
89
91
|
const resolvedDefault = defaultTheme ?? (enableSystem ? "system" : "light");
|
|
90
92
|
const storeRef = useRef(createThemeStore());
|
|
@@ -117,9 +119,9 @@ function ClientThemeProvider({
|
|
|
117
119
|
}
|
|
118
120
|
for (const attr of attrs) {
|
|
119
121
|
if (attr === "class") {
|
|
120
|
-
const toRemove = themes.
|
|
122
|
+
const toRemove = themes.flatMap((t) => (valueMap?.[t] ?? t).split(" "));
|
|
121
123
|
el.classList.remove(...toRemove);
|
|
122
|
-
el.classList.add(attrValue);
|
|
124
|
+
el.classList.add(...attrValue.split(" "));
|
|
123
125
|
} else {
|
|
124
126
|
el.setAttribute(attr, attrValue);
|
|
125
127
|
}
|
|
@@ -147,6 +149,15 @@ function ClientThemeProvider({
|
|
|
147
149
|
if (forcedTheme) {
|
|
148
150
|
setStoreTheme(forcedTheme);
|
|
149
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 {}
|
|
150
161
|
} else {
|
|
151
162
|
let stored = null;
|
|
152
163
|
try {
|
|
@@ -155,7 +166,7 @@ function ClientThemeProvider({
|
|
|
155
166
|
stored = s.getItem(storageKey);
|
|
156
167
|
}
|
|
157
168
|
} catch {}
|
|
158
|
-
const initial = stored && themes.includes(stored) ? stored : resolvedDefault;
|
|
169
|
+
const initial = !followSystem && stored && themes.includes(stored) ? stored : resolvedDefault;
|
|
159
170
|
setStoreTheme(initial);
|
|
160
171
|
applyToDom(initial === "system" ? sys ?? "light" : initial);
|
|
161
172
|
}
|
|
@@ -177,6 +188,7 @@ function ClientThemeProvider({
|
|
|
177
188
|
return () => mq.removeEventListener("change", handler);
|
|
178
189
|
}, [
|
|
179
190
|
forcedTheme,
|
|
191
|
+
initialTheme,
|
|
180
192
|
resolvedDefault,
|
|
181
193
|
storage,
|
|
182
194
|
storageKey,
|
|
@@ -203,10 +215,10 @@ function ClientThemeProvider({
|
|
|
203
215
|
};
|
|
204
216
|
}, [applyToDom, forcedTheme, getSnapshot]);
|
|
205
217
|
useEffect(() => {
|
|
206
|
-
if (storage === "none")
|
|
218
|
+
if (storage === "none" || storage === "sessionStorage")
|
|
207
219
|
return;
|
|
208
220
|
const handler = (e) => {
|
|
209
|
-
if (e.key !== storageKey || !e.newValue)
|
|
221
|
+
if (e.storageArea !== localStorage || e.key !== storageKey || !e.newValue)
|
|
210
222
|
return;
|
|
211
223
|
if (themes.includes(e.newValue)) {
|
|
212
224
|
const newTheme = e.newValue;
|
|
@@ -247,12 +259,13 @@ function ClientThemeProvider({
|
|
|
247
259
|
children
|
|
248
260
|
}, undefined, false, undefined, this);
|
|
249
261
|
}
|
|
250
|
-
|
|
251
262
|
// src/script.ts
|
|
252
|
-
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) {
|
|
253
264
|
let theme;
|
|
254
265
|
if (forcedTheme) {
|
|
255
266
|
theme = forcedTheme;
|
|
267
|
+
} else if (initialTheme && themes.includes(initialTheme)) {
|
|
268
|
+
theme = initialTheme;
|
|
256
269
|
} else {
|
|
257
270
|
let stored = null;
|
|
258
271
|
try {
|
|
@@ -273,9 +286,9 @@ function themeScript(storageKey, attribute, defaultTheme, enableSystem, enableCo
|
|
|
273
286
|
const attrs = Array.isArray(attribute) ? attribute : [attribute];
|
|
274
287
|
for (const attr of attrs) {
|
|
275
288
|
if (attr === "class") {
|
|
276
|
-
const toRemove = themes.
|
|
289
|
+
const toRemove = themes.flatMap((t) => (value?.[t] || t).split(" "));
|
|
277
290
|
el.classList.remove(...toRemove);
|
|
278
|
-
el.classList.add(attrValue);
|
|
291
|
+
el.classList.add(...attrValue.split(" "));
|
|
279
292
|
} else {
|
|
280
293
|
el.setAttribute(attr, attrValue);
|
|
281
294
|
}
|
|
@@ -309,7 +322,8 @@ function getScript(config) {
|
|
|
309
322
|
JSON.stringify(config.value ?? null),
|
|
310
323
|
JSON.stringify(config.target),
|
|
311
324
|
JSON.stringify(config.storage),
|
|
312
|
-
JSON.stringify(config.themeColors ?? null)
|
|
325
|
+
JSON.stringify(config.themeColors ?? null),
|
|
326
|
+
JSON.stringify(config.initialTheme ?? null)
|
|
313
327
|
].join(",");
|
|
314
328
|
return `(${fn})(${args})`;
|
|
315
329
|
}
|
|
@@ -333,7 +347,8 @@ function ThemeProvider({
|
|
|
333
347
|
nonce,
|
|
334
348
|
onThemeChange,
|
|
335
349
|
themeColor,
|
|
336
|
-
followSystem = false
|
|
350
|
+
followSystem = false,
|
|
351
|
+
initialTheme
|
|
337
352
|
}) {
|
|
338
353
|
const resolvedDefault = defaultTheme ?? (enableSystem ? "system" : "light");
|
|
339
354
|
return /* @__PURE__ */ jsxDEV2(Fragment, {
|
|
@@ -351,7 +366,8 @@ function ThemeProvider({
|
|
|
351
366
|
value: valueMap,
|
|
352
367
|
target,
|
|
353
368
|
storage,
|
|
354
|
-
themeColors: themeColor
|
|
369
|
+
themeColors: themeColor,
|
|
370
|
+
initialTheme
|
|
355
371
|
})
|
|
356
372
|
},
|
|
357
373
|
nonce
|
|
@@ -371,12 +387,42 @@ function ThemeProvider({
|
|
|
371
387
|
themeColor,
|
|
372
388
|
followSystem,
|
|
373
389
|
onThemeChange,
|
|
390
|
+
initialTheme,
|
|
374
391
|
children
|
|
375
392
|
}, undefined, false, undefined, this)
|
|
376
393
|
]
|
|
377
394
|
}, undefined, true, undefined, this);
|
|
378
395
|
}
|
|
396
|
+
// src/themed-image.tsx
|
|
397
|
+
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
398
|
+
|
|
399
|
+
var TRANSPARENT_FALLBACK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
400
|
+
function ThemedImage({
|
|
401
|
+
src,
|
|
402
|
+
fallback = TRANSPARENT_FALLBACK,
|
|
403
|
+
alt,
|
|
404
|
+
...props
|
|
405
|
+
}) {
|
|
406
|
+
const { resolvedTheme } = useTheme();
|
|
407
|
+
const resolvedSrc = resolvedTheme && src[resolvedTheme] || fallback;
|
|
408
|
+
return /* @__PURE__ */ jsxDEV3("img", {
|
|
409
|
+
src: resolvedSrc,
|
|
410
|
+
alt,
|
|
411
|
+
...props
|
|
412
|
+
}, undefined, false, undefined, this);
|
|
413
|
+
}
|
|
414
|
+
// src/use-theme-value.ts
|
|
415
|
+
|
|
416
|
+
function useThemeValue(map) {
|
|
417
|
+
const { resolvedTheme } = useTheme();
|
|
418
|
+
if (!resolvedTheme)
|
|
419
|
+
return;
|
|
420
|
+
return map[resolvedTheme];
|
|
421
|
+
}
|
|
379
422
|
export {
|
|
423
|
+
useThemeValue,
|
|
380
424
|
useTheme,
|
|
381
|
-
|
|
425
|
+
ThemedImage,
|
|
426
|
+
ThemeProvider,
|
|
427
|
+
ClientThemeProvider
|
|
382
428
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrksz/themes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A modern, fully-featured theme management library for Next.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@biomejs/biome": "^2.4.8",
|
|
25
|
+
"@testing-library/react": "^16.3.2",
|
|
25
26
|
"@types/bun": "^1.3.11",
|
|
26
27
|
"@types/react": "^19.2.14",
|
|
27
28
|
"@types/react-dom": "^19.2.3",
|