@stackwright/themes 0.4.1 → 0.5.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/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
3
  import React, { ReactNode } from 'react';
3
4
 
4
5
  declare const componentStyleSchema: z.ZodObject<{
@@ -10,6 +11,17 @@ declare const componentStyleSchema: z.ZodObject<{
10
11
  nav: z.ZodOptional<z.ZodString>;
11
12
  text: z.ZodOptional<z.ZodString>;
12
13
  }, z.core.$catchall<z.ZodOptional<z.ZodString>>>;
14
+ declare const colorsSchema: z.ZodObject<{
15
+ primary: z.ZodString;
16
+ secondary: z.ZodString;
17
+ accent: z.ZodString;
18
+ background: z.ZodString;
19
+ surface: z.ZodString;
20
+ text: z.ZodString;
21
+ textSecondary: z.ZodString;
22
+ }, z.core.$strip>;
23
+ type ThemeColors = z.infer<typeof colorsSchema>;
24
+ type ColorMode = 'light' | 'dark' | 'system';
13
25
  declare const themeConfigSchema: z.ZodObject<{
14
26
  id: z.ZodString;
15
27
  name: z.ZodString;
@@ -23,6 +35,15 @@ declare const themeConfigSchema: z.ZodObject<{
23
35
  text: z.ZodString;
24
36
  textSecondary: z.ZodString;
25
37
  }, z.core.$strip>;
38
+ darkColors: z.ZodOptional<z.ZodObject<{
39
+ primary: z.ZodString;
40
+ secondary: z.ZodString;
41
+ accent: z.ZodString;
42
+ background: z.ZodString;
43
+ surface: z.ZodString;
44
+ text: z.ZodString;
45
+ textSecondary: z.ZodString;
46
+ }, z.core.$strip>>;
26
47
  backgroundImage: z.ZodOptional<z.ZodObject<{
27
48
  url: z.ZodString;
28
49
  repeat: z.ZodOptional<z.ZodEnum<{
@@ -123,6 +144,15 @@ declare const themeSchema: z.ZodObject<{
123
144
  text: z.ZodString;
124
145
  textSecondary: z.ZodString;
125
146
  }, z.core.$strip>;
147
+ darkColors: z.ZodOptional<z.ZodObject<{
148
+ primary: z.ZodString;
149
+ secondary: z.ZodString;
150
+ accent: z.ZodString;
151
+ background: z.ZodString;
152
+ surface: z.ZodString;
153
+ text: z.ZodString;
154
+ textSecondary: z.ZodString;
155
+ }, z.core.$strip>>;
126
156
  backgroundImage: z.ZodOptional<z.ZodObject<{
127
157
  url: z.ZodString;
128
158
  repeat: z.ZodOptional<z.ZodEnum<{
@@ -216,15 +246,57 @@ interface Theme extends ThemeConfig {
216
246
  }
217
247
 
218
248
  interface ThemeContextType {
249
+ /** The resolved theme — `colors` reflects the active color mode. */
219
250
  theme: Theme;
251
+ /** The original theme with both `colors` and `darkColors` intact. */
252
+ rawTheme: Theme;
220
253
  setTheme: (theme: Theme) => void;
254
+ /** Current color mode setting (`'light'`, `'dark'`, or `'system'`). */
255
+ colorMode: ColorMode;
256
+ /** Switch the color mode. */
257
+ setColorMode: (mode: ColorMode) => void;
258
+ /** The actually active mode after resolving `'system'`. */
259
+ resolvedColorMode: 'light' | 'dark';
221
260
  }
222
261
  interface ThemeProviderProps {
223
262
  theme: Theme;
224
263
  children: ReactNode;
264
+ /** Initial color mode. Defaults to `'system'`. */
265
+ initialColorMode?: ColorMode;
225
266
  }
226
267
  declare const ThemeProvider: React.FC<ThemeProviderProps>;
268
+ /**
269
+ * Access the current theme and color mode controls.
270
+ * Must be called inside a `<ThemeProvider>`.
271
+ */
227
272
  declare const useTheme: () => ThemeContextType;
273
+ /**
274
+ * Like `useTheme`, but returns `undefined` instead of throwing when
275
+ * no `ThemeProvider` is present. Useful for optional-context components
276
+ * like `ThemeStyleInjector`.
277
+ */
278
+ declare const useThemeOptional: () => ThemeContextType | undefined;
279
+ /**
280
+ * Converts a Stackwright Theme to CSS custom properties.
281
+ * Inject these via ThemeStyleInjector or a `<style>` tag.
282
+ */
283
+ declare function themeToCSSVars(theme: Theme): Record<string, string>;
284
+ interface ThemeStyleInjectorProps {
285
+ /**
286
+ * Explicit theme override. When omitted the resolved theme from the
287
+ * nearest `ThemeProvider` is used automatically — this is the
288
+ * recommended approach so CSS vars stay in sync with color mode changes.
289
+ */
290
+ theme?: Theme;
291
+ children: ReactNode;
292
+ className?: string;
293
+ }
294
+ /**
295
+ * Wraps children in a div that injects Stackwright CSS custom properties.
296
+ * Place this inside a `<ThemeProvider>` and it will track color mode
297
+ * changes automatically.
298
+ */
299
+ declare function ThemeStyleInjector({ theme: themeProp, children, className, }: ThemeStyleInjectorProps): react_jsx_runtime.JSX.Element;
228
300
 
229
301
  declare class ThemeLoader {
230
302
  private static themes;
@@ -237,4 +309,23 @@ declare class ThemeLoader {
237
309
  private static getEmbeddedTheme;
238
310
  }
239
311
 
240
- export { type ComponentStyle, type Theme, type ThemeConfig, ThemeLoader, ThemeProvider, componentStyleSchema, themeConfigSchema, themeSchema, useTheme };
312
+ /**
313
+ * Blocking script that reads the `sw-color-mode` cookie and applies the
314
+ * correct color mode attribute to `<html>` before React hydrates.
315
+ *
316
+ * Place this in `_document.tsx` `<Head>` (Pages Router) or in a `<script>`
317
+ * tag in `layout.tsx` (App Router). It must execute before the body is
318
+ * painted — intentionally render-blocking.
319
+ *
320
+ * The `data-sw-color-mode` attribute set by this script is read by
321
+ * `ThemeProvider`'s `systemPreference` initialiser, ensuring the first
322
+ * React render matches the visible state — no hydration mismatch.
323
+ *
324
+ * For return visitors (cookie exists): correct theme on first paint, zero flash.
325
+ * For first-time visitors with OS dark mode: one-frame flash (same as next-themes v0.2).
326
+ */
327
+ declare function ColorModeScript({ fallback }: {
328
+ fallback?: ColorMode;
329
+ }): react_jsx_runtime.JSX.Element;
330
+
331
+ export { type ColorMode, ColorModeScript, type ComponentStyle, type Theme, type ThemeColors, type ThemeColors as ThemeColorsType, type ThemeConfig, ThemeLoader, ThemeProvider, ThemeStyleInjector, colorsSchema, componentStyleSchema, themeConfigSchema, themeSchema, themeToCSSVars, useTheme, useThemeOptional };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
3
  import React, { ReactNode } from 'react';
3
4
 
4
5
  declare const componentStyleSchema: z.ZodObject<{
@@ -10,6 +11,17 @@ declare const componentStyleSchema: z.ZodObject<{
10
11
  nav: z.ZodOptional<z.ZodString>;
11
12
  text: z.ZodOptional<z.ZodString>;
12
13
  }, z.core.$catchall<z.ZodOptional<z.ZodString>>>;
14
+ declare const colorsSchema: z.ZodObject<{
15
+ primary: z.ZodString;
16
+ secondary: z.ZodString;
17
+ accent: z.ZodString;
18
+ background: z.ZodString;
19
+ surface: z.ZodString;
20
+ text: z.ZodString;
21
+ textSecondary: z.ZodString;
22
+ }, z.core.$strip>;
23
+ type ThemeColors = z.infer<typeof colorsSchema>;
24
+ type ColorMode = 'light' | 'dark' | 'system';
13
25
  declare const themeConfigSchema: z.ZodObject<{
14
26
  id: z.ZodString;
15
27
  name: z.ZodString;
@@ -23,6 +35,15 @@ declare const themeConfigSchema: z.ZodObject<{
23
35
  text: z.ZodString;
24
36
  textSecondary: z.ZodString;
25
37
  }, z.core.$strip>;
38
+ darkColors: z.ZodOptional<z.ZodObject<{
39
+ primary: z.ZodString;
40
+ secondary: z.ZodString;
41
+ accent: z.ZodString;
42
+ background: z.ZodString;
43
+ surface: z.ZodString;
44
+ text: z.ZodString;
45
+ textSecondary: z.ZodString;
46
+ }, z.core.$strip>>;
26
47
  backgroundImage: z.ZodOptional<z.ZodObject<{
27
48
  url: z.ZodString;
28
49
  repeat: z.ZodOptional<z.ZodEnum<{
@@ -123,6 +144,15 @@ declare const themeSchema: z.ZodObject<{
123
144
  text: z.ZodString;
124
145
  textSecondary: z.ZodString;
125
146
  }, z.core.$strip>;
147
+ darkColors: z.ZodOptional<z.ZodObject<{
148
+ primary: z.ZodString;
149
+ secondary: z.ZodString;
150
+ accent: z.ZodString;
151
+ background: z.ZodString;
152
+ surface: z.ZodString;
153
+ text: z.ZodString;
154
+ textSecondary: z.ZodString;
155
+ }, z.core.$strip>>;
126
156
  backgroundImage: z.ZodOptional<z.ZodObject<{
127
157
  url: z.ZodString;
128
158
  repeat: z.ZodOptional<z.ZodEnum<{
@@ -216,15 +246,57 @@ interface Theme extends ThemeConfig {
216
246
  }
217
247
 
218
248
  interface ThemeContextType {
249
+ /** The resolved theme — `colors` reflects the active color mode. */
219
250
  theme: Theme;
251
+ /** The original theme with both `colors` and `darkColors` intact. */
252
+ rawTheme: Theme;
220
253
  setTheme: (theme: Theme) => void;
254
+ /** Current color mode setting (`'light'`, `'dark'`, or `'system'`). */
255
+ colorMode: ColorMode;
256
+ /** Switch the color mode. */
257
+ setColorMode: (mode: ColorMode) => void;
258
+ /** The actually active mode after resolving `'system'`. */
259
+ resolvedColorMode: 'light' | 'dark';
221
260
  }
222
261
  interface ThemeProviderProps {
223
262
  theme: Theme;
224
263
  children: ReactNode;
264
+ /** Initial color mode. Defaults to `'system'`. */
265
+ initialColorMode?: ColorMode;
225
266
  }
226
267
  declare const ThemeProvider: React.FC<ThemeProviderProps>;
268
+ /**
269
+ * Access the current theme and color mode controls.
270
+ * Must be called inside a `<ThemeProvider>`.
271
+ */
227
272
  declare const useTheme: () => ThemeContextType;
273
+ /**
274
+ * Like `useTheme`, but returns `undefined` instead of throwing when
275
+ * no `ThemeProvider` is present. Useful for optional-context components
276
+ * like `ThemeStyleInjector`.
277
+ */
278
+ declare const useThemeOptional: () => ThemeContextType | undefined;
279
+ /**
280
+ * Converts a Stackwright Theme to CSS custom properties.
281
+ * Inject these via ThemeStyleInjector or a `<style>` tag.
282
+ */
283
+ declare function themeToCSSVars(theme: Theme): Record<string, string>;
284
+ interface ThemeStyleInjectorProps {
285
+ /**
286
+ * Explicit theme override. When omitted the resolved theme from the
287
+ * nearest `ThemeProvider` is used automatically — this is the
288
+ * recommended approach so CSS vars stay in sync with color mode changes.
289
+ */
290
+ theme?: Theme;
291
+ children: ReactNode;
292
+ className?: string;
293
+ }
294
+ /**
295
+ * Wraps children in a div that injects Stackwright CSS custom properties.
296
+ * Place this inside a `<ThemeProvider>` and it will track color mode
297
+ * changes automatically.
298
+ */
299
+ declare function ThemeStyleInjector({ theme: themeProp, children, className, }: ThemeStyleInjectorProps): react_jsx_runtime.JSX.Element;
228
300
 
229
301
  declare class ThemeLoader {
230
302
  private static themes;
@@ -237,4 +309,23 @@ declare class ThemeLoader {
237
309
  private static getEmbeddedTheme;
238
310
  }
239
311
 
240
- export { type ComponentStyle, type Theme, type ThemeConfig, ThemeLoader, ThemeProvider, componentStyleSchema, themeConfigSchema, themeSchema, useTheme };
312
+ /**
313
+ * Blocking script that reads the `sw-color-mode` cookie and applies the
314
+ * correct color mode attribute to `<html>` before React hydrates.
315
+ *
316
+ * Place this in `_document.tsx` `<Head>` (Pages Router) or in a `<script>`
317
+ * tag in `layout.tsx` (App Router). It must execute before the body is
318
+ * painted — intentionally render-blocking.
319
+ *
320
+ * The `data-sw-color-mode` attribute set by this script is read by
321
+ * `ThemeProvider`'s `systemPreference` initialiser, ensuring the first
322
+ * React render matches the visible state — no hydration mismatch.
323
+ *
324
+ * For return visitors (cookie exists): correct theme on first paint, zero flash.
325
+ * For first-time visitors with OS dark mode: one-frame flash (same as next-themes v0.2).
326
+ */
327
+ declare function ColorModeScript({ fallback }: {
328
+ fallback?: ColorMode;
329
+ }): react_jsx_runtime.JSX.Element;
330
+
331
+ export { type ColorMode, ColorModeScript, type ComponentStyle, type Theme, type ThemeColors, type ThemeColors as ThemeColorsType, type ThemeConfig, ThemeLoader, ThemeProvider, ThemeStyleInjector, colorsSchema, componentStyleSchema, themeConfigSchema, themeSchema, themeToCSSVars, useTheme, useThemeOptional };
package/dist/index.js CHANGED
@@ -30,12 +30,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ ColorModeScript: () => ColorModeScript,
33
34
  ThemeLoader: () => ThemeLoader,
34
35
  ThemeProvider: () => ThemeProvider,
36
+ ThemeStyleInjector: () => ThemeStyleInjector,
37
+ colorsSchema: () => colorsSchema,
35
38
  componentStyleSchema: () => componentStyleSchema,
36
39
  themeConfigSchema: () => themeConfigSchema,
37
40
  themeSchema: () => themeSchema,
38
- useTheme: () => useTheme
41
+ themeToCSSVars: () => themeToCSSVars,
42
+ useTheme: () => useTheme,
43
+ useThemeOptional: () => useThemeOptional
39
44
  });
40
45
  module.exports = __toCommonJS(index_exports);
41
46
 
@@ -50,19 +55,21 @@ var componentStyleSchema = import_zod.z.object({
50
55
  nav: import_zod.z.string().optional(),
51
56
  text: import_zod.z.string().optional()
52
57
  }).catchall(import_zod.z.string().optional());
58
+ var colorsSchema = import_zod.z.object({
59
+ primary: import_zod.z.string(),
60
+ secondary: import_zod.z.string(),
61
+ accent: import_zod.z.string(),
62
+ background: import_zod.z.string(),
63
+ surface: import_zod.z.string(),
64
+ text: import_zod.z.string(),
65
+ textSecondary: import_zod.z.string()
66
+ });
53
67
  var themeConfigSchema = import_zod.z.object({
54
68
  id: import_zod.z.string(),
55
69
  name: import_zod.z.string(),
56
70
  description: import_zod.z.string(),
57
- colors: import_zod.z.object({
58
- primary: import_zod.z.string(),
59
- secondary: import_zod.z.string(),
60
- accent: import_zod.z.string(),
61
- background: import_zod.z.string(),
62
- surface: import_zod.z.string(),
63
- text: import_zod.z.string(),
64
- textSecondary: import_zod.z.string()
65
- }),
71
+ colors: colorsSchema,
72
+ darkColors: colorsSchema.optional(),
66
73
  backgroundImage: import_zod.z.object({
67
74
  url: import_zod.z.string(),
68
75
  repeat: import_zod.z.enum(["repeat", "repeat-x", "repeat-y", "no-repeat"]).optional(),
@@ -109,9 +116,88 @@ var themeSchema = themeConfigSchema;
109
116
  var import_react = require("react");
110
117
  var import_jsx_runtime = require("react/jsx-runtime");
111
118
  var ThemeContext = (0, import_react.createContext)(void 0);
112
- var ThemeProvider = ({ theme: initialTheme, children }) => {
113
- const [theme, setTheme] = (0, import_react.useState)(initialTheme);
114
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value: { theme, setTheme }, children });
119
+ var COLOR_MODE_COOKIE = "sw-color-mode";
120
+ function getSystemPreference() {
121
+ if (typeof window === "undefined") return "light";
122
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
123
+ }
124
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? import_react.useLayoutEffect : import_react.useEffect;
125
+ function resolveColors(theme, mode) {
126
+ if (mode === "dark" && theme.darkColors) {
127
+ return theme.darkColors;
128
+ }
129
+ return theme.colors;
130
+ }
131
+ function readCookie(name) {
132
+ if (typeof document === "undefined") return void 0;
133
+ const match = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
134
+ return match ? decodeURIComponent(match[1]) : void 0;
135
+ }
136
+ function writeCookie(name, value) {
137
+ if (typeof document === "undefined") return;
138
+ const maxAge = 365 * 24 * 60 * 60;
139
+ document.cookie = name + "=" + encodeURIComponent(value) + "; max-age=" + maxAge + "; path=/; SameSite=Lax";
140
+ }
141
+ function deleteCookie(name) {
142
+ if (typeof document === "undefined") return;
143
+ document.cookie = name + "=; max-age=0; path=/";
144
+ }
145
+ var ThemeProvider = ({
146
+ theme: initialTheme,
147
+ children,
148
+ initialColorMode = "system"
149
+ }) => {
150
+ const [rawTheme, setRawTheme] = (0, import_react.useState)(initialTheme);
151
+ const [colorMode, setColorModeState] = (0, import_react.useState)(initialColorMode);
152
+ const [systemPreference, setSystemPreference] = (0, import_react.useState)("light");
153
+ useIsomorphicLayoutEffect(() => {
154
+ const saved = readCookie(COLOR_MODE_COOKIE);
155
+ if (saved === "light" || saved === "dark") {
156
+ setColorModeState(saved);
157
+ }
158
+ const attr = document.documentElement.getAttribute("data-sw-color-mode");
159
+ if (attr === "dark" || attr === "light") {
160
+ setSystemPreference(attr);
161
+ } else {
162
+ setSystemPreference(getSystemPreference());
163
+ }
164
+ }, []);
165
+ (0, import_react.useEffect)(() => {
166
+ if (typeof window === "undefined") return;
167
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
168
+ const handler = (e) => {
169
+ setSystemPreference(e.matches ? "dark" : "light");
170
+ };
171
+ mql.addEventListener("change", handler);
172
+ return () => mql.removeEventListener("change", handler);
173
+ }, []);
174
+ const setColorMode = (0, import_react.useCallback)((mode) => {
175
+ setColorModeState(mode);
176
+ if (mode === "system") {
177
+ deleteCookie(COLOR_MODE_COOKIE);
178
+ if (typeof document !== "undefined") {
179
+ const systemMode = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
180
+ document.documentElement.setAttribute("data-sw-color-mode", systemMode);
181
+ }
182
+ } else {
183
+ writeCookie(COLOR_MODE_COOKIE, mode);
184
+ if (typeof document !== "undefined") {
185
+ document.documentElement.setAttribute("data-sw-color-mode", mode);
186
+ }
187
+ }
188
+ }, []);
189
+ const resolvedColorMode = colorMode === "system" ? systemPreference : colorMode;
190
+ const theme = (0, import_react.useMemo)(() => {
191
+ const effectiveColors = resolveColors(rawTheme, resolvedColorMode);
192
+ if (effectiveColors === rawTheme.colors) return rawTheme;
193
+ return { ...rawTheme, colors: effectiveColors };
194
+ }, [rawTheme, resolvedColorMode]);
195
+ const setTheme = (0, import_react.useCallback)((t) => setRawTheme(t), []);
196
+ const value = (0, import_react.useMemo)(
197
+ () => ({ theme, rawTheme, setTheme, colorMode, setColorMode, resolvedColorMode }),
198
+ [theme, rawTheme, setTheme, colorMode, setColorMode, resolvedColorMode]
199
+ );
200
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value, children });
115
201
  };
116
202
  var useTheme = () => {
117
203
  const context = (0, import_react.useContext)(ThemeContext);
@@ -120,6 +206,41 @@ var useTheme = () => {
120
206
  }
121
207
  return context;
122
208
  };
209
+ var useThemeOptional = () => {
210
+ return (0, import_react.useContext)(ThemeContext);
211
+ };
212
+ function themeToCSSVars(theme) {
213
+ return {
214
+ "--sw-color-primary": theme.colors.primary,
215
+ "--sw-color-secondary": theme.colors.secondary,
216
+ "--sw-color-accent": theme.colors.accent,
217
+ "--sw-color-bg": theme.colors.background,
218
+ "--sw-color-surface": theme.colors.surface,
219
+ "--sw-color-text": theme.colors.text,
220
+ "--sw-color-text-secondary": theme.colors.textSecondary,
221
+ "--sw-font-primary": theme.typography?.fontFamily?.primary ?? "sans-serif",
222
+ "--sw-font-secondary": theme.typography?.fontFamily?.secondary ?? "sans-serif",
223
+ "--sw-spacing-xs": theme.spacing?.xs ?? "0.25rem",
224
+ "--sw-spacing-sm": theme.spacing?.sm ?? "0.5rem",
225
+ "--sw-spacing-md": theme.spacing?.md ?? "1rem",
226
+ "--sw-spacing-lg": theme.spacing?.lg ?? "1.5rem",
227
+ "--sw-spacing-xl": theme.spacing?.xl ?? "2rem",
228
+ "--sw-spacing-2xl": theme.spacing?.["2xl"] ?? "3rem"
229
+ };
230
+ }
231
+ function ThemeStyleInjector({
232
+ theme: themeProp,
233
+ children,
234
+ className
235
+ }) {
236
+ const ctx = useThemeOptional();
237
+ const theme = themeProp ?? ctx?.theme;
238
+ if (!theme) {
239
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
240
+ }
241
+ const cssVars = themeToCSSVars(theme);
242
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: cssVars, children });
243
+ }
123
244
 
124
245
  // src/themeLoader.ts
125
246
  var yaml = __toESM(require("js-yaml"));
@@ -146,7 +267,6 @@ var ThemeLoader = class {
146
267
  }
147
268
  static registerCustomTheme(theme) {
148
269
  this.themes.set(theme.name, theme);
149
- console.log(`\u{1F3A8} Registered custom theme: ${theme.name}`);
150
270
  }
151
271
  static loadCustomTheme(theme) {
152
272
  this.registerCustomTheme(theme);
@@ -166,6 +286,14 @@ colors:
166
286
  surface: "#ffffff"
167
287
  text: "#1f2937"
168
288
  textSecondary: "#6b7280"
289
+ darkColors:
290
+ primary: "#fbbf24"
291
+ secondary: "#94a3b8"
292
+ accent: "#f59e0b"
293
+ background: "#0f172a"
294
+ surface: "#1e293b"
295
+ text: "#f1f5f9"
296
+ textSecondary: "#94a3b8"
169
297
  typography:
170
298
  fontFamily:
171
299
  primary: "Roboto, sans-serif"
@@ -198,6 +326,14 @@ colors:
198
326
  surface: "#ffffff"
199
327
  text: "#374151"
200
328
  textSecondary: "#9ca3af"
329
+ darkColors:
330
+ primary: "#f472b6"
331
+ secondary: "#9ca3af"
332
+ accent: "#ec4899"
333
+ background: "#111827"
334
+ surface: "#1f2937"
335
+ text: "#f9fafb"
336
+ textSecondary: "#9ca3af"
201
337
  typography:
202
338
  fontFamily:
203
339
  primary: "Roboto, sans-serif"
@@ -225,13 +361,36 @@ spacing:
225
361
  return themes[name];
226
362
  }
227
363
  };
364
+
365
+ // src/ColorModeScript.tsx
366
+ var import_jsx_runtime2 = require("react/jsx-runtime");
367
+ function ColorModeScript({ fallback = "system" }) {
368
+ const scriptContent = `
369
+ (function(){
370
+ try {
371
+ var m = document.cookie.match(/(?:^|; )sw-color-mode=([^;]*)/);
372
+ var mode = m ? m[1] : '${fallback}';
373
+ if (mode === 'system') {
374
+ mode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
375
+ }
376
+ document.documentElement.setAttribute('data-sw-color-mode', mode);
377
+ } catch(e) {}
378
+ })();
379
+ `.trim();
380
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("script", { dangerouslySetInnerHTML: { __html: scriptContent } });
381
+ }
228
382
  // Annotate the CommonJS export names for ESM import in node:
229
383
  0 && (module.exports = {
384
+ ColorModeScript,
230
385
  ThemeLoader,
231
386
  ThemeProvider,
387
+ ThemeStyleInjector,
388
+ colorsSchema,
232
389
  componentStyleSchema,
233
390
  themeConfigSchema,
234
391
  themeSchema,
235
- useTheme
392
+ themeToCSSVars,
393
+ useTheme,
394
+ useThemeOptional
236
395
  });
237
396
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/ThemeProvider.tsx","../src/themeLoader.ts"],"sourcesContent":["export * from './types';\nexport * from './ThemeProvider';\nexport * from './themeLoader';\nexport type { ThemeConfig, Theme, ComponentStyle } from './types';\nexport { componentStyleSchema, themeConfigSchema, themeSchema } from './types';","import { z } from 'zod';\n\nexport const componentStyleSchema = z.object({\n base: z.string().optional(),\n primary: z.string().optional(),\n secondary: z.string().optional(),\n outline: z.string().optional(),\n shadow: z.string().optional(),\n nav: z.string().optional(),\n text: z.string().optional(),\n}).catchall(z.string().optional());\n\nexport const themeConfigSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string(),\n colors: z.object({\n primary: z.string(),\n secondary: z.string(),\n accent: z.string(),\n background: z.string(),\n surface: z.string(),\n text: z.string(),\n textSecondary: z.string(),\n }),\n backgroundImage: z.object({\n url: z.string(),\n repeat: z.enum(['repeat', 'repeat-x', 'repeat-y', 'no-repeat']).optional(),\n size: z.string().optional(),\n position: z.string().optional(),\n attachment: z.enum(['scroll', 'fixed', 'local']).optional(),\n scale: z.number().optional(),\n animation: z.enum(['drift', 'float', 'shimmer', 'shimmer-float', 'none']).optional(),\n customAnimation: z.string().optional(),\n }).optional(),\n typography: z.object({\n fontFamily: z.object({\n primary: z.string(),\n secondary: z.string(),\n }),\n scale: z.object({\n xs: z.string(),\n sm: z.string(),\n base: z.string(),\n lg: z.string(),\n xl: z.string(),\n '2xl': z.string(),\n '3xl': z.string(),\n }),\n }),\n spacing: z.object({\n xs: z.string(),\n sm: z.string(),\n md: z.string(),\n lg: z.string(),\n xl: z.string(),\n '2xl': z.string(),\n }),\n components: z.object({\n button: componentStyleSchema.optional(),\n card: componentStyleSchema.optional(),\n header: componentStyleSchema.optional(),\n footer: componentStyleSchema.optional(),\n }).optional(),\n});\n\nexport const themeSchema = themeConfigSchema;\n\nexport type ThemeConfig = z.infer<typeof themeConfigSchema>;\nexport type ComponentStyle = z.infer<typeof componentStyleSchema>;\nexport interface Theme extends ThemeConfig {}\n","import React, { createContext, useContext, useState, ReactNode } from 'react';\nimport { Theme } from './types';\n\ninterface ThemeContextType {\n theme: Theme;\n setTheme: (theme: Theme) => void;\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\ninterface ThemeProviderProps {\n theme: Theme;\n children: ReactNode;\n}\n\nexport const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme: initialTheme, children }) => {\n const [theme, setTheme] = useState<Theme>(initialTheme);\n return (\n <ThemeContext.Provider value={{ theme, setTheme }}>\n {children}\n </ThemeContext.Provider>\n );\n};\n\nexport const useTheme = (): ThemeContextType => {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n};","import * as yaml from 'js-yaml';\nimport { Theme } from './types';\n\nexport class ThemeLoader {\n private static themes: Map<string, Theme> = new Map();\n\n static loadThemeFromYaml(yamlContent: string): Theme {\n try {\n const theme = yaml.load(yamlContent) as Theme;\n this.themes.set(theme.name, theme);\n return theme;\n } catch (error) {\n throw new Error(`Failed to parse theme YAML: ${error}`);\n }\n }\n\n static loadThemeFromFile(themeName: string): Theme {\n // In a real implementation, this would load from the file system\n // For now, we'll embed the themes\n const themeData = this.getEmbeddedTheme(themeName);\n return this.loadThemeFromYaml(themeData);\n }\n\n static getTheme(name: string): Theme | undefined {\n return this.themes.get(name);\n }\n\n static getAllThemes(): Theme[] {\n return Array.from(this.themes.values());\n }\n\n static registerCustomTheme(theme: Theme): void {\n this.themes.set(theme.name, theme);\n console.log(`🎨 Registered custom theme: ${theme.name}`);\n }\n\n static loadCustomTheme(theme: Theme): Theme {\n this.registerCustomTheme(theme);\n return theme;\n }\n\n private static getEmbeddedTheme(name: string): string {\n const themes: Record<string, string> = {\n corporate: `\nid: \"corporate\"\nname: \"Corporate\"\ndescription: \"A professional amber-toned corporate theme\"\ncolors:\n primary: \"#f59e0b\"\n secondary: \"#334155\"\n accent: \"#d97706\"\n background: \"#f8fafc\"\n surface: \"#ffffff\"\n text: \"#1f2937\"\n textSecondary: \"#6b7280\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`,\n soft: `\nid: \"soft\"\nname: \"Soft\"\ndescription: \"A gentle pink-toned soft theme\"\ncolors:\n primary: \"#ec4899\"\n secondary: \"#6b7280\"\n accent: \"#db2777\"\n background: \"#f9fafb\"\n surface: \"#ffffff\"\n text: \"#374151\"\n textSecondary: \"#9ca3af\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`\n };\n\n if (!themes[name]) {\n throw new Error(`Theme '${name}' not found`);\n }\n\n return themes[name];\n }\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/ThemeProvider.tsx","../src/themeLoader.ts","../src/ColorModeScript.tsx"],"sourcesContent":["export * from './types';\nexport * from './ThemeProvider';\nexport * from './themeLoader';\nexport * from './ColorModeScript';\nexport type { ThemeConfig, Theme, ComponentStyle, ThemeColors, ColorMode } from './types';\nexport { colorsSchema, componentStyleSchema, themeConfigSchema, themeSchema } from './types';\n","import { z } from 'zod';\n\nexport const componentStyleSchema = z\n .object({\n base: z.string().optional(),\n primary: z.string().optional(),\n secondary: z.string().optional(),\n outline: z.string().optional(),\n shadow: z.string().optional(),\n nav: z.string().optional(),\n text: z.string().optional(),\n })\n .catchall(z.string().optional());\n\nexport const colorsSchema = z.object({\n primary: z.string(),\n secondary: z.string(),\n accent: z.string(),\n background: z.string(),\n surface: z.string(),\n text: z.string(),\n textSecondary: z.string(),\n});\n\nexport type ThemeColors = z.infer<typeof colorsSchema>;\n\nexport type ColorMode = 'light' | 'dark' | 'system';\n\nexport const themeConfigSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string(),\n colors: colorsSchema,\n darkColors: colorsSchema.optional(),\n backgroundImage: z\n .object({\n url: z.string(),\n repeat: z.enum(['repeat', 'repeat-x', 'repeat-y', 'no-repeat']).optional(),\n size: z.string().optional(),\n position: z.string().optional(),\n attachment: z.enum(['scroll', 'fixed', 'local']).optional(),\n scale: z.number().optional(),\n animation: z.enum(['drift', 'float', 'shimmer', 'shimmer-float', 'none']).optional(),\n customAnimation: z.string().optional(),\n })\n .optional(),\n typography: z.object({\n fontFamily: z.object({\n primary: z.string(),\n secondary: z.string(),\n }),\n scale: z.object({\n xs: z.string(),\n sm: z.string(),\n base: z.string(),\n lg: z.string(),\n xl: z.string(),\n '2xl': z.string(),\n '3xl': z.string(),\n }),\n }),\n spacing: z.object({\n xs: z.string(),\n sm: z.string(),\n md: z.string(),\n lg: z.string(),\n xl: z.string(),\n '2xl': z.string(),\n }),\n components: z\n .object({\n button: componentStyleSchema.optional(),\n card: componentStyleSchema.optional(),\n header: componentStyleSchema.optional(),\n footer: componentStyleSchema.optional(),\n })\n .optional(),\n});\n\nexport const themeSchema = themeConfigSchema;\n\nexport type ThemeConfig = z.infer<typeof themeConfigSchema>;\nexport type ComponentStyle = z.infer<typeof componentStyleSchema>;\nexport interface Theme extends ThemeConfig {}\nexport type { ThemeColors as ThemeColorsType };\n","import React, {\n createContext,\n useContext,\n useState,\n useEffect,\n useLayoutEffect,\n useMemo,\n useCallback,\n ReactNode,\n CSSProperties,\n} from 'react';\nimport { Theme, ColorMode, ThemeColors } from './types';\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface ThemeContextType {\n /** The resolved theme — `colors` reflects the active color mode. */\n theme: Theme;\n /** The original theme with both `colors` and `darkColors` intact. */\n rawTheme: Theme;\n setTheme: (theme: Theme) => void;\n /** Current color mode setting (`'light'`, `'dark'`, or `'system'`). */\n colorMode: ColorMode;\n /** Switch the color mode. */\n setColorMode: (mode: ColorMode) => void;\n /** The actually active mode after resolving `'system'`. */\n resolvedColorMode: 'light' | 'dark';\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst COLOR_MODE_COOKIE = 'sw-color-mode';\n\n/** Detect OS-level dark mode preference via `matchMedia`. SSR-safe. */\nfunction getSystemPreference(): 'light' | 'dark' {\n if (typeof window === 'undefined') return 'light';\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\n/**\n * SSR-safe `useLayoutEffect`. Falls back to `useEffect` on the server to\n * avoid React warnings, but uses `useLayoutEffect` on the client so that\n * color-mode state updates happen before the browser paints — preventing\n * a visible flash of the wrong theme.\n */\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n/** Pick the effective colors for the given mode. */\nfunction resolveColors(theme: Theme, mode: 'light' | 'dark'): ThemeColors {\n if (mode === 'dark' && theme.darkColors) {\n return theme.darkColors;\n }\n return theme.colors;\n}\n\n/**\n * Read a cookie by name. SSR-safe (returns undefined when no document).\n * Inline to avoid circular dependency on @stackwright/core.\n */\nfunction readCookie(name: string): string | undefined {\n if (typeof document === 'undefined') return undefined;\n const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));\n return match ? decodeURIComponent(match[1]) : undefined;\n}\n\n/** Write a cookie. SSR-safe no-op when no document. */\nfunction writeCookie(name: string, value: string): void {\n if (typeof document === 'undefined') return;\n const maxAge = 365 * 24 * 60 * 60;\n document.cookie =\n name + '=' + encodeURIComponent(value) + '; max-age=' + maxAge + '; path=/; SameSite=Lax';\n}\n\n/** Remove a cookie by setting max-age=0. */\nfunction deleteCookie(name: string): void {\n if (typeof document === 'undefined') return;\n document.cookie = name + '=; max-age=0; path=/';\n}\n\n// ---------------------------------------------------------------------------\n// ThemeProvider\n// ---------------------------------------------------------------------------\n\ninterface ThemeProviderProps {\n theme: Theme;\n children: ReactNode;\n /** Initial color mode. Defaults to `'system'`. */\n initialColorMode?: ColorMode;\n}\n\nexport const ThemeProvider: React.FC<ThemeProviderProps> = ({\n theme: initialTheme,\n children,\n initialColorMode = 'system',\n}) => {\n const [rawTheme, setRawTheme] = useState<Theme>(initialTheme);\n const [colorMode, setColorModeState] = useState<ColorMode>(initialColorMode);\n // Always initialise to 'light' — this matches the server render and\n // avoids the hydration mismatch that occurred when the useState\n // initialiser read from the DOM attribute set by ColorModeScript.\n const [systemPreference, setSystemPreference] = useState<'light' | 'dark'>('light');\n\n // After hydration, sync the real color mode from cookie / blocking-script\n // attribute / OS preference. Uses layoutEffect to run before the browser\n // paints, so the user never sees the wrong theme.\n useIsomorphicLayoutEffect(() => {\n const saved = readCookie(COLOR_MODE_COOKIE);\n if (saved === 'light' || saved === 'dark') {\n setColorModeState(saved);\n }\n\n // Trust the blocking ColorModeScript's attribute first, then fall back\n // to matchMedia.\n const attr = document.documentElement.getAttribute('data-sw-color-mode');\n if (attr === 'dark' || attr === 'light') {\n setSystemPreference(attr);\n } else {\n setSystemPreference(getSystemPreference());\n }\n }, []);\n\n // Listen for OS-level color scheme changes.\n useEffect(() => {\n if (typeof window === 'undefined') return;\n const mql = window.matchMedia('(prefers-color-scheme: dark)');\n const handler = (e: MediaQueryListEvent) => {\n setSystemPreference(e.matches ? 'dark' : 'light');\n };\n mql.addEventListener('change', handler);\n return () => mql.removeEventListener('change', handler);\n }, []);\n\n // Persist color mode to cookie on change, and clear when set to 'system'.\n const setColorMode = useCallback((mode: ColorMode) => {\n setColorModeState(mode);\n if (mode === 'system') {\n deleteCookie(COLOR_MODE_COOKIE);\n // Resolve system preference and sync the DOM attribute so CSS\n // variable selectors update without a page reload.\n if (typeof document !== 'undefined') {\n const systemMode = window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light';\n document.documentElement.setAttribute('data-sw-color-mode', systemMode);\n }\n } else {\n writeCookie(COLOR_MODE_COOKIE, mode);\n if (typeof document !== 'undefined') {\n document.documentElement.setAttribute('data-sw-color-mode', mode);\n }\n }\n }, []);\n\n const resolvedColorMode: 'light' | 'dark' = colorMode === 'system' ? systemPreference : colorMode;\n\n // Build the effective theme — swap `colors` for `darkColors` when in dark mode.\n const theme = useMemo<Theme>(() => {\n const effectiveColors = resolveColors(rawTheme, resolvedColorMode);\n if (effectiveColors === rawTheme.colors) return rawTheme;\n return { ...rawTheme, colors: effectiveColors };\n }, [rawTheme, resolvedColorMode]);\n\n const setTheme = useCallback((t: Theme) => setRawTheme(t), []);\n\n const value = useMemo<ThemeContextType>(\n () => ({ theme, rawTheme, setTheme, colorMode, setColorMode, resolvedColorMode }),\n [theme, rawTheme, setTheme, colorMode, setColorMode, resolvedColorMode]\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n};\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Access the current theme and color mode controls.\n * Must be called inside a `<ThemeProvider>`.\n */\nexport const useTheme = (): ThemeContextType => {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n};\n\n/**\n * Like `useTheme`, but returns `undefined` instead of throwing when\n * no `ThemeProvider` is present. Useful for optional-context components\n * like `ThemeStyleInjector`.\n */\nexport const useThemeOptional = (): ThemeContextType | undefined => {\n return useContext(ThemeContext);\n};\n\n// ---------------------------------------------------------------------------\n// CSS custom property helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Converts a Stackwright Theme to CSS custom properties.\n * Inject these via ThemeStyleInjector or a `<style>` tag.\n */\nexport function themeToCSSVars(theme: Theme): Record<string, string> {\n return {\n '--sw-color-primary': theme.colors.primary,\n '--sw-color-secondary': theme.colors.secondary,\n '--sw-color-accent': theme.colors.accent,\n '--sw-color-bg': theme.colors.background,\n '--sw-color-surface': theme.colors.surface,\n '--sw-color-text': theme.colors.text,\n '--sw-color-text-secondary': theme.colors.textSecondary,\n '--sw-font-primary': theme.typography?.fontFamily?.primary ?? 'sans-serif',\n '--sw-font-secondary': theme.typography?.fontFamily?.secondary ?? 'sans-serif',\n '--sw-spacing-xs': theme.spacing?.xs ?? '0.25rem',\n '--sw-spacing-sm': theme.spacing?.sm ?? '0.5rem',\n '--sw-spacing-md': theme.spacing?.md ?? '1rem',\n '--sw-spacing-lg': theme.spacing?.lg ?? '1.5rem',\n '--sw-spacing-xl': theme.spacing?.xl ?? '2rem',\n '--sw-spacing-2xl': theme.spacing?.['2xl'] ?? '3rem',\n };\n}\n\n// ---------------------------------------------------------------------------\n// ThemeStyleInjector\n// ---------------------------------------------------------------------------\n\ninterface ThemeStyleInjectorProps {\n /**\n * Explicit theme override. When omitted the resolved theme from the\n * nearest `ThemeProvider` is used automatically — this is the\n * recommended approach so CSS vars stay in sync with color mode changes.\n */\n theme?: Theme;\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Wraps children in a div that injects Stackwright CSS custom properties.\n * Place this inside a `<ThemeProvider>` and it will track color mode\n * changes automatically.\n */\nexport function ThemeStyleInjector({\n theme: themeProp,\n children,\n className,\n}: ThemeStyleInjectorProps) {\n const ctx = useThemeOptional();\n const theme = themeProp ?? ctx?.theme;\n\n if (!theme) {\n // No theme from prop or context — render children unstyled rather than crashing.\n return <>{children}</>;\n }\n\n const cssVars = themeToCSSVars(theme);\n return (\n <div className={className} style={cssVars as CSSProperties}>\n {children}\n </div>\n );\n}\n","import * as yaml from 'js-yaml';\nimport { Theme } from './types';\n\nexport class ThemeLoader {\n private static themes: Map<string, Theme> = new Map();\n\n static loadThemeFromYaml(yamlContent: string): Theme {\n try {\n const theme = yaml.load(yamlContent) as Theme;\n this.themes.set(theme.name, theme);\n return theme;\n } catch (error) {\n throw new Error(`Failed to parse theme YAML: ${error}`);\n }\n }\n\n static loadThemeFromFile(themeName: string): Theme {\n const themeData = this.getEmbeddedTheme(themeName);\n return this.loadThemeFromYaml(themeData);\n }\n\n static getTheme(name: string): Theme | undefined {\n return this.themes.get(name);\n }\n\n static getAllThemes(): Theme[] {\n return Array.from(this.themes.values());\n }\n\n static registerCustomTheme(theme: Theme): void {\n this.themes.set(theme.name, theme);\n }\n\n static loadCustomTheme(theme: Theme): Theme {\n this.registerCustomTheme(theme);\n return theme;\n }\n\n private static getEmbeddedTheme(name: string): string {\n const themes: Record<string, string> = {\n corporate: `\nid: \"corporate\"\nname: \"Corporate\"\ndescription: \"A professional amber-toned corporate theme\"\ncolors:\n primary: \"#f59e0b\"\n secondary: \"#334155\"\n accent: \"#d97706\"\n background: \"#f8fafc\"\n surface: \"#ffffff\"\n text: \"#1f2937\"\n textSecondary: \"#6b7280\"\ndarkColors:\n primary: \"#fbbf24\"\n secondary: \"#94a3b8\"\n accent: \"#f59e0b\"\n background: \"#0f172a\"\n surface: \"#1e293b\"\n text: \"#f1f5f9\"\n textSecondary: \"#94a3b8\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`,\n soft: `\nid: \"soft\"\nname: \"Soft\"\ndescription: \"A gentle pink-toned soft theme\"\ncolors:\n primary: \"#ec4899\"\n secondary: \"#6b7280\"\n accent: \"#db2777\"\n background: \"#f9fafb\"\n surface: \"#ffffff\"\n text: \"#374151\"\n textSecondary: \"#9ca3af\"\ndarkColors:\n primary: \"#f472b6\"\n secondary: \"#9ca3af\"\n accent: \"#ec4899\"\n background: \"#111827\"\n surface: \"#1f2937\"\n text: \"#f9fafb\"\n textSecondary: \"#9ca3af\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`,\n };\n\n if (!themes[name]) {\n throw new Error(`Theme '${name}' not found`);\n }\n\n return themes[name];\n }\n}\n","import React from 'react';\nimport { ColorMode } from './types';\n\n/**\n * Blocking script that reads the `sw-color-mode` cookie and applies the\n * correct color mode attribute to `<html>` before React hydrates.\n *\n * Place this in `_document.tsx` `<Head>` (Pages Router) or in a `<script>`\n * tag in `layout.tsx` (App Router). It must execute before the body is\n * painted — intentionally render-blocking.\n *\n * The `data-sw-color-mode` attribute set by this script is read by\n * `ThemeProvider`'s `systemPreference` initialiser, ensuring the first\n * React render matches the visible state — no hydration mismatch.\n *\n * For return visitors (cookie exists): correct theme on first paint, zero flash.\n * For first-time visitors with OS dark mode: one-frame flash (same as next-themes v0.2).\n */\nexport function ColorModeScript({ fallback = 'system' }: { fallback?: ColorMode }) {\n // The script is raw JS — no React, no module imports. It reads the cookie\n // and sets a data attribute. ~300 bytes, executes in <1ms.\n const scriptContent = `\n(function(){\n try {\n var m = document.cookie.match(/(?:^|; )sw-color-mode=([^;]*)/);\n var mode = m ? m[1] : '${fallback}';\n if (mode === 'system') {\n mode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n document.documentElement.setAttribute('data-sw-color-mode', mode);\n } catch(e) {}\n})();\n`.trim();\n\n return <script dangerouslySetInnerHTML={{ __html: scriptContent }} />;\n}\n"],"mappings":"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","names":["import_jsx_runtime"]}
package/dist/index.mjs CHANGED
@@ -9,19 +9,21 @@ var componentStyleSchema = z.object({
9
9
  nav: z.string().optional(),
10
10
  text: z.string().optional()
11
11
  }).catchall(z.string().optional());
12
+ var colorsSchema = z.object({
13
+ primary: z.string(),
14
+ secondary: z.string(),
15
+ accent: z.string(),
16
+ background: z.string(),
17
+ surface: z.string(),
18
+ text: z.string(),
19
+ textSecondary: z.string()
20
+ });
12
21
  var themeConfigSchema = z.object({
13
22
  id: z.string(),
14
23
  name: z.string(),
15
24
  description: z.string(),
16
- colors: z.object({
17
- primary: z.string(),
18
- secondary: z.string(),
19
- accent: z.string(),
20
- background: z.string(),
21
- surface: z.string(),
22
- text: z.string(),
23
- textSecondary: z.string()
24
- }),
25
+ colors: colorsSchema,
26
+ darkColors: colorsSchema.optional(),
25
27
  backgroundImage: z.object({
26
28
  url: z.string(),
27
29
  repeat: z.enum(["repeat", "repeat-x", "repeat-y", "no-repeat"]).optional(),
@@ -65,12 +67,99 @@ var themeConfigSchema = z.object({
65
67
  var themeSchema = themeConfigSchema;
66
68
 
67
69
  // src/ThemeProvider.tsx
68
- import { createContext, useContext, useState } from "react";
69
- import { jsx } from "react/jsx-runtime";
70
+ import {
71
+ createContext,
72
+ useContext,
73
+ useState,
74
+ useEffect,
75
+ useLayoutEffect,
76
+ useMemo,
77
+ useCallback
78
+ } from "react";
79
+ import { Fragment, jsx } from "react/jsx-runtime";
70
80
  var ThemeContext = createContext(void 0);
71
- var ThemeProvider = ({ theme: initialTheme, children }) => {
72
- const [theme, setTheme] = useState(initialTheme);
73
- return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, setTheme }, children });
81
+ var COLOR_MODE_COOKIE = "sw-color-mode";
82
+ function getSystemPreference() {
83
+ if (typeof window === "undefined") return "light";
84
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
85
+ }
86
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
87
+ function resolveColors(theme, mode) {
88
+ if (mode === "dark" && theme.darkColors) {
89
+ return theme.darkColors;
90
+ }
91
+ return theme.colors;
92
+ }
93
+ function readCookie(name) {
94
+ if (typeof document === "undefined") return void 0;
95
+ const match = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
96
+ return match ? decodeURIComponent(match[1]) : void 0;
97
+ }
98
+ function writeCookie(name, value) {
99
+ if (typeof document === "undefined") return;
100
+ const maxAge = 365 * 24 * 60 * 60;
101
+ document.cookie = name + "=" + encodeURIComponent(value) + "; max-age=" + maxAge + "; path=/; SameSite=Lax";
102
+ }
103
+ function deleteCookie(name) {
104
+ if (typeof document === "undefined") return;
105
+ document.cookie = name + "=; max-age=0; path=/";
106
+ }
107
+ var ThemeProvider = ({
108
+ theme: initialTheme,
109
+ children,
110
+ initialColorMode = "system"
111
+ }) => {
112
+ const [rawTheme, setRawTheme] = useState(initialTheme);
113
+ const [colorMode, setColorModeState] = useState(initialColorMode);
114
+ const [systemPreference, setSystemPreference] = useState("light");
115
+ useIsomorphicLayoutEffect(() => {
116
+ const saved = readCookie(COLOR_MODE_COOKIE);
117
+ if (saved === "light" || saved === "dark") {
118
+ setColorModeState(saved);
119
+ }
120
+ const attr = document.documentElement.getAttribute("data-sw-color-mode");
121
+ if (attr === "dark" || attr === "light") {
122
+ setSystemPreference(attr);
123
+ } else {
124
+ setSystemPreference(getSystemPreference());
125
+ }
126
+ }, []);
127
+ useEffect(() => {
128
+ if (typeof window === "undefined") return;
129
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
130
+ const handler = (e) => {
131
+ setSystemPreference(e.matches ? "dark" : "light");
132
+ };
133
+ mql.addEventListener("change", handler);
134
+ return () => mql.removeEventListener("change", handler);
135
+ }, []);
136
+ const setColorMode = useCallback((mode) => {
137
+ setColorModeState(mode);
138
+ if (mode === "system") {
139
+ deleteCookie(COLOR_MODE_COOKIE);
140
+ if (typeof document !== "undefined") {
141
+ const systemMode = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
142
+ document.documentElement.setAttribute("data-sw-color-mode", systemMode);
143
+ }
144
+ } else {
145
+ writeCookie(COLOR_MODE_COOKIE, mode);
146
+ if (typeof document !== "undefined") {
147
+ document.documentElement.setAttribute("data-sw-color-mode", mode);
148
+ }
149
+ }
150
+ }, []);
151
+ const resolvedColorMode = colorMode === "system" ? systemPreference : colorMode;
152
+ const theme = useMemo(() => {
153
+ const effectiveColors = resolveColors(rawTheme, resolvedColorMode);
154
+ if (effectiveColors === rawTheme.colors) return rawTheme;
155
+ return { ...rawTheme, colors: effectiveColors };
156
+ }, [rawTheme, resolvedColorMode]);
157
+ const setTheme = useCallback((t) => setRawTheme(t), []);
158
+ const value = useMemo(
159
+ () => ({ theme, rawTheme, setTheme, colorMode, setColorMode, resolvedColorMode }),
160
+ [theme, rawTheme, setTheme, colorMode, setColorMode, resolvedColorMode]
161
+ );
162
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
74
163
  };
75
164
  var useTheme = () => {
76
165
  const context = useContext(ThemeContext);
@@ -79,6 +168,41 @@ var useTheme = () => {
79
168
  }
80
169
  return context;
81
170
  };
171
+ var useThemeOptional = () => {
172
+ return useContext(ThemeContext);
173
+ };
174
+ function themeToCSSVars(theme) {
175
+ return {
176
+ "--sw-color-primary": theme.colors.primary,
177
+ "--sw-color-secondary": theme.colors.secondary,
178
+ "--sw-color-accent": theme.colors.accent,
179
+ "--sw-color-bg": theme.colors.background,
180
+ "--sw-color-surface": theme.colors.surface,
181
+ "--sw-color-text": theme.colors.text,
182
+ "--sw-color-text-secondary": theme.colors.textSecondary,
183
+ "--sw-font-primary": theme.typography?.fontFamily?.primary ?? "sans-serif",
184
+ "--sw-font-secondary": theme.typography?.fontFamily?.secondary ?? "sans-serif",
185
+ "--sw-spacing-xs": theme.spacing?.xs ?? "0.25rem",
186
+ "--sw-spacing-sm": theme.spacing?.sm ?? "0.5rem",
187
+ "--sw-spacing-md": theme.spacing?.md ?? "1rem",
188
+ "--sw-spacing-lg": theme.spacing?.lg ?? "1.5rem",
189
+ "--sw-spacing-xl": theme.spacing?.xl ?? "2rem",
190
+ "--sw-spacing-2xl": theme.spacing?.["2xl"] ?? "3rem"
191
+ };
192
+ }
193
+ function ThemeStyleInjector({
194
+ theme: themeProp,
195
+ children,
196
+ className
197
+ }) {
198
+ const ctx = useThemeOptional();
199
+ const theme = themeProp ?? ctx?.theme;
200
+ if (!theme) {
201
+ return /* @__PURE__ */ jsx(Fragment, { children });
202
+ }
203
+ const cssVars = themeToCSSVars(theme);
204
+ return /* @__PURE__ */ jsx("div", { className, style: cssVars, children });
205
+ }
82
206
 
83
207
  // src/themeLoader.ts
84
208
  import * as yaml from "js-yaml";
@@ -105,7 +229,6 @@ var ThemeLoader = class {
105
229
  }
106
230
  static registerCustomTheme(theme) {
107
231
  this.themes.set(theme.name, theme);
108
- console.log(`\u{1F3A8} Registered custom theme: ${theme.name}`);
109
232
  }
110
233
  static loadCustomTheme(theme) {
111
234
  this.registerCustomTheme(theme);
@@ -125,6 +248,14 @@ colors:
125
248
  surface: "#ffffff"
126
249
  text: "#1f2937"
127
250
  textSecondary: "#6b7280"
251
+ darkColors:
252
+ primary: "#fbbf24"
253
+ secondary: "#94a3b8"
254
+ accent: "#f59e0b"
255
+ background: "#0f172a"
256
+ surface: "#1e293b"
257
+ text: "#f1f5f9"
258
+ textSecondary: "#94a3b8"
128
259
  typography:
129
260
  fontFamily:
130
261
  primary: "Roboto, sans-serif"
@@ -157,6 +288,14 @@ colors:
157
288
  surface: "#ffffff"
158
289
  text: "#374151"
159
290
  textSecondary: "#9ca3af"
291
+ darkColors:
292
+ primary: "#f472b6"
293
+ secondary: "#9ca3af"
294
+ accent: "#ec4899"
295
+ background: "#111827"
296
+ surface: "#1f2937"
297
+ text: "#f9fafb"
298
+ textSecondary: "#9ca3af"
160
299
  typography:
161
300
  fontFamily:
162
301
  primary: "Roboto, sans-serif"
@@ -184,12 +323,35 @@ spacing:
184
323
  return themes[name];
185
324
  }
186
325
  };
326
+
327
+ // src/ColorModeScript.tsx
328
+ import { jsx as jsx2 } from "react/jsx-runtime";
329
+ function ColorModeScript({ fallback = "system" }) {
330
+ const scriptContent = `
331
+ (function(){
332
+ try {
333
+ var m = document.cookie.match(/(?:^|; )sw-color-mode=([^;]*)/);
334
+ var mode = m ? m[1] : '${fallback}';
335
+ if (mode === 'system') {
336
+ mode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
337
+ }
338
+ document.documentElement.setAttribute('data-sw-color-mode', mode);
339
+ } catch(e) {}
340
+ })();
341
+ `.trim();
342
+ return /* @__PURE__ */ jsx2("script", { dangerouslySetInnerHTML: { __html: scriptContent } });
343
+ }
187
344
  export {
345
+ ColorModeScript,
188
346
  ThemeLoader,
189
347
  ThemeProvider,
348
+ ThemeStyleInjector,
349
+ colorsSchema,
190
350
  componentStyleSchema,
191
351
  themeConfigSchema,
192
352
  themeSchema,
193
- useTheme
353
+ themeToCSSVars,
354
+ useTheme,
355
+ useThemeOptional
194
356
  };
195
357
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/ThemeProvider.tsx","../src/themeLoader.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const componentStyleSchema = z.object({\n base: z.string().optional(),\n primary: z.string().optional(),\n secondary: z.string().optional(),\n outline: z.string().optional(),\n shadow: z.string().optional(),\n nav: z.string().optional(),\n text: z.string().optional(),\n}).catchall(z.string().optional());\n\nexport const themeConfigSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string(),\n colors: z.object({\n primary: z.string(),\n secondary: z.string(),\n accent: z.string(),\n background: z.string(),\n surface: z.string(),\n text: z.string(),\n textSecondary: z.string(),\n }),\n backgroundImage: z.object({\n url: z.string(),\n repeat: z.enum(['repeat', 'repeat-x', 'repeat-y', 'no-repeat']).optional(),\n size: z.string().optional(),\n position: z.string().optional(),\n attachment: z.enum(['scroll', 'fixed', 'local']).optional(),\n scale: z.number().optional(),\n animation: z.enum(['drift', 'float', 'shimmer', 'shimmer-float', 'none']).optional(),\n customAnimation: z.string().optional(),\n }).optional(),\n typography: z.object({\n fontFamily: z.object({\n primary: z.string(),\n secondary: z.string(),\n }),\n scale: z.object({\n xs: z.string(),\n sm: z.string(),\n base: z.string(),\n lg: z.string(),\n xl: z.string(),\n '2xl': z.string(),\n '3xl': z.string(),\n }),\n }),\n spacing: z.object({\n xs: z.string(),\n sm: z.string(),\n md: z.string(),\n lg: z.string(),\n xl: z.string(),\n '2xl': z.string(),\n }),\n components: z.object({\n button: componentStyleSchema.optional(),\n card: componentStyleSchema.optional(),\n header: componentStyleSchema.optional(),\n footer: componentStyleSchema.optional(),\n }).optional(),\n});\n\nexport const themeSchema = themeConfigSchema;\n\nexport type ThemeConfig = z.infer<typeof themeConfigSchema>;\nexport type ComponentStyle = z.infer<typeof componentStyleSchema>;\nexport interface Theme extends ThemeConfig {}\n","import React, { createContext, useContext, useState, ReactNode } from 'react';\nimport { Theme } from './types';\n\ninterface ThemeContextType {\n theme: Theme;\n setTheme: (theme: Theme) => void;\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\ninterface ThemeProviderProps {\n theme: Theme;\n children: ReactNode;\n}\n\nexport const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme: initialTheme, children }) => {\n const [theme, setTheme] = useState<Theme>(initialTheme);\n return (\n <ThemeContext.Provider value={{ theme, setTheme }}>\n {children}\n </ThemeContext.Provider>\n );\n};\n\nexport const useTheme = (): ThemeContextType => {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n};","import * as yaml from 'js-yaml';\nimport { Theme } from './types';\n\nexport class ThemeLoader {\n private static themes: Map<string, Theme> = new Map();\n\n static loadThemeFromYaml(yamlContent: string): Theme {\n try {\n const theme = yaml.load(yamlContent) as Theme;\n this.themes.set(theme.name, theme);\n return theme;\n } catch (error) {\n throw new Error(`Failed to parse theme YAML: ${error}`);\n }\n }\n\n static loadThemeFromFile(themeName: string): Theme {\n // In a real implementation, this would load from the file system\n // For now, we'll embed the themes\n const themeData = this.getEmbeddedTheme(themeName);\n return this.loadThemeFromYaml(themeData);\n }\n\n static getTheme(name: string): Theme | undefined {\n return this.themes.get(name);\n }\n\n static getAllThemes(): Theme[] {\n return Array.from(this.themes.values());\n }\n\n static registerCustomTheme(theme: Theme): void {\n this.themes.set(theme.name, theme);\n console.log(`🎨 Registered custom theme: ${theme.name}`);\n }\n\n static loadCustomTheme(theme: Theme): Theme {\n this.registerCustomTheme(theme);\n return theme;\n }\n\n private static getEmbeddedTheme(name: string): string {\n const themes: Record<string, string> = {\n corporate: `\nid: \"corporate\"\nname: \"Corporate\"\ndescription: \"A professional amber-toned corporate theme\"\ncolors:\n primary: \"#f59e0b\"\n secondary: \"#334155\"\n accent: \"#d97706\"\n background: \"#f8fafc\"\n surface: \"#ffffff\"\n text: \"#1f2937\"\n textSecondary: \"#6b7280\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`,\n soft: `\nid: \"soft\"\nname: \"Soft\"\ndescription: \"A gentle pink-toned soft theme\"\ncolors:\n primary: \"#ec4899\"\n secondary: \"#6b7280\"\n accent: \"#db2777\"\n background: \"#f9fafb\"\n surface: \"#ffffff\"\n text: \"#374151\"\n textSecondary: \"#9ca3af\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`\n };\n\n if (!themes[name]) {\n throw new Error(`Theme '${name}' not found`);\n }\n\n return themes[name];\n }\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/types.ts","../src/ThemeProvider.tsx","../src/themeLoader.ts","../src/ColorModeScript.tsx"],"sourcesContent":["import { z } from 'zod';\n\nexport const componentStyleSchema = z\n .object({\n base: z.string().optional(),\n primary: z.string().optional(),\n secondary: z.string().optional(),\n outline: z.string().optional(),\n shadow: z.string().optional(),\n nav: z.string().optional(),\n text: z.string().optional(),\n })\n .catchall(z.string().optional());\n\nexport const colorsSchema = z.object({\n primary: z.string(),\n secondary: z.string(),\n accent: z.string(),\n background: z.string(),\n surface: z.string(),\n text: z.string(),\n textSecondary: z.string(),\n});\n\nexport type ThemeColors = z.infer<typeof colorsSchema>;\n\nexport type ColorMode = 'light' | 'dark' | 'system';\n\nexport const themeConfigSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string(),\n colors: colorsSchema,\n darkColors: colorsSchema.optional(),\n backgroundImage: z\n .object({\n url: z.string(),\n repeat: z.enum(['repeat', 'repeat-x', 'repeat-y', 'no-repeat']).optional(),\n size: z.string().optional(),\n position: z.string().optional(),\n attachment: z.enum(['scroll', 'fixed', 'local']).optional(),\n scale: z.number().optional(),\n animation: z.enum(['drift', 'float', 'shimmer', 'shimmer-float', 'none']).optional(),\n customAnimation: z.string().optional(),\n })\n .optional(),\n typography: z.object({\n fontFamily: z.object({\n primary: z.string(),\n secondary: z.string(),\n }),\n scale: z.object({\n xs: z.string(),\n sm: z.string(),\n base: z.string(),\n lg: z.string(),\n xl: z.string(),\n '2xl': z.string(),\n '3xl': z.string(),\n }),\n }),\n spacing: z.object({\n xs: z.string(),\n sm: z.string(),\n md: z.string(),\n lg: z.string(),\n xl: z.string(),\n '2xl': z.string(),\n }),\n components: z\n .object({\n button: componentStyleSchema.optional(),\n card: componentStyleSchema.optional(),\n header: componentStyleSchema.optional(),\n footer: componentStyleSchema.optional(),\n })\n .optional(),\n});\n\nexport const themeSchema = themeConfigSchema;\n\nexport type ThemeConfig = z.infer<typeof themeConfigSchema>;\nexport type ComponentStyle = z.infer<typeof componentStyleSchema>;\nexport interface Theme extends ThemeConfig {}\nexport type { ThemeColors as ThemeColorsType };\n","import React, {\n createContext,\n useContext,\n useState,\n useEffect,\n useLayoutEffect,\n useMemo,\n useCallback,\n ReactNode,\n CSSProperties,\n} from 'react';\nimport { Theme, ColorMode, ThemeColors } from './types';\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface ThemeContextType {\n /** The resolved theme — `colors` reflects the active color mode. */\n theme: Theme;\n /** The original theme with both `colors` and `darkColors` intact. */\n rawTheme: Theme;\n setTheme: (theme: Theme) => void;\n /** Current color mode setting (`'light'`, `'dark'`, or `'system'`). */\n colorMode: ColorMode;\n /** Switch the color mode. */\n setColorMode: (mode: ColorMode) => void;\n /** The actually active mode after resolving `'system'`. */\n resolvedColorMode: 'light' | 'dark';\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst COLOR_MODE_COOKIE = 'sw-color-mode';\n\n/** Detect OS-level dark mode preference via `matchMedia`. SSR-safe. */\nfunction getSystemPreference(): 'light' | 'dark' {\n if (typeof window === 'undefined') return 'light';\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\n/**\n * SSR-safe `useLayoutEffect`. Falls back to `useEffect` on the server to\n * avoid React warnings, but uses `useLayoutEffect` on the client so that\n * color-mode state updates happen before the browser paints — preventing\n * a visible flash of the wrong theme.\n */\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n/** Pick the effective colors for the given mode. */\nfunction resolveColors(theme: Theme, mode: 'light' | 'dark'): ThemeColors {\n if (mode === 'dark' && theme.darkColors) {\n return theme.darkColors;\n }\n return theme.colors;\n}\n\n/**\n * Read a cookie by name. SSR-safe (returns undefined when no document).\n * Inline to avoid circular dependency on @stackwright/core.\n */\nfunction readCookie(name: string): string | undefined {\n if (typeof document === 'undefined') return undefined;\n const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));\n return match ? decodeURIComponent(match[1]) : undefined;\n}\n\n/** Write a cookie. SSR-safe no-op when no document. */\nfunction writeCookie(name: string, value: string): void {\n if (typeof document === 'undefined') return;\n const maxAge = 365 * 24 * 60 * 60;\n document.cookie =\n name + '=' + encodeURIComponent(value) + '; max-age=' + maxAge + '; path=/; SameSite=Lax';\n}\n\n/** Remove a cookie by setting max-age=0. */\nfunction deleteCookie(name: string): void {\n if (typeof document === 'undefined') return;\n document.cookie = name + '=; max-age=0; path=/';\n}\n\n// ---------------------------------------------------------------------------\n// ThemeProvider\n// ---------------------------------------------------------------------------\n\ninterface ThemeProviderProps {\n theme: Theme;\n children: ReactNode;\n /** Initial color mode. Defaults to `'system'`. */\n initialColorMode?: ColorMode;\n}\n\nexport const ThemeProvider: React.FC<ThemeProviderProps> = ({\n theme: initialTheme,\n children,\n initialColorMode = 'system',\n}) => {\n const [rawTheme, setRawTheme] = useState<Theme>(initialTheme);\n const [colorMode, setColorModeState] = useState<ColorMode>(initialColorMode);\n // Always initialise to 'light' — this matches the server render and\n // avoids the hydration mismatch that occurred when the useState\n // initialiser read from the DOM attribute set by ColorModeScript.\n const [systemPreference, setSystemPreference] = useState<'light' | 'dark'>('light');\n\n // After hydration, sync the real color mode from cookie / blocking-script\n // attribute / OS preference. Uses layoutEffect to run before the browser\n // paints, so the user never sees the wrong theme.\n useIsomorphicLayoutEffect(() => {\n const saved = readCookie(COLOR_MODE_COOKIE);\n if (saved === 'light' || saved === 'dark') {\n setColorModeState(saved);\n }\n\n // Trust the blocking ColorModeScript's attribute first, then fall back\n // to matchMedia.\n const attr = document.documentElement.getAttribute('data-sw-color-mode');\n if (attr === 'dark' || attr === 'light') {\n setSystemPreference(attr);\n } else {\n setSystemPreference(getSystemPreference());\n }\n }, []);\n\n // Listen for OS-level color scheme changes.\n useEffect(() => {\n if (typeof window === 'undefined') return;\n const mql = window.matchMedia('(prefers-color-scheme: dark)');\n const handler = (e: MediaQueryListEvent) => {\n setSystemPreference(e.matches ? 'dark' : 'light');\n };\n mql.addEventListener('change', handler);\n return () => mql.removeEventListener('change', handler);\n }, []);\n\n // Persist color mode to cookie on change, and clear when set to 'system'.\n const setColorMode = useCallback((mode: ColorMode) => {\n setColorModeState(mode);\n if (mode === 'system') {\n deleteCookie(COLOR_MODE_COOKIE);\n // Resolve system preference and sync the DOM attribute so CSS\n // variable selectors update without a page reload.\n if (typeof document !== 'undefined') {\n const systemMode = window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light';\n document.documentElement.setAttribute('data-sw-color-mode', systemMode);\n }\n } else {\n writeCookie(COLOR_MODE_COOKIE, mode);\n if (typeof document !== 'undefined') {\n document.documentElement.setAttribute('data-sw-color-mode', mode);\n }\n }\n }, []);\n\n const resolvedColorMode: 'light' | 'dark' = colorMode === 'system' ? systemPreference : colorMode;\n\n // Build the effective theme — swap `colors` for `darkColors` when in dark mode.\n const theme = useMemo<Theme>(() => {\n const effectiveColors = resolveColors(rawTheme, resolvedColorMode);\n if (effectiveColors === rawTheme.colors) return rawTheme;\n return { ...rawTheme, colors: effectiveColors };\n }, [rawTheme, resolvedColorMode]);\n\n const setTheme = useCallback((t: Theme) => setRawTheme(t), []);\n\n const value = useMemo<ThemeContextType>(\n () => ({ theme, rawTheme, setTheme, colorMode, setColorMode, resolvedColorMode }),\n [theme, rawTheme, setTheme, colorMode, setColorMode, resolvedColorMode]\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n};\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Access the current theme and color mode controls.\n * Must be called inside a `<ThemeProvider>`.\n */\nexport const useTheme = (): ThemeContextType => {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n};\n\n/**\n * Like `useTheme`, but returns `undefined` instead of throwing when\n * no `ThemeProvider` is present. Useful for optional-context components\n * like `ThemeStyleInjector`.\n */\nexport const useThemeOptional = (): ThemeContextType | undefined => {\n return useContext(ThemeContext);\n};\n\n// ---------------------------------------------------------------------------\n// CSS custom property helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Converts a Stackwright Theme to CSS custom properties.\n * Inject these via ThemeStyleInjector or a `<style>` tag.\n */\nexport function themeToCSSVars(theme: Theme): Record<string, string> {\n return {\n '--sw-color-primary': theme.colors.primary,\n '--sw-color-secondary': theme.colors.secondary,\n '--sw-color-accent': theme.colors.accent,\n '--sw-color-bg': theme.colors.background,\n '--sw-color-surface': theme.colors.surface,\n '--sw-color-text': theme.colors.text,\n '--sw-color-text-secondary': theme.colors.textSecondary,\n '--sw-font-primary': theme.typography?.fontFamily?.primary ?? 'sans-serif',\n '--sw-font-secondary': theme.typography?.fontFamily?.secondary ?? 'sans-serif',\n '--sw-spacing-xs': theme.spacing?.xs ?? '0.25rem',\n '--sw-spacing-sm': theme.spacing?.sm ?? '0.5rem',\n '--sw-spacing-md': theme.spacing?.md ?? '1rem',\n '--sw-spacing-lg': theme.spacing?.lg ?? '1.5rem',\n '--sw-spacing-xl': theme.spacing?.xl ?? '2rem',\n '--sw-spacing-2xl': theme.spacing?.['2xl'] ?? '3rem',\n };\n}\n\n// ---------------------------------------------------------------------------\n// ThemeStyleInjector\n// ---------------------------------------------------------------------------\n\ninterface ThemeStyleInjectorProps {\n /**\n * Explicit theme override. When omitted the resolved theme from the\n * nearest `ThemeProvider` is used automatically — this is the\n * recommended approach so CSS vars stay in sync with color mode changes.\n */\n theme?: Theme;\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Wraps children in a div that injects Stackwright CSS custom properties.\n * Place this inside a `<ThemeProvider>` and it will track color mode\n * changes automatically.\n */\nexport function ThemeStyleInjector({\n theme: themeProp,\n children,\n className,\n}: ThemeStyleInjectorProps) {\n const ctx = useThemeOptional();\n const theme = themeProp ?? ctx?.theme;\n\n if (!theme) {\n // No theme from prop or context — render children unstyled rather than crashing.\n return <>{children}</>;\n }\n\n const cssVars = themeToCSSVars(theme);\n return (\n <div className={className} style={cssVars as CSSProperties}>\n {children}\n </div>\n );\n}\n","import * as yaml from 'js-yaml';\nimport { Theme } from './types';\n\nexport class ThemeLoader {\n private static themes: Map<string, Theme> = new Map();\n\n static loadThemeFromYaml(yamlContent: string): Theme {\n try {\n const theme = yaml.load(yamlContent) as Theme;\n this.themes.set(theme.name, theme);\n return theme;\n } catch (error) {\n throw new Error(`Failed to parse theme YAML: ${error}`);\n }\n }\n\n static loadThemeFromFile(themeName: string): Theme {\n const themeData = this.getEmbeddedTheme(themeName);\n return this.loadThemeFromYaml(themeData);\n }\n\n static getTheme(name: string): Theme | undefined {\n return this.themes.get(name);\n }\n\n static getAllThemes(): Theme[] {\n return Array.from(this.themes.values());\n }\n\n static registerCustomTheme(theme: Theme): void {\n this.themes.set(theme.name, theme);\n }\n\n static loadCustomTheme(theme: Theme): Theme {\n this.registerCustomTheme(theme);\n return theme;\n }\n\n private static getEmbeddedTheme(name: string): string {\n const themes: Record<string, string> = {\n corporate: `\nid: \"corporate\"\nname: \"Corporate\"\ndescription: \"A professional amber-toned corporate theme\"\ncolors:\n primary: \"#f59e0b\"\n secondary: \"#334155\"\n accent: \"#d97706\"\n background: \"#f8fafc\"\n surface: \"#ffffff\"\n text: \"#1f2937\"\n textSecondary: \"#6b7280\"\ndarkColors:\n primary: \"#fbbf24\"\n secondary: \"#94a3b8\"\n accent: \"#f59e0b\"\n background: \"#0f172a\"\n surface: \"#1e293b\"\n text: \"#f1f5f9\"\n textSecondary: \"#94a3b8\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`,\n soft: `\nid: \"soft\"\nname: \"Soft\"\ndescription: \"A gentle pink-toned soft theme\"\ncolors:\n primary: \"#ec4899\"\n secondary: \"#6b7280\"\n accent: \"#db2777\"\n background: \"#f9fafb\"\n surface: \"#ffffff\"\n text: \"#374151\"\n textSecondary: \"#9ca3af\"\ndarkColors:\n primary: \"#f472b6\"\n secondary: \"#9ca3af\"\n accent: \"#ec4899\"\n background: \"#111827\"\n surface: \"#1f2937\"\n text: \"#f9fafb\"\n textSecondary: \"#9ca3af\"\ntypography:\n fontFamily:\n primary: \"Roboto, sans-serif\"\n secondary: \"Roboto, sans-serif\"\n scale:\n xs: \"0.75rem\"\n sm: \"0.875rem\"\n base: \"1rem\"\n lg: \"1.125rem\"\n xl: \"1.25rem\"\n 2xl: \"1.5rem\"\n 3xl: \"1.875rem\"\nspacing:\n xs: \"0.5rem\"\n sm: \"0.75rem\"\n md: \"1rem\"\n lg: \"1.5rem\"\n xl: \"2rem\"\n 2xl: \"3rem\"\n`,\n };\n\n if (!themes[name]) {\n throw new Error(`Theme '${name}' not found`);\n }\n\n return themes[name];\n }\n}\n","import React from 'react';\nimport { ColorMode } from './types';\n\n/**\n * Blocking script that reads the `sw-color-mode` cookie and applies the\n * correct color mode attribute to `<html>` before React hydrates.\n *\n * Place this in `_document.tsx` `<Head>` (Pages Router) or in a `<script>`\n * tag in `layout.tsx` (App Router). It must execute before the body is\n * painted — intentionally render-blocking.\n *\n * The `data-sw-color-mode` attribute set by this script is read by\n * `ThemeProvider`'s `systemPreference` initialiser, ensuring the first\n * React render matches the visible state — no hydration mismatch.\n *\n * For return visitors (cookie exists): correct theme on first paint, zero flash.\n * For first-time visitors with OS dark mode: one-frame flash (same as next-themes v0.2).\n */\nexport function ColorModeScript({ fallback = 'system' }: { fallback?: ColorMode }) {\n // The script is raw JS — no React, no module imports. It reads the cookie\n // and sets a data attribute. ~300 bytes, executes in <1ms.\n const scriptContent = `\n(function(){\n try {\n var m = document.cookie.match(/(?:^|; )sw-color-mode=([^;]*)/);\n var mode = m ? m[1] : '${fallback}';\n if (mode === 'system') {\n mode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n document.documentElement.setAttribute('data-sw-color-mode', mode);\n } catch(e) {}\n})();\n`.trim();\n\n return <script dangerouslySetInnerHTML={{ __html: scriptContent }} />;\n}\n"],"mappings":"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","names":["jsx"]}
package/package.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "@stackwright/themes",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
+ "description": "YAML-configurable theming with dark mode, CSS custom properties, and cookie-based persistence",
5
+ "license": "MIT",
4
6
  "repository": {
5
7
  "type": "git",
6
8
  "url": "https://github.com/Per-Aspera-LLC/stackwright"
@@ -20,14 +22,19 @@
20
22
  },
21
23
  "dependencies": {
22
24
  "js-yaml": "^4",
23
- "zod": "^4.1.12"
25
+ "zod": "^4.3.6"
24
26
  },
25
27
  "devDependencies": {
26
- "@types/react": "^19",
28
+ "@testing-library/jest-dom": "^6.9",
29
+ "@testing-library/react": "^16.3",
27
30
  "@types/js-yaml": "^4",
31
+ "@types/react": "^19.2.14",
32
+ "@types/react-dom": "^19.2.3",
33
+ "jsdom": "^28.1.0",
28
34
  "react": "^19",
29
- "typescript": "^5",
35
+ "react-dom": "^19",
30
36
  "tsup": "^8",
37
+ "typescript": "^5",
31
38
  "vitest": "^4"
32
39
  },
33
40
  "peerDependencies": {
@@ -37,6 +44,7 @@
37
44
  "build": "tsup",
38
45
  "dev": "tsup --watch",
39
46
  "test": "vitest",
40
- "test:run": "vitest run"
47
+ "test:run": "vitest run",
48
+ "test:coverage": "vitest run --coverage"
41
49
  }
42
50
  }