@dheme/next 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,19 +34,15 @@ pnpm add @dheme/next @dheme/react @dheme/sdk
34
34
 
35
35
  ```tsx
36
36
  // app/layout.tsx (Server Component)
37
- import { DhemeScript } from '@dheme/next/server'; // Server Component — server path only
38
- import { DhemeProvider } from '@dheme/next';
37
+ import { DhemeSetup } from '@dheme/next';
39
38
 
40
39
  export default function RootLayout({ children }: { children: React.ReactNode }) {
41
40
  return (
42
- <html lang="en">
43
- <head>
44
- <DhemeScript apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" />
45
- </head>
41
+ <html lang="en" suppressHydrationWarning>
46
42
  <body>
47
- <DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
43
+ <DhemeSetup apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" defaultMode="light">
48
44
  {children}
49
- </DhemeProvider>
45
+ </DhemeSetup>
50
46
  </body>
51
47
  </html>
52
48
  );
@@ -55,15 +51,20 @@ export default function RootLayout({ children }: { children: React.ReactNode })
55
51
 
56
52
  That's it. Your app has 19 CSS variables applied server-side — zero client-side fetch, zero FOUC.
57
53
 
54
+ `DhemeSetup` combines `DhemeScript` + `DhemeProvider` in a single declaration and ensures `defaultMode` is always consistent between them.
55
+
58
56
  ## How It Works
59
57
 
60
58
  ### Every visit (zero FOUC, zero client fetch)
61
59
 
62
60
  1. `<DhemeScript>` is a **Server Component** — it calls the Dheme API on the server
63
61
  2. It inlines a `<style>` tag with CSS variables for **both** light and dark modes
64
- 3. It inlines a tiny `<script>` (~250 bytes) that detects the user's mode preference (cookie or `prefers-color-scheme`) and applies the `.dark` class
65
- 4. The browser receives HTML with styles already applied — **before any paint**
66
- 5. React hydrates, `DhemeProvider` sets `isReady = true`no API call needed
62
+ 3. It inlines a tiny `<script>` (~300 bytes) that:
63
+ - Detects the user's mode preference (cookie or `prefers-color-scheme`)
64
+ - Sets `document.documentElement.style.backgroundColor` immediately (white for light, black for dark) **before any paint** to prevent the light→dark background flash
65
+ - Applies the `.dark` class
66
+ 4. The browser receives HTML with styles and background already applied — **before any paint**
67
+ 5. React hydrates, `DhemeProvider` sets `isReady = true` and removes the temporary inline `backgroundColor` — **no API call needed**
67
68
 
68
69
  ### Server-side caching
69
70
 
@@ -74,25 +75,81 @@ The server maintains an **in-memory LRU cache** (100 entries, 1h TTL). Since the
74
75
 
75
76
  ## Components
76
77
 
78
+ ### `<DhemeSetup>` (Server Component — recommended)
79
+
80
+ Single entry point that combines `DhemeScript` + `DhemeProvider`. Use this when you don't need client-side callbacks (`onThemeChange`, `onModeChange`, etc.).
81
+
82
+ ```tsx
83
+ // app/layout.tsx
84
+ import { DhemeSetup } from '@dheme/next';
85
+
86
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
87
+ return (
88
+ <html lang="en" suppressHydrationWarning>
89
+ <body>
90
+ <DhemeSetup
91
+ apiKey={process.env.DHEME_API_KEY!}
92
+ theme="#3b82f6"
93
+ defaultMode="dark" // Specified once — propagated to both script and provider
94
+ themeParams={{ radius: 0.75, tailwindVersion: 'v4' }}
95
+ >
96
+ {children}
97
+ </DhemeSetup>
98
+ </body>
99
+ </html>
100
+ );
101
+ }
102
+ ```
103
+
104
+ | Prop | Type | Default | Description |
105
+ | ------------------- | ------------------------------------------------- | -------------- | ------------------------------------------------------------------------------ |
106
+ | `theme` | `string` | - | **Required.** Primary HEX color. |
107
+ | `defaultMode` | `'light' \| 'dark'` | `'light'` | Initial color mode. Passed to both script and provider. |
108
+ | `themeParams` | `Omit<GenerateThemeRequest, 'theme'>` | - | Additional generation parameters. |
109
+ | `apiKey` | `string` | - | Dheme API key. Server-side only — never sent to the browser. |
110
+ | `baseUrl` | `string` | - | Override API base URL. |
111
+ | `nonce` | `string` | - | CSP nonce for injected style/script tags. |
112
+ | `onGenerateTheme` | `(params) => Promise<GenerateThemeResponse>` | - | Server-side custom theme function. Only used by `DhemeScript`. |
113
+ | `proxyUrl` | `string` | `'/api/dheme'` | Client-side proxy route URL. |
114
+ | `cookieSync` | `boolean` | `true` | Sync mode and params to cookies. |
115
+ | `persist` | `boolean` | `true` | Persist theme in localStorage. |
116
+ | `autoApply` | `boolean` | `true` | Apply CSS variables automatically. |
117
+ | `loadingBackground` | `boolean \| { light?: string; dark?: string }` | `true` | Background color applied to `<html>` while the theme loads. Prevents the light→dark flash when `DhemeScript` applies the dark class after paint. `true` = `#ffffff` / `#000000`, `false` = disabled, or pass custom hex values per mode. |
118
+ | `children` | `React.ReactNode` | - | **Required.** App content. |
119
+
120
+ > For apps that need `onThemeChange`, `onModeChange`, `onError`, or `loadingContent`, use `DhemeScript` + `DhemeProvider` separately (see below).
121
+
122
+ ---
123
+
77
124
  ### `<DhemeScript>` (Server Component)
