@stackwright/themes 0.5.0 → 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.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -175,8 +175,15 @@ var ThemeProvider = ({
|
|
|
175
175
|
setColorModeState(mode);
|
|
176
176
|
if (mode === "system") {
|
|
177
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
|
+
}
|
|
178
182
|
} else {
|
|
179
183
|
writeCookie(COLOR_MODE_COOKIE, mode);
|
|
184
|
+
if (typeof document !== "undefined") {
|
|
185
|
+
document.documentElement.setAttribute("data-sw-color-mode", mode);
|
|
186
|
+
}
|
|
180
187
|
}
|
|
181
188
|
}, []);
|
|
182
189
|
const resolvedColorMode = colorMode === "system" ? systemPreference : colorMode;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 } else {\n writeCookie(COLOR_MODE_COOKIE, mode);\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"]}
|
|
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
|
@@ -137,8 +137,15 @@ var ThemeProvider = ({
|
|
|
137
137
|
setColorModeState(mode);
|
|
138
138
|
if (mode === "system") {
|
|
139
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
|
+
}
|
|
140
144
|
} else {
|
|
141
145
|
writeCookie(COLOR_MODE_COOKIE, mode);
|
|
146
|
+
if (typeof document !== "undefined") {
|
|
147
|
+
document.documentElement.setAttribute("data-sw-color-mode", mode);
|
|
148
|
+
}
|
|
142
149
|
}
|
|
143
150
|
}, []);
|
|
144
151
|
const resolvedColorMode = colorMode === "system" ? systemPreference : colorMode;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 } else {\n writeCookie(COLOR_MODE_COOKIE, mode);\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"]}
|
|
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.5.
|
|
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"
|
|
@@ -42,6 +44,7 @@
|
|
|
42
44
|
"build": "tsup",
|
|
43
45
|
"dev": "tsup --watch",
|
|
44
46
|
"test": "vitest",
|
|
45
|
-
"test:run": "vitest run"
|
|
47
|
+
"test:run": "vitest run",
|
|
48
|
+
"test:coverage": "vitest run --coverage"
|
|
46
49
|
}
|
|
47
50
|
}
|