@dheme/next 1.12.0 → 1.13.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 +97 -44
- package/dist/index.d.mts +88 -1
- package/dist/index.d.ts +88 -1
- package/dist/index.js +136 -9
- package/dist/index.mjs +128 -3
- package/package.json +2 -2
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 {
|
|
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
|
-
<
|
|
43
|
+
<DhemeSetup apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" defaultMode="light">
|
|
48
44
|
{children}
|
|
49
|
-
</
|
|
45
|
+
</DhemeSetup>
|
|
50
46
|
</body>
|
|
51
47
|
</html>
|
|
52
48
|
);
|
|
@@ -55,6 +51,8 @@ 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)
|
|
@@ -74,25 +72,80 @@ The server maintains an **in-memory LRU cache** (100 entries, 1h TTL). Since the
|
|
|
74
72
|
|
|
75
73
|
## Components
|
|
76
74
|
|
|
75
|
+
### `<DhemeSetup>` (Server Component — recommended)
|
|
76
|
+
|
|
77
|
+
Single entry point that combines `DhemeScript` + `DhemeProvider`. Use this when you don't need client-side callbacks (`onThemeChange`, `onModeChange`, etc.).
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// app/layout.tsx
|
|
81
|
+
import { DhemeSetup } from '@dheme/next';
|
|
82
|
+
|
|
83
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
84
|
+
return (
|
|
85
|
+
<html lang="en" suppressHydrationWarning>
|
|
86
|
+
<body>
|
|
87
|
+
<DhemeSetup
|
|
88
|
+
apiKey={process.env.DHEME_API_KEY!}
|
|
89
|
+
theme="#3b82f6"
|
|
90
|
+
defaultMode="dark" // Specified once — propagated to both script and provider
|
|
91
|
+
themeParams={{ radius: 0.75, tailwindVersion: 'v4' }}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</DhemeSetup>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Prop | Type | Default | Description |
|
|
102
|
+
| ----------------- | ------------------------------------------------- | --------- | ---------------------------------------------------------------- |
|
|
103
|
+
| `theme` | `string` | - | **Required.** Primary HEX color. |
|
|
104
|
+
| `defaultMode` | `'light' \| 'dark'` | `'light'` | Initial color mode. Passed to both script and provider. |
|
|
105
|
+
| `themeParams` | `Omit<GenerateThemeRequest, 'theme'>` | - | Additional generation parameters. |
|
|
106
|
+
| `apiKey` | `string` | - | Dheme API key. Server-side only — never sent to the browser. |
|
|
107
|
+
| `baseUrl` | `string` | - | Override API base URL. |
|
|
108
|
+
| `nonce` | `string` | - | CSP nonce for injected style/script tags. |
|
|
109
|
+
| `onGenerateTheme` | `(params) => Promise<GenerateThemeResponse>` | - | Server-side custom theme function. Only used by `DhemeScript`. |
|
|
110
|
+
| `proxyUrl` | `string` | `'/api/dheme'` | Client-side proxy route URL. |
|
|
111
|
+
| `cookieSync` | `boolean` | `true` | Sync mode and params to cookies. |
|
|
112
|
+
| `persist` | `boolean` | `true` | Persist theme in localStorage. |
|
|
113
|
+
| `autoApply` | `boolean` | `true` | Apply CSS variables automatically. |
|
|
114
|
+
| `children` | `React.ReactNode` | - | **Required.** App content. |
|
|
115
|
+
|
|
116
|
+
> For apps that need `onThemeChange`, `onModeChange`, `onError`, or `loadingContent`, use `DhemeScript` + `DhemeProvider` separately (see below).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
77
120
|
### `<DhemeScript>` (Server Component)
|
|
78
121
|
|
|
79
|
-
Fetches the theme on the server and renders inline `<style>` + `<script>` tags.
|
|
122
|
+
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
123
|
|
|
81
124
|
```tsx
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
/>
|
|
125
|
+
import { DhemeScript, DhemeProvider } from '@dheme/next';
|
|
126
|
+
|
|
127
|
+
// app/layout.tsx
|
|
128
|
+
export default function RootLayout({ children }) {
|
|
129
|
+
return (
|
|
130
|
+
<html lang="en" suppressHydrationWarning>
|
|
131
|
+
<head>
|
|
132
|
+
<DhemeScript
|
|
133
|
+
apiKey={process.env.DHEME_API_KEY!}
|
|
134
|
+
theme="#3b82f6"
|
|
135
|
+
defaultMode="dark"
|
|
136
|
+
themeParams={{ radius: 0.75, tailwindVersion: 'v4' }}
|
|
137
|
+
nonce="abc123"
|
|
138
|
+
/>
|
|
139
|
+
</head>
|
|
140
|
+
<body>
|
|
141
|
+
<DhemeProvider theme="#3b82f6" defaultMode="dark">
|
|
142
|
+
{children}
|
|
143
|
+
</DhemeProvider>
|
|
144
|
+
</body>
|
|
145
|
+
</html>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
96
149
|
```
|
|
97
150
|
|
|
98
151
|
| Prop | Type | Default | Description |
|
|
@@ -127,20 +180,16 @@ A floating FAB for real-time theme generation. Re-exported from `@dheme/react` w
|
|
|
127
180
|
|
|
128
181
|
```tsx
|
|
129
182
|
// app/layout.tsx
|
|
130
|
-
import {
|
|
131
|
-
import { DhemeProvider, ThemeGenerator } from '@dheme/next';
|
|
183
|
+
import { DhemeSetup, ThemeGenerator } from '@dheme/next';
|
|
132
184
|
|
|
133
185
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
134
186
|
return (
|
|
135
187
|
<html lang="en" suppressHydrationWarning>
|
|
136
|
-
<head>
|
|
137
|
-
<DhemeScript apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" />
|
|
138
|
-
</head>
|
|
139
188
|
<body>
|
|
140
|
-
<
|
|
189
|
+
<DhemeSetup apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
|
|
141
190
|
{children}
|
|
142
191
|
<ThemeGenerator />
|
|
143
|
-
</
|
|
192
|
+
</DhemeSetup>
|
|
144
193
|
</body>
|
|
145
194
|
</html>
|
|
146
195
|
);
|
|
@@ -303,23 +352,20 @@ DHEME_BASE_URL=http://localhost:3005
|
|
|
303
352
|
### `app/layout.tsx`
|
|
304
353
|
|
|
305
354
|
```tsx
|
|
306
|
-
import {
|
|
307
|
-
import { DhemeProvider } from '@dheme/next';
|
|
355
|
+
import { DhemeSetup } from '@dheme/next';
|
|
308
356
|
|
|
309
357
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
310
358
|
return (
|
|
311
359
|
<html lang="en" suppressHydrationWarning>
|
|
312
|
-
<
|
|
313
|
-
<
|
|
360
|
+
<body>
|
|
361
|
+
<DhemeSetup
|
|
314
362
|
apiKey={process.env.DHEME_API_KEY!}
|
|
315
363
|
theme="#3b82f6"
|
|
364
|
+
defaultMode="light"
|
|
316
365
|
themeParams={{ radius: 0.5 }}
|
|
317
|
-
|
|
318
|
-
</head>
|
|
319
|
-
<body>
|
|
320
|
-
<DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
|
|
366
|
+
>
|
|
321
367
|
{children}
|
|
322
|
-
</
|
|
368
|
+
</DhemeSetup>
|
|
323
369
|
</body>
|
|
324
370
|
</html>
|
|
325
371
|
);
|
|
@@ -386,10 +432,14 @@ export async function GET(request: Request) {
|
|
|
386
432
|
Server Client
|
|
387
433
|
────── ──────
|
|
388
434
|
|
|
435
|
+
DhemeSetup (Server Component)
|
|
436
|
+
│
|
|
437
|
+
├──────────────────────────────────────────────┐
|
|
438
|
+
│ │
|
|
389
439
|
Request → DhemeScript (Server Component) DhemeProvider (Client Component)
|
|
390
440
|
│ │
|
|
391
441
|
├─ themeCache.get(key) ├─ localStorage cache check
|
|
392
|
-
│ ↓ miss? call SDK │ ↓ hit?
|
|
442
|
+
│ ↓ miss? call SDK │ ↓ hit? apply synchronously
|
|
393
443
|
├─ themeCache.set(key, data) ├─ background revalidation
|
|
394
444
|
│ │
|
|
395
445
|
├─ <style> with :root + .dark ├─ cookie sync (mode + params)
|
|
@@ -414,13 +464,16 @@ Request → DhemeScript (Server Component) DhemeProvider (Client Component)
|
|
|
414
464
|
|
|
415
465
|
## TypeScript
|
|
416
466
|
|
|
417
|
-
All types are exported
|
|
467
|
+
All types are exported from `@dheme/next`:
|
|
418
468
|
|
|
419
469
|
```typescript
|
|
420
|
-
// Client types
|
|
421
470
|
import type {
|
|
471
|
+
// Component props
|
|
472
|
+
DhemeSetupProps,
|
|
422
473
|
DhemeProviderProps,
|
|
474
|
+
DhemeScriptProps,
|
|
423
475
|
ThemeGeneratorProps,
|
|
476
|
+
// Theme types
|
|
424
477
|
ThemeMode,
|
|
425
478
|
ThemeDataState,
|
|
426
479
|
ThemeActionsState,
|
|
@@ -430,8 +483,8 @@ import type {
|
|
|
430
483
|
HSLColor,
|
|
431
484
|
} from '@dheme/next';
|
|
432
485
|
|
|
433
|
-
// Server types
|
|
434
|
-
import type {
|
|
486
|
+
// Server utility types
|
|
487
|
+
import type { GenerateThemeStylesOptions } from '@dheme/next/server';
|
|
435
488
|
```
|
|
436
489
|
|
|
437
490
|
## Related Packages
|
package/dist/index.d.mts
CHANGED
|
@@ -24,6 +24,66 @@ 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
|
+
children: React.ReactNode;
|
|
86
|
+
}
|
|
27
87
|
interface GenerateThemeStylesOptions {
|
|
28
88
|
/** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
|
|
29
89
|
apiKey?: string;
|
|
@@ -40,4 +100,31 @@ interface GenerateThemeStylesOptions {
|
|
|
40
100
|
|
|
41
101
|
declare function DhemeProvider({ children, cookieSync, proxyUrl, onThemeChange, onModeChange, onGenerateTheme, theme: primaryColor, themeParams, ...props }: DhemeProviderProps): React.ReactElement;
|
|
42
102
|
|
|
43
|
-
|
|
103
|
+
declare function DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme, }: DhemeScriptProps): Promise<React.ReactElement>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Combined Server Component that renders DhemeScript + DhemeProvider in a single declaration.
|
|
107
|
+
* Accepts `defaultMode` once, eliminating the need to keep it in sync across two components.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // app/layout.tsx
|
|
111
|
+
* import { DhemeSetup } from '@dheme/next';
|
|
112
|
+
*
|
|
113
|
+
* export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
114
|
+
* return (
|
|
115
|
+
* <html>
|
|
116
|
+
* <body>
|
|
117
|
+
* <DhemeSetup theme="7C3AED" defaultMode="dark" apiKey={process.env.DHEME_API_KEY}>
|
|
118
|
+
* {children}
|
|
119
|
+
* </DhemeSetup>
|
|
120
|
+
* </body>
|
|
121
|
+
* </html>
|
|
122
|
+
* );
|
|
123
|
+
* }
|
|
124
|
+
*
|
|
125
|
+
* For apps that need client-side callbacks (onThemeChange, onModeChange, onError, loadingContent),
|
|
126
|
+
* use DhemeScript + DhemeProvider separately.
|
|
127
|
+
*/
|
|
128
|
+
declare function DhemeSetup({ theme, defaultMode, themeParams, apiKey, baseUrl, nonce, onGenerateTheme, proxyUrl, cookieSync, persist, autoApply, children, }: DhemeSetupProps): Promise<React.ReactElement>;
|
|
129
|
+
|
|
130
|
+
export { DhemeProvider, type DhemeProviderProps, DhemeScript, type DhemeScriptProps, DhemeSetup, type DhemeSetupProps, type GenerateThemeStylesOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,66 @@ 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
|
+
children: React.ReactNode;
|
|
86
|
+
}
|
|
27
87
|
interface GenerateThemeStylesOptions {
|
|
28
88
|
/** API key (obrigatório para uso externo; omitir para rotas internas sem autenticação) */
|
|
29
89
|
apiKey?: string;
|
|
@@ -40,4 +100,31 @@ interface GenerateThemeStylesOptions {
|
|
|
40
100
|
|
|
41
101
|
declare function DhemeProvider({ children, cookieSync, proxyUrl, onThemeChange, onModeChange, onGenerateTheme, theme: primaryColor, themeParams, ...props }: DhemeProviderProps): React.ReactElement;
|
|
42
102
|
|
|
43
|
-
|
|
103
|
+
declare function DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme, }: DhemeScriptProps): Promise<React.ReactElement>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Combined Server Component that renders DhemeScript + DhemeProvider in a single declaration.
|
|
107
|
+
* Accepts `defaultMode` once, eliminating the need to keep it in sync across two components.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // app/layout.tsx
|
|
111
|
+
* import { DhemeSetup } from '@dheme/next';
|
|
112
|
+
*
|
|
113
|
+
* export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
114
|
+
* return (
|
|
115
|
+
* <html>
|
|
116
|
+
* <body>
|
|
117
|
+
* <DhemeSetup theme="7C3AED" defaultMode="dark" apiKey={process.env.DHEME_API_KEY}>
|
|
118
|
+
* {children}
|
|
119
|
+
* </DhemeSetup>
|
|
120
|
+
* </body>
|
|
121
|
+
* </html>
|
|
122
|
+
* );
|
|
123
|
+
* }
|
|
124
|
+
*
|
|
125
|
+
* For apps that need client-side callbacks (onThemeChange, onModeChange, onError, loadingContent),
|
|
126
|
+
* use DhemeScript + DhemeProvider separately.
|
|
127
|
+
*/
|
|
128
|
+
declare function DhemeSetup({ theme, defaultMode, themeParams, apiKey, baseUrl, nonce, onGenerateTheme, proxyUrl, cookieSync, persist, autoApply, children, }: DhemeSetupProps): Promise<React.ReactElement>;
|
|
129
|
+
|
|
130
|
+
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: () =>
|
|
37
|
-
buildCacheKey: () =>
|
|
38
|
-
themeToCSS: () =>
|
|
39
|
-
useDhemeClient: () =>
|
|
40
|
-
useGenerateTheme: () =>
|
|
41
|
-
useTheme: () =>
|
|
42
|
-
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,137 @@ 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
|
+
children
|
|
225
|
+
}) {
|
|
226
|
+
return import_react5.default.createElement(
|
|
227
|
+
import_react5.default.Fragment,
|
|
228
|
+
null,
|
|
229
|
+
// Server Component: fetches theme server-side, emits render-blocking <style> + <script>
|
|
230
|
+
await DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme }),
|
|
231
|
+
// Client Component: provides theme context, handles runtime interactivity
|
|
232
|
+
import_react5.default.createElement(DhemeProvider, {
|
|
233
|
+
theme,
|
|
234
|
+
themeParams,
|
|
235
|
+
defaultMode,
|
|
236
|
+
proxyUrl,
|
|
237
|
+
cookieSync,
|
|
238
|
+
persist,
|
|
239
|
+
autoApply,
|
|
240
|
+
children
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
120
245
|
// src/index.ts
|
|
121
|
-
var
|
|
122
|
-
var
|
|
246
|
+
var import_react6 = require("@dheme/react");
|
|
247
|
+
var import_react7 = require("@dheme/react");
|
|
123
248
|
// Annotate the CommonJS export names for ESM import in node:
|
|
124
249
|
0 && (module.exports = {
|
|
125
250
|
DhemeProvider,
|
|
251
|
+
DhemeScript,
|
|
252
|
+
DhemeSetup,
|
|
126
253
|
ThemeGenerator,
|
|
127
254
|
applyThemeCSSVariables,
|
|
128
255
|
buildCacheKey,
|
package/dist/index.mjs
CHANGED
|
@@ -74,15 +74,140 @@ 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
|
+
children
|
|
180
|
+
}) {
|
|
181
|
+
return React3.createElement(
|
|
182
|
+
React3.Fragment,
|
|
183
|
+
null,
|
|
184
|
+
// Server Component: fetches theme server-side, emits render-blocking <style> + <script>
|
|
185
|
+
await DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, onGenerateTheme }),
|
|
186
|
+
// Client Component: provides theme context, handles runtime interactivity
|
|
187
|
+
React3.createElement(DhemeProvider, {
|
|
188
|
+
theme,
|
|
189
|
+
themeParams,
|
|
190
|
+
defaultMode,
|
|
191
|
+
proxyUrl,
|
|
192
|
+
cookieSync,
|
|
193
|
+
persist,
|
|
194
|
+
autoApply,
|
|
195
|
+
children
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
77
200
|
// src/index.ts
|
|
78
201
|
import { useTheme, useThemeActions, useGenerateTheme, useDhemeClient } from "@dheme/react";
|
|
79
|
-
import { themeToCSS, applyThemeCSSVariables, buildCacheKey } from "@dheme/react";
|
|
202
|
+
import { themeToCSS as themeToCSS2, applyThemeCSSVariables, buildCacheKey as buildCacheKey2 } from "@dheme/react";
|
|
80
203
|
export {
|
|
81
204
|
DhemeProvider,
|
|
205
|
+
DhemeScript,
|
|
206
|
+
DhemeSetup,
|
|
82
207
|
ThemeGenerator,
|
|
83
208
|
applyThemeCSSVariables,
|
|
84
|
-
buildCacheKey,
|
|
85
|
-
themeToCSS,
|
|
209
|
+
buildCacheKey2 as buildCacheKey,
|
|
210
|
+
themeToCSS2 as themeToCSS,
|
|
86
211
|
useDhemeClient,
|
|
87
212
|
useGenerateTheme,
|
|
88
213
|
useTheme,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dheme/next",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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.
|
|
59
|
+
"@dheme/react": "^2.15.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/react": "^18.2.0",
|