78
125
 
79
- Fetches the theme on the server and renders inline `<style>` + `<script>` tags. Place it in `<head>`.
126
+ Fetches the theme on the server and renders inline `<style>` + `<script>` tags. Used directly when you need to place the styles in `<head>` or when combining with a custom `DhemeProvider`.
80
127
 
81
128
  ```tsx
82
- <DhemeScript
83
- apiKey={process.env.DHEME_API_KEY!} // Required — server-side only
84
- theme="#3b82f6" // Required — primary HEX color
85
- themeParams={{
86
- // Optional generation params
87
- radius: 0.75,
88
- saturationAdjust: 10,
89
- borderIsColored: false,
90
- tailwindVersion: 'v4', // 'v3' | 'v4' (default: 'v4')
91
- }}
92
- defaultMode="light" // 'light' | 'dark' (default: 'light')
93
- baseUrl="http://localhost:3005" // Override API URL (optional)
94
- nonce="abc123" // CSP nonce (optional)
95
- />
129
+ import { DhemeScript, DhemeProvider } from '@dheme/next';
130
+
131
+ // app/layout.tsx
132
+ export default function RootLayout({ children }) {
133
+ return (
134
+ <html lang="en" suppressHydrationWarning>
135
+ <head>
136
+ <DhemeScript
137
+ apiKey={process.env.DHEME_API_KEY!}
138
+ theme="#3b82f6"
139
+ defaultMode="dark"
140
+ themeParams={{ radius: 0.75, tailwindVersion: 'v4' }}
141
+ nonce="abc123"
142
+ />
143
+ </head>
144
+ <body>
145
+ <DhemeProvider theme="#3b82f6" defaultMode="dark">
146
+ {children}
147
+ </DhemeProvider>
148
+ </body>
149
+ </html>
150
+ );
151
+ }
152
+ ```
96
153
  ```
97
154
 
98
155
  | Prop | Type | Default | Description |
@@ -127,20 +184,16 @@ A floating FAB for real-time theme generation. Re-exported from `@dheme/react` w
127
184
 
128
185
  ```tsx
129
186
  // app/layout.tsx
130
- import { DhemeScript } from '@dheme/next/server';
131
- import { DhemeProvider, ThemeGenerator } from '@dheme/next';
187
+ import { DhemeSetup, ThemeGenerator } from '@dheme/next';
132
188
 
133
189
  export default function RootLayout({ children }: { children: React.ReactNode }) {
134
190
  return (
135
191
  <html lang="en" suppressHydrationWarning>
136
- <head>
137
- <DhemeScript apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" />
138
- </head>
139
192
  <body>
140
- <DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
193
+ <DhemeSetup apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
141
194
  {children}
142
195
  <ThemeGenerator />
143
- </DhemeProvider>
196
+ </DhemeSetup>
144
197
  </body>
145
198
  </html>
146
199
  );
@@ -303,23 +356,20 @@ DHEME_BASE_URL=http://localhost:3005
303
356
  ### `app/layout.tsx`
304
357
 
305
358
  ```tsx
306
- import { DhemeScript } from '@dheme/next/server';
307
- import { DhemeProvider } from '@dheme/next';
359
+ import { DhemeSetup } from '@dheme/next';
308
360
 
309
361
  export default function RootLayout({ children }: { children: React.ReactNode }) {
310
362
  return (
311
363
  <html lang="en" suppressHydrationWarning>
312
- <head>
313
- <DhemeScript
364
+ <body>
365
+ <DhemeSetup
314
366
  apiKey={process.env.DHEME_API_KEY!}
315
367
  theme="#3b82f6"
368
+ defaultMode="light"
316
369
  themeParams={{ radius: 0.5 }}
317
- />
318
- </head>
319
- <body>
320
- <DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
370
+ >
321
371
  {children}
322
- </DhemeProvider>
372
+ </DhemeSetup>
323
373
  </body>
324
374
  </html>
325
375
  );
@@ -386,10 +436,14 @@ export async function GET(request: Request) {
386
436
  Server Client
387
437
  ────── ──────
388
438
 
439
+ DhemeSetup (Server Component)
440
+
441
+ ├──────────────────────────────────────────────┐
442
+ │ │
389
443
  Request → DhemeScript (Server Component) DhemeProvider (Client Component)
390
444
  │ │
391
445
  ├─ themeCache.get(key) ├─ localStorage cache check
392
- │ ↓ miss? call SDK │ ↓ hit? serve instantly
446
+ │ ↓ miss? call SDK │ ↓ hit? apply synchronously
393
447
  ├─ themeCache.set(key, data) ├─ background revalidation
394
448
  │ │
395
449
  ├─ <style> with :root + .dark ├─ cookie sync (mode + params)
@@ -414,13 +468,16 @@ Request → DhemeScript (Server Component) DhemeProvider (Client Component)
414
468
 
415
469
  ## TypeScript
416
470
 
417
- All types are exported:
471
+ All types are exported from `@dheme/next`:
418
472
 
419
473
  ```typescript
420
- // Client types
421
474
  import type {
475
+ // Component props
476
+ DhemeSetupProps,
422
477
  DhemeProviderProps,
478
+ DhemeScriptProps,
423
479
  ThemeGeneratorProps,
480
+ // Theme types
424
481
  ThemeMode,
425
482
  ThemeDataState,
426
483
  ThemeActionsState,
@@ -430,8 +487,8 @@ import type {
430
487
  HSLColor,
431
488
  } from '@dheme/next';
432
489
 
433
- // Server types
434
- import type { DhemeScriptProps, GenerateThemeStylesOptions } from '@dheme/next/server';
490
+ // Server utility types
491
+ import type { GenerateThemeStylesOptions } from '@dheme/next/server';
435
492
  ```
436
493
 
437
494
  ## Related Packages
package/dist/index.d.mts CHANGED
@@ -24,6 +24,75 @@ interface DhemeProviderProps extends DhemeProviderProps$1 {
24
24
  */
25
25
  proxyUrl?: string;
26
26
  }
27
+ interface DhemeScriptProps {
28
+ /** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
29
+ apiKey?: string;
30
+ theme: string;
31
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
32
+ defaultMode?: ThemeMode;
33
+ baseUrl?: string;
34
+ nonce?: string;
35
+ /**
36
+ * Custom theme generation function. When provided, replaces the SDK client's
37
+ * generateTheme call entirely. Useful for internal use cases with custom endpoints.
38
+ *
39
+ * @example
40
+ * // Call an internal proxy route without API key:
41
+ * onGenerateTheme={async (params) => {
42
+ * const res = await fetch('/api/generate-theme/proxy', {
43
+ * method: 'POST',
44
+ * headers: { 'Content-Type': 'application/json' },
45
+ * body: JSON.stringify(params),
46
+ * });
47
+ * return res.json();
48
+ * }}
49
+ */
50
+ onGenerateTheme?: (params: GenerateThemeRequest) => Promise<GenerateThemeResponse>;
51
+ }
52
+ interface DhemeSetupProps {
53
+ /** Primary color hex for the theme (e.g. "7C3AED" or "#7C3AED") */
54
+ theme: string;
55
+ /**
56
+ * Default color mode. Passed to both DhemeScript and DhemeProvider — single source of truth.
57
+ * @default 'light'
58
+ */
59
+ defaultMode?: ThemeMode;
60
+ /** Additional theme generation parameters */
61
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
62
+ /** API key for the Dheme service. Server-side only — never sent to the browser. */
63
+ apiKey?: string;
64
+ /** Override the base URL for the Dheme API */
65
+ baseUrl?: string;
66
+ /** Nonce for Content Security Policy */
67
+ nonce?: string;
68
+ /**
69
+ * Server-side custom theme generation function.
70
+ * Replaces the SDK client call in DhemeScript. NOT forwarded to DhemeProvider.
71
+ * For client-side custom generation, use DhemeProvider directly with onGenerateTheme.
72
+ */
73
+ onGenerateTheme?: (params: GenerateThemeRequest) => Promise<GenerateThemeResponse>;
74
+ /**
75
+ * URL of the client-side proxy route.
76
+ * Defaults to "/api/dheme" when no apiKey is provided.
77
+ */
78
+ proxyUrl?: string;
79
+ /** Sync active mode and theme params to cookies for SSR hydration. @default true */
80
+ cookieSync?: boolean;
81
+ /** Persist theme data in localStorage for instant cache hits. @default true */
82
+ persist?: boolean;
83
+ /** Automatically apply CSS variables when theme changes. @default true */
84
+ autoApply?: boolean;
85
+ /**
86
+ * Aplica um `backgroundColor` de fallback no `<html>` antes do tema carregar,
87
+ * prevenindo o Flash of Unstyled Background (FOUB). Forwarded para o DhemeProvider interno.
88
+ * @default true
89
+ */
90
+ loadingBackground?: boolean | {
91
+ light?: string;
92
+ dark?: string;
93
+ };
94
+ children: React.ReactNode;
95
+ }
27
96
  interface GenerateThemeStylesOptions {
28
97
  /** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
29
98
  apiKey?: string;
@@ -40,4 +109,31 @@ interface GenerateThemeStylesOptions {
40
109
 
41
110
  declare function DhemeProvider({ children, cookieSync, proxyUrl, onThemeChange, onModeChange, onGenerateTheme, theme: primaryColor, themeParams, ...props }: DhemeProviderProps): React.ReactElement;
42
111
 
43
- export { DhemeProvider, type DhemeProviderProps, type GenerateThemeStylesOptions };
112
+ declare function DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme, }: DhemeScriptProps): Promise<React.ReactElement>;
113
+
114
+ /**
115
+ * Combined Server Component that renders DhemeScript + DhemeProvider in a single declaration.
116
+ * Accepts `defaultMode` once, eliminating the need to keep it in sync across two components.
117
+ *
118
+ * @example
119
+ * // app/layout.tsx
120
+ * import { DhemeSetup } from '@dheme/next';
121
+ *
122
+ * export default function RootLayout({ children }: { children: React.ReactNode }) {
123
+ * return (
124
+ * <html>
125
+ * <body>
126
+ * <DhemeSetup theme="7C3AED" defaultMode="dark" apiKey={process.env.DHEME_API_KEY}>
127
+ * {children}
128
+ * </DhemeSetup>
129
+ * </body>
130
+ * </html>
131
+ * );
132
+ * }
133
+ *
134
+ * For apps that need client-side callbacks (onThemeChange, onModeChange, onError, loadingContent),
135
+ * use DhemeScript + DhemeProvider separately.
136
+ */
137
+ declare function DhemeSetup({ theme, defaultMode, themeParams, apiKey, baseUrl, nonce, onGenerateTheme, proxyUrl, cookieSync, persist, autoApply, loadingBackground, children, }: DhemeSetupProps): Promise<React.ReactElement>;
138
+
139
+ export { DhemeProvider, type DhemeProviderProps, DhemeScript, type DhemeScriptProps, DhemeSetup, type DhemeSetupProps, type GenerateThemeStylesOptions };
package/dist/index.d.ts CHANGED
@@ -24,6 +24,75 @@ interface DhemeProviderProps extends DhemeProviderProps$1 {
24
24
  */
25
25
  proxyUrl?: string;
26
26
  }
27
+ interface DhemeScriptProps {
28
+ /** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
29
+ apiKey?: string;
30
+ theme: string;
31
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
32
+ defaultMode?: ThemeMode;
33
+ baseUrl?: string;
34
+ nonce?: string;
35
+ /**
36
+ * Custom theme generation function. When provided, replaces the SDK client's
37
+ * generateTheme call entirely. Useful for internal use cases with custom endpoints.
38
+ *
39
+ * @example
40
+ * // Call an internal proxy route without API key:
41
+ * onGenerateTheme={async (params) => {
42
+ * const res = await fetch('/api/generate-theme/proxy', {
43
+ * method: 'POST',
44
+ * headers: { 'Content-Type': 'application/json' },
45
+ * body: JSON.stringify(params),
46
+ * });
47
+ * return res.json();
48
+ * }}
49
+ */
50
+ onGenerateTheme?: (params: GenerateThemeRequest) => Promise<GenerateThemeResponse>;
51
+ }
52
+ interface DhemeSetupProps {
53
+ /** Primary color hex for the theme (e.g. "7C3AED" or "#7C3AED") */
54
+ theme: string;
55
+ /**
56
+ * Default color mode. Passed to both DhemeScript and DhemeProvider — single source of truth.
57
+ * @default 'light'
58
+ */
59
+ defaultMode?: ThemeMode;
60
+ /** Additional theme generation parameters */
61
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
62
+ /** API key for the Dheme service. Server-side only — never sent to the browser. */
63
+ apiKey?: string;
64
+ /** Override the base URL for the Dheme API */
65
+ baseUrl?: string;
66
+ /** Nonce for Content Security Policy */
67
+ nonce?: string;
68
+ /**
69
+ * Server-side custom theme generation function.
70
+ * Replaces the SDK client call in DhemeScript. NOT forwarded to DhemeProvider.
71
+ * For client-side custom generation, use DhemeProvider directly with onGenerateTheme.
72
+ */
73
+ onGenerateTheme?: (params: GenerateThemeRequest) => Promise<GenerateThemeResponse>;
74
+ /**
75
+ * URL of the client-side proxy route.
76
+ * Defaults to "/api/dheme" when no apiKey is provided.
77
+ */
78
+ proxyUrl?: string;
79
+ /** Sync active mode and theme params to cookies for SSR hydration. @default true */
80
+ cookieSync?: boolean;
81
+ /** Persist theme data in localStorage for instant cache hits. @default true */
82
+ persist?: boolean;
83
+ /** Automatically apply CSS variables when theme changes. @default true */
84
+ autoApply?: boolean;
85
+ /**
86
+ * Aplica um `backgroundColor` de fallback no `<html>` antes do tema carregar,
87
+ * prevenindo o Flash of Unstyled Background (FOUB). Forwarded para o DhemeProvider interno.
88
+ * @default true
89
+ */
90
+ loadingBackground?: boolean | {
91
+ light?: string;
92
+ dark?: string;
93
+ };
94
+ children: React.ReactNode;
95
+ }
27
96
  interface GenerateThemeStylesOptions {
28
97
  /** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
29
98
  apiKey?: string;
@@ -40,4 +109,31 @@ interface GenerateThemeStylesOptions {
40
109
 
41
110
  declare function DhemeProvider({ children, cookieSync, proxyUrl, onThemeChange, onModeChange, onGenerateTheme, theme: primaryColor, themeParams, ...props }: DhemeProviderProps): React.ReactElement;
42
111
 
43
- export { DhemeProvider, type DhemeProviderProps, type GenerateThemeStylesOptions };
112
+ declare function DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme, }: DhemeScriptProps): Promise<React.ReactElement>;
113
+
114
+ /**
115
+ * Combined Server Component that renders DhemeScript + DhemeProvider in a single declaration.
116
+ * Accepts `defaultMode` once, eliminating the need to keep it in sync across two components.
117
+ *
118
+ * @example
119
+ * // app/layout.tsx
120
+ * import { DhemeSetup } from '@dheme/next';
121
+ *
122
+ * export default function RootLayout({ children }: { children: React.ReactNode }) {
123
+ * return (
124
+ * <html>
125
+ * <body>
126
+ * <DhemeSetup theme="7C3AED" defaultMode="dark" apiKey={process.env.DHEME_API_KEY}>
127
+ * {children}
128
+ * </DhemeSetup>
129
+ * </body>
130
+ * </html>
131
+ * );
132
+ * }
133
+ *
134
+ * For apps that need client-side callbacks (onThemeChange, onModeChange, onError, loadingContent),
135
+ * use DhemeScript + DhemeProvider separately.
136
+ */
137
+ declare function DhemeSetup({ theme, defaultMode, themeParams, apiKey, baseUrl, nonce, onGenerateTheme, proxyUrl, cookieSync, persist, autoApply, loadingBackground, children, }: DhemeSetupProps): Promise<React.ReactElement>;
138
+
139
+ export { DhemeProvider, type DhemeProviderProps, DhemeScript, type DhemeScriptProps, DhemeSetup, type DhemeSetupProps, type GenerateThemeStylesOptions };
package/dist/index.js CHANGED
@@ -32,14 +32,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var src_exports = {};
33
33
  __export(src_exports, {
34
34
  DhemeProvider: () => DhemeProvider,
35
+ DhemeScript: () => DhemeScript,
36
+ DhemeSetup: () => DhemeSetup,
35
37
  ThemeGenerator: () => import_react3.ThemeGenerator,
36
- applyThemeCSSVariables: () => import_react5.applyThemeCSSVariables,
37
- buildCacheKey: () => import_react5.buildCacheKey,
38
- themeToCSS: () => import_react5.themeToCSS,
39
- useDhemeClient: () => import_react4.useDhemeClient,
40
- useGenerateTheme: () => import_react4.useGenerateTheme,
41
- useTheme: () => import_react4.useTheme,
42
- useThemeActions: () => import_react4.useThemeActions
38
+ applyThemeCSSVariables: () => import_react7.applyThemeCSSVariables,
39
+ buildCacheKey: () => import_react7.buildCacheKey,
40
+ themeToCSS: () => import_react7.themeToCSS,
41
+ useDhemeClient: () => import_react6.useDhemeClient,
42
+ useGenerateTheme: () => import_react6.useGenerateTheme,
43
+ useTheme: () => import_react6.useTheme,
44
+ useThemeActions: () => import_react6.useThemeActions
43
45
  });
44
46
  module.exports = __toCommonJS(src_exports);
45
47
 
@@ -117,12 +119,139 @@ function DhemeProvider({
117
119
  // src/components/ThemeGenerator.tsx
118
120
  var import_react3 = require("@dheme/react");
119
121
 
122
+ // src/components/DhemeScript.tsx
123
+ var import_react4 = __toESM(require("react"));
124
+ var import_script = __toESM(require("next/script"));
125
+ var import_sdk = require("@dheme/sdk");
126
+ var import_utils = require("@dheme/react/utils");
127
+
128
+ // src/server/cache.ts
129
+ var ThemeCache = class {
130
+ constructor(maxSize = 100, ttlMs = 36e5) {
131
+ this.cache = /* @__PURE__ */ new Map();
132
+ this.maxSize = maxSize;
133
+ this.ttl = ttlMs;
134
+ }
135
+ get(key) {
136
+ const entry = this.cache.get(key);
137
+ if (!entry) return null;
138
+ if (Date.now() - entry.timestamp > this.ttl) {
139
+ this.cache.delete(key);
140
+ return null;
141
+ }
142
+ return entry.data;
143
+ }
144
+ set(key, data) {
145
+ if (this.cache.size >= this.maxSize) {
146
+ const oldestKey = this.cache.keys().next().value;
147
+ if (oldestKey !== void 0) {
148
+ this.cache.delete(oldestKey);
149
+ }
150
+ }
151
+ this.cache.set(key, { data, timestamp: Date.now() });
152
+ }
153
+ clear() {
154
+ this.cache.clear();
155
+ }
156
+ };
157
+ var themeCache = new ThemeCache();
158
+
159
+ // src/components/DhemeScript.tsx
160
+ async function DhemeScript({
161
+ apiKey,
162
+ theme,
163
+ themeParams,
164
+ defaultMode = "light",
165
+ baseUrl,
166
+ nonce,
167
+ onGenerateTheme
168
+ }) {
169
+ const params = { theme, ...themeParams };
170
+ const cacheKey = (0, import_utils.buildCacheKey)(params);
171
+ let themeData = themeCache.get(cacheKey);
172
+ if (!themeData) {
173
+ if (onGenerateTheme) {
174
+ themeData = await onGenerateTheme(params);
175
+ } else {
176
+ const client = new import_sdk.DhemeClient({ apiKey, baseUrl });
177
+ const response = await client.generateTheme(params);
178
+ themeData = response.data;
179
+ }
180
+ themeCache.set(cacheKey, themeData);
181
+ }
182
+ const tailwindVersion = params.tailwindVersion ?? "v4";
183
+ const lightCSS = (0, import_utils.themeToCSS)(themeData, "light", tailwindVersion);
184
+ const darkCSS = (0, import_utils.themeToCSS)(themeData, "dark", tailwindVersion);
185
+ const styleContent = `:root{${lightCSS}}.dark{${darkCSS}}`;
186
+ const scriptContent = (0, import_utils.getNextBlockingScriptPayload)(defaultMode);
187
+ const styleProps = {
188
+ nonce,
189
+ precedence: "high",
190
+ dangerouslySetInnerHTML: { __html: styleContent }
191
+ };
192
+ return import_react4.default.createElement(
193
+ import_react4.default.Fragment,
194
+ null,
195
+ import_react4.default.createElement("style", styleProps),
196
+ // strategy="beforeInteractive" causes Next.js to inject this script into
197
+ // the HTML before any page JS runs — prevents dark/light mode flash.
198
+ import_react4.default.createElement(import_script.default, {
199
+ id: "dheme-mode-detect",
200
+ strategy: "beforeInteractive",
201
+ nonce,
202
+ dangerouslySetInnerHTML: { __html: scriptContent }
203
+ })
204
+ );
205
+ }
206
+
207
+ // src/components/DhemeSetup.tsx
208
+ var import_react5 = __toESM(require("react"));
209
+ async function DhemeSetup({
210
+ // Shared between script and provider
211
+ theme,
212
+ defaultMode = "light",
213
+ themeParams,
214
+ // DhemeScript only (server-side — never reaches the browser)
215
+ apiKey,
216
+ baseUrl,
217
+ nonce,
218
+ onGenerateTheme,
219
+ // DhemeProvider only (client-side)
220
+ proxyUrl,
221
+ cookieSync,
222
+ persist,
223
+ autoApply,
224
+ loadingBackground,
225
+ children
226
+ }) {
227
+ return import_react5.default.createElement(
228
+ import_react5.default.Fragment,
229
+ null,
230
+ // Server Component: fetches theme server-side, emits render-blocking <style> + <script>
231
+ await DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme }),
232
+ // Client Component: provides theme context, handles runtime interactivity
233
+ import_react5.default.createElement(DhemeProvider, {
234
+ theme,
235
+ themeParams,
236
+ defaultMode,
237
+ proxyUrl,
238
+ cookieSync,
239
+ persist,
240
+ autoApply,
241
+ loadingBackground,
242
+ children
243
+ })
244
+ );
245
+ }
246
+
120
247
  // src/index.ts
121
- var import_react4 = require("@dheme/react");
122
- var import_react5 = require("@dheme/react");
248
+ var import_react6 = require("@dheme/react");
249
+ var import_react7 = require("@dheme/react");
123
250
  // Annotate the CommonJS export names for ESM import in node:
124
251
  0 && (module.exports = {
125
252
  DhemeProvider,
253
+ DhemeScript,
254
+ DhemeSetup,
126
255
  ThemeGenerator,
127
256
  applyThemeCSSVariables,
128
257
  buildCacheKey,
package/dist/index.mjs CHANGED
@@ -74,15 +74,142 @@ function DhemeProvider({
74
74
  // src/components/ThemeGenerator.tsx
75
75
  import { ThemeGenerator } from "@dheme/react";
76
76
 
77
+ // src/components/DhemeScript.tsx
78
+ import React2 from "react";
79
+ import Script from "next/script";
80
+ import { DhemeClient } from "@dheme/sdk";
81
+ import { themeToCSS, buildCacheKey, getNextBlockingScriptPayload } from "@dheme/react/utils";
82
+
83
+ // src/server/cache.ts
84
+ var ThemeCache = class {
85
+ constructor(maxSize = 100, ttlMs = 36e5) {
86
+ this.cache = /* @__PURE__ */ new Map();
87
+ this.maxSize = maxSize;
88
+ this.ttl = ttlMs;
89
+ }
90
+ get(key) {
91
+ const entry = this.cache.get(key);
92
+ if (!entry) return null;
93
+ if (Date.now() - entry.timestamp > this.ttl) {
94
+ this.cache.delete(key);
95
+ return null;
96
+ }
97
+ return entry.data;
98
+ }
99
+ set(key, data) {
100
+ if (this.cache.size >= this.maxSize) {
101
+ const oldestKey = this.cache.keys().next().value;
102
+ if (oldestKey !== void 0) {
103
+ this.cache.delete(oldestKey);
104
+ }
105
+ }
106
+ this.cache.set(key, { data, timestamp: Date.now() });
107
+ }
108
+ clear() {
109
+ this.cache.clear();
110
+ }
111
+ };
112
+ var themeCache = new ThemeCache();
113
+
114
+ // src/components/DhemeScript.tsx
115
+ async function DhemeScript({
116
+ apiKey,
117
+ theme,
118
+ themeParams,
119
+ defaultMode = "light",
120
+ baseUrl,
121
+ nonce,
122
+ onGenerateTheme
123
+ }) {
124
+ const params = { theme, ...themeParams };
125
+ const cacheKey = buildCacheKey(params);
126
+ let themeData = themeCache.get(cacheKey);
127
+ if (!themeData) {
128
+ if (onGenerateTheme) {
129
+ themeData = await onGenerateTheme(params);
130
+ } else {
131
+ const client = new DhemeClient({ apiKey, baseUrl });
132
+ const response = await client.generateTheme(params);
133
+ themeData = response.data;
134
+ }
135
+ themeCache.set(cacheKey, themeData);
136
+ }
137
+ const tailwindVersion = params.tailwindVersion ?? "v4";
138
+ const lightCSS = themeToCSS(themeData, "light", tailwindVersion);
139
+ const darkCSS = themeToCSS(themeData, "dark", tailwindVersion);
140
+ const styleContent = `:root{${lightCSS}}.dark{${darkCSS}}`;
141
+ const scriptContent = getNextBlockingScriptPayload(defaultMode);
142
+ const styleProps = {
143
+ nonce,
144
+ precedence: "high",
145
+ dangerouslySetInnerHTML: { __html: styleContent }
146
+ };
147
+ return React2.createElement(
148
+ React2.Fragment,
149
+ null,
150
+ React2.createElement("style", styleProps),
151
+ // strategy="beforeInteractive" causes Next.js to inject this script into
152
+ // the HTML before any page JS runs — prevents dark/light mode flash.
153
+ React2.createElement(Script, {
154
+ id: "dheme-mode-detect",
155
+ strategy: "beforeInteractive",
156
+ nonce,
157
+ dangerouslySetInnerHTML: { __html: scriptContent }
158
+ })
159
+ );
160
+ }
161
+
162
+ // src/components/DhemeSetup.tsx
163
+ import React3 from "react";
164
+ async function DhemeSetup({
165
+ // Shared between script and provider
166
+ theme,
167
+ defaultMode = "light",
168
+ themeParams,
169
+ // DhemeScript only (server-side — never reaches the browser)
170
+ apiKey,
171
+ baseUrl,
172
+ nonce,
173
+ onGenerateTheme,
174
+ // DhemeProvider only (client-side)
175
+ proxyUrl,
176
+ cookieSync,
177
+ persist,
178
+ autoApply,
179
+ loadingBackground,
180
+ children
181
+ }) {
182
+ return React3.createElement(
183
+ React3.Fragment,
184
+ null,
185
+ // Server Component: fetches theme server-side, emits render-blocking <style> + <script>
186
+ await DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme }),
187
+ // Client Component: provides theme context, handles runtime interactivity
188
+ React3.createElement(DhemeProvider, {
189
+ theme,
190
+ themeParams,
191
+ defaultMode,
192
+ proxyUrl,
193
+ cookieSync,
194
+ persist,
195
+ autoApply,
196
+ loadingBackground,
197
+ children
198
+ })
199
+ );
200
+ }
201
+
77
202
  // src/index.ts
78
203
  import { useTheme, useThemeActions, useGenerateTheme, useDhemeClient } from "@dheme/react";
79
- import { themeToCSS, applyThemeCSSVariables, buildCacheKey } from "@dheme/react";
204
+ import { themeToCSS as themeToCSS2, applyThemeCSSVariables, buildCacheKey as buildCacheKey2 } from "@dheme/react";
80
205
  export {
81
206
  DhemeProvider,
207
+ DhemeScript,
208
+ DhemeSetup,
82
209
  ThemeGenerator,
83
210
  applyThemeCSSVariables,
84
- buildCacheKey,
85
- themeToCSS,
211
+ buildCacheKey2 as buildCacheKey,
212
+ themeToCSS2 as themeToCSS,
86
213
  useDhemeClient,
87
214
  useGenerateTheme,
88
215
  useTheme,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dheme/next",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Next.js App Router bindings for Dheme SDK with server-side theme generation",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@dheme/sdk": "^1.1.0",
59
- "@dheme/react": "^2.13.0"
59
+ "@dheme/react": "^2.16.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/react": "^18.2.0",