@dheme/next 1.0.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 ADDED
@@ -0,0 +1,401 @@
1
+ # @dheme/next
2
+
3
+ Next.js App Router bindings for the [Dheme](https://dheme.com) Theme Generator API. Server-side theme generation with **zero FOUC** — even on first visit.
4
+
5
+ Built for **Next.js 14+** (App Router). For **React SPAs** (Vite, CRA), use [`@dheme/react`](https://www.npmjs.com/package/@dheme/react) instead.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @dheme/next @dheme/react @dheme/sdk
11
+ ```
12
+
13
+ ```bash
14
+ yarn add @dheme/next @dheme/react @dheme/sdk
15
+ ```
16
+
17
+ ```bash
18
+ pnpm add @dheme/next @dheme/react @dheme/sdk
19
+ ```
20
+
21
+ ### Requirements
22
+
23
+ | Dependency | Version | Why |
24
+ | -------------- | -------- | ----------------------------- |
25
+ | `next` | >= 14 | App Router, Server Components |
26
+ | `react` | >= 18 | Peer dependency |
27
+ | `react-dom` | >= 18 | Peer dependency |
28
+ | `@dheme/react` | >= 2.0.0 | Shared hooks, utils, provider |
29
+ | `@dheme/sdk` | >= 1.1.0 | Core API client |
30
+
31
+ `@dheme/react` and `@dheme/sdk` are included as dependencies and installed automatically.
32
+
33
+ ## Quick Start
34
+
35
+ ```tsx
36
+ // app/layout.tsx (Server Component)
37
+ import { DhemeScript, DhemeProvider } from '@dheme/next';
38
+
39
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
40
+ return (
41
+ <html lang="en">
42
+ <head>
43
+ <DhemeScript apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6" />
44
+ </head>
45
+ <body>
46
+ <DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
47
+ {children}
48
+ </DhemeProvider>
49
+ </body>
50
+ </html>
51
+ );
52
+ }
53
+ ```
54
+
55
+ That's it. Your app has 19 CSS variables applied server-side — zero client-side fetch, zero FOUC.
56
+
57
+ ## How It Works
58
+
59
+ ### Every visit (zero FOUC, zero client fetch)
60
+
61
+ 1. `<DhemeScript>` is a **Server Component** — it calls the Dheme API on the server
62
+ 2. It inlines a `<style>` tag with CSS variables for **both** light and dark modes
63
+ 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
64
+ 4. The browser receives HTML with styles already applied — **before any paint**
65
+ 5. React hydrates, `DhemeProvider` sets `isReady = true` — no API call needed
66
+
67
+ ### Server-side caching
68
+
69
+ The server maintains an **in-memory LRU cache** (100 entries, 1h TTL). Since theme generation is deterministic (same input = same output), the cache is highly effective:
70
+
71
+ - **First request:** API call to Dheme → cached
72
+ - **All subsequent requests:** Served from memory, no API call
73
+
74
+ ## Components
75
+
76
+ ### `<DhemeScript>` (Server Component)
77
+
78
+ Fetches the theme on the server and renders inline `<style>` + `<script>` tags. Place it in `<head>`.
79
+
80
+ ```tsx
81
+ <DhemeScript
82
+ apiKey={process.env.DHEME_API_KEY!} // Required — server-side only
83
+ theme="#3b82f6" // Required — primary HEX color
84
+ themeParams={{
85
+ // Optional generation params
86
+ radius: 0.75,
87
+ saturationAdjust: 10,
88
+ }}
89
+ defaultMode="light" // 'light' | 'dark' (default: 'light')
90
+ baseUrl="http://localhost:3005" // Override API URL (optional)
91
+ nonce="abc123" // CSP nonce (optional)
92
+ />
93
+ ```
94
+
95
+ | Prop | Type | Default | Description |
96
+ | ------------- | ------------------------------------- | --------- | ------------------------------------- |
97
+ | `apiKey` | `string` | - | **Required.** Dheme API key. |
98
+ | `theme` | `string` | - | **Required.** Primary HEX color. |
99
+ | `themeParams` | `Omit<GenerateThemeRequest, 'theme'>` | - | Additional generation parameters. |
100
+ | `defaultMode` | `'light' \| 'dark'` | `'light'` | Fallback mode if no preference found. |
101
+ | `baseUrl` | `string` | - | Override API base URL. |
102
+ | `nonce` | `string` | - | CSP nonce for style and script tags. |
103
+
104
+ **What it renders:**
105
+
106
+ ```html
107
+ <!-- CSS for both modes — parsed before paint -->
108
+ <style>
109
+ :root { --background:0 0% 100%; --primary:221.2 83.2% 53.3%; ... }
110
+ .dark { --background:222.2 84% 4.9%; --primary:217.2 91.2% 59.8%; ... }
111
+ </style>
112
+
113
+ <!-- Mode detection — applies .dark class if needed -->
114
+ <script>
115
+ (function(){try{var m=document.cookie.match(/dheme-mode=(\w+)/);...})()
116
+ </script>
117
+ ```
118
+
119
+ ### `<DhemeProvider>` (Client Component)
120
+
121
+ Wraps `@dheme/react`'s provider with **cookie synchronization**. When the user changes theme or mode on the client, it writes to cookies so the server can read them on the next request.
122
+
123
+ ```tsx
124
+ <DhemeProvider
125
+ apiKey={process.env.DHEME_API_KEY!}
126
+ theme="#3b82f6"
127
+ cookieSync={true} // Sync mode/params to cookies (default: true)
128
+ >
129
+ {children}
130
+ </DhemeProvider>
131
+ ```
132
+
133
+ Accepts all props from `@dheme/react`'s `DhemeProvider` plus:
134
+
135
+ | Prop | Type | Default | Description |
136
+ | ------------ | --------- | ------- | -------------------------------------- |
137
+ | `cookieSync` | `boolean` | `true` | Sync theme params and mode to cookies. |
138
+
139
+ ### Cookie strategy
140
+
141
+ | Cookie | Size | Contains | Purpose |
142
+ | -------------- | ---------- | -------------------------------- | ---------------------------- |
143
+ | `dheme-mode` | ~5 bytes | `"light"` or `"dark"` | Server reads mode preference |
144
+ | `dheme-params` | ~100 bytes | Base64-encoded generation params | Server rebuilds cache key |
145
+
146
+ Only lightweight data is stored in cookies — never the full theme response.
147
+
148
+ ## Hooks
149
+
150
+ All hooks are re-exported from `@dheme/react`. Import them from `@dheme/next` directly:
151
+
152
+ ```tsx
153
+ import { useTheme, useThemeActions, useGenerateTheme, useDhemeClient } from '@dheme/next';
154
+ ```
155
+
156
+ ### `useTheme()`
157
+
158
+ ```tsx
159
+ const { theme, mode, isReady } = useTheme();
160
+ ```
161
+
162
+ Read theme data. Only re-renders when theme or mode changes.
163
+
164
+ ### `useThemeActions()`
165
+
166
+ ```tsx
167
+ const { setMode, generateTheme, clearTheme, isLoading, error } = useThemeActions();
168
+ ```
169
+
170
+ Access actions. Only re-renders on action state changes.
171
+
172
+ ### `useGenerateTheme()`
173
+
174
+ ```tsx
175
+ const { generateTheme, isGenerating, error } = useGenerateTheme();
176
+ ```
177
+
178
+ Local loading state for individual components.
179
+
180
+ ### `useDhemeClient()`
181
+
182
+ ```tsx
183
+ const client = useDhemeClient();
184
+ ```
185
+
186
+ Raw `DhemeClient` access for `getUsage()`, etc.
187
+
188
+ ## Server Utilities
189
+
190
+ Import server-only functions from `@dheme/next/server`:
191
+
192
+ ```tsx
193
+ import { generateThemeStyles, themeCache } from '@dheme/next/server';
194
+ ```
195
+
196
+ ### `generateThemeStyles(options)`
197
+
198
+ Generate theme CSS on the server. Uses the in-memory LRU cache.
199
+
200
+ ```tsx
201
+ import { generateThemeStyles } from '@dheme/next/server';
202
+
203
+ // In a Server Component or Route Handler
204
+ const css = await generateThemeStyles({
205
+ apiKey: process.env.DHEME_API_KEY!,
206
+ theme: '#3b82f6',
207
+ mode: 'light',
208
+ });
209
+
210
+ // css = "--background:0 0% 100%;--foreground:222.2 84% 4.9%;..."
211
+ ```
212
+
213
+ | Option | Type | Default | Description |
214
+ | ------------- | ------------------------------------- | --------- | -------------------------------- |
215
+ | `apiKey` | `string` | - | **Required.** Dheme API key. |
216
+ | `theme` | `string` | - | **Required.** Primary HEX color. |
217
+ | `themeParams` | `Omit<GenerateThemeRequest, 'theme'>` | - | Additional generation params. |
218
+ | `mode` | `'light' \| 'dark'` | `'light'` | Which mode's CSS to generate. |
219
+ | `baseUrl` | `string` | - | Override API base URL. |
220
+
221
+ ### `themeCache`
222
+
223
+ The in-memory LRU cache instance. Useful for cache management:
224
+
225
+ ```tsx
226
+ import { themeCache } from '@dheme/next/server';
227
+
228
+ // Clear all cached themes (e.g., after a deployment)
229
+ themeCache.clear();
230
+ ```
231
+
232
+ ### `getModeFromCookie()` / `getParamsFromCookie()`
233
+
234
+ Read theme data from cookies in Server Components or Route Handlers:
235
+
236
+ ```tsx
237
+ import { getModeFromCookie, getParamsFromCookie } from '@dheme/next/server';
238
+
239
+ const mode = await getModeFromCookie(); // 'light' | 'dark' | null
240
+ const params = await getParamsFromCookie(); // JSON string | null
241
+ ```
242
+
243
+ ## Environment Variables
244
+
245
+ | Variable | Where to set | Description |
246
+ | ---------------- | ------------ | ------------------------------------- |
247
+ | `DHEME_API_KEY` | `.env.local` | Your Dheme API key (server-side only) |
248
+ | `DHEME_BASE_URL` | `.env.local` | Override API URL for local dev |
249
+
250
+ **Important:** `DHEME_API_KEY` is a server-side secret — do **not** prefix it with `NEXT_PUBLIC_`. The `<DhemeScript>` Server Component reads it on the server and never exposes it to the client.
251
+
252
+ For local development with a local Dheme API:
253
+
254
+ ```bash
255
+ # .env.local
256
+ DHEME_API_KEY=dheme_abc12345_...
257
+ DHEME_BASE_URL=http://localhost:3005
258
+ ```
259
+
260
+ ## Full Example
261
+
262
+ ### `app/layout.tsx`
263
+
264
+ ```tsx
265
+ import { DhemeScript, DhemeProvider } from '@dheme/next';
266
+
267
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
268
+ return (
269
+ <html lang="en" suppressHydrationWarning>
270
+ <head>
271
+ <DhemeScript
272
+ apiKey={process.env.DHEME_API_KEY!}
273
+ theme="#3b82f6"
274
+ themeParams={{ radius: 0.5 }}
275
+ />
276
+ </head>
277
+ <body>
278
+ <DhemeProvider apiKey={process.env.DHEME_API_KEY!} theme="#3b82f6">
279
+ {children}
280
+ </DhemeProvider>
281
+ </body>
282
+ </html>
283
+ );
284
+ }
285
+ ```
286
+
287
+ ### `app/page.tsx` (Server Component)
288
+
289
+ ```tsx
290
+ // Server Components work — CSS variables are available globally
291
+ export default function Home() {
292
+ return (
293
+ <main className="bg-background text-foreground">
294
+ <h1 className="text-primary">Welcome</h1>
295
+ <ThemeToggle />
296
+ </main>
297
+ );
298
+ }
299
+ ```
300
+
301
+ ### `components/ThemeToggle.tsx` (Client Component)
302
+
303
+ ```tsx
304
+ 'use client';
305
+
306
+ import { useTheme, useThemeActions } from '@dheme/next';
307
+
308
+ export function ThemeToggle() {
309
+ const { mode } = useTheme();
310
+ const { setMode } = useThemeActions();
311
+
312
+ return (
313
+ <button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
314
+ {mode === 'light' ? 'Dark' : 'Light'} Mode
315
+ </button>
316
+ );
317
+ }
318
+ ```
319
+
320
+ ### `app/api/theme/route.ts` (Route Handler)
321
+
322
+ ```tsx
323
+ import { generateThemeStyles } from '@dheme/next/server';
324
+
325
+ export async function GET(request: Request) {
326
+ const { searchParams } = new URL(request.url);
327
+ const color = searchParams.get('color') || '#3b82f6';
328
+
329
+ const css = await generateThemeStyles({
330
+ apiKey: process.env.DHEME_API_KEY!,
331
+ theme: color,
332
+ mode: 'light',
333
+ });
334
+
335
+ return new Response(`:root{${css}}`, {
336
+ headers: { 'Content-Type': 'text/css' },
337
+ });
338
+ }
339
+ ```
340
+
341
+ ## Architecture
342
+
343
+ ```
344
+ Server Client
345
+ ────── ──────
346
+
347
+ Request → DhemeScript (Server Component) DhemeProvider (Client Component)
348
+ │ │
349
+ ├─ themeCache.get(key) ├─ localStorage cache check
350
+ │ ↓ miss? call SDK │ ↓ hit? serve instantly
351
+ ├─ themeCache.set(key, data) ├─ background revalidation
352
+ │ │
353
+ ├─ <style> with :root + .dark ├─ cookie sync (mode + params)
354
+ └─ <script> mode detection └─ context providers (split)
355
+ ├─ ThemeDataContext
356
+ └─ ThemeActionsContext
357
+ ```
358
+
359
+ ## Comparison: @dheme/react vs @dheme/next
360
+
361
+ | Feature | @dheme/react | @dheme/next |
362
+ | ------------------------ | -------------------- | ---------------------------- |
363
+ | **Platform** | Vite, CRA, SPAs | Next.js 14+ App Router |
364
+ | **FOUC on first visit** | Brief (no cache yet) | None (CSS inline in HTML) |
365
+ | **FOUC on cached visit** | None | None |
366
+ | **Theme fetch** | Client-side | Server-side |
367
+ | **API key exposure** | In client bundle | Server-only (secure) |
368
+ | **Caching** | localStorage | LRU in-memory + localStorage |
369
+ | **Mode persistence** | localStorage | Cookies + localStorage |
370
+ | **SSR support** | No | Yes |
371
+
372
+ ## TypeScript
373
+
374
+ All types are exported:
375
+
376
+ ```typescript
377
+ import type {
378
+ DhemeProviderProps,
379
+ DhemeScriptProps,
380
+ GenerateThemeStylesOptions,
381
+ ThemeMode,
382
+ ThemeDataState,
383
+ ThemeActionsState,
384
+ GenerateThemeRequest,
385
+ GenerateThemeResponse,
386
+ ColorTokens,
387
+ HSLColor,
388
+ } from '@dheme/next';
389
+ ```
390
+
391
+ ## Related Packages
392
+
393
+ | Package | Description | When to use |
394
+ | -------------- | ------------------------------- | -------------------------- |
395
+ | `@dheme/sdk` | Core TypeScript SDK | Direct API access, Node.js |
396
+ | `@dheme/react` | React bindings | Vite, CRA, React SPAs |
397
+ | `@dheme/next` | Next.js bindings (this package) | Next.js 14+ with SSR |
398
+
399
+ ## License
400
+
401
+ MIT
@@ -0,0 +1,34 @@
1
+ // src/server/cache.ts
2
+ var ThemeCache = class {
3
+ constructor(maxSize = 100, ttlMs = 36e5) {
4
+ this.cache = /* @__PURE__ */ new Map();
5
+ this.maxSize = maxSize;
6
+ this.ttl = ttlMs;
7
+ }
8
+ get(key) {
9
+ const entry = this.cache.get(key);
10
+ if (!entry) return null;
11
+ if (Date.now() - entry.timestamp > this.ttl) {
12
+ this.cache.delete(key);
13
+ return null;
14
+ }
15
+ return entry.data;
16
+ }
17
+ set(key, data) {
18
+ if (this.cache.size >= this.maxSize) {
19
+ const oldestKey = this.cache.keys().next().value;
20
+ if (oldestKey !== void 0) {
21
+ this.cache.delete(oldestKey);
22
+ }
23
+ }
24
+ this.cache.set(key, { data, timestamp: Date.now() });
25
+ }
26
+ clear() {
27
+ this.cache.clear();
28
+ }
29
+ };
30
+ var themeCache = new ThemeCache();
31
+
32
+ export {
33
+ themeCache
34
+ };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { D as DhemeProviderProps, a as DhemeScriptProps } from './types-yW-Y2Yox.mjs';
3
+ export { G as GenerateThemeStylesOptions } from './types-yW-Y2Yox.mjs';
4
+ export { ThemeActionsState, ThemeDataState, ThemeMode, applyThemeCSSVariables, buildCacheKey, themeToCSS, useDhemeClient, useGenerateTheme, useTheme, useThemeActions } from '@dheme/react';
5
+ export { ColorTokens, GenerateThemeRequest, GenerateThemeResponse, HSLColor } from '@dheme/sdk';
6
+
7
+ declare function DhemeProvider({ children, cookieSync, onThemeChange, onModeChange, theme: primaryColor, themeParams, ...props }: DhemeProviderProps): React.ReactElement;
8
+
9
+ declare function DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, }: DhemeScriptProps): Promise<React.ReactElement>;
10
+
11
+ export { DhemeProvider, DhemeProviderProps, DhemeScript, DhemeScriptProps };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { D as DhemeProviderProps, a as DhemeScriptProps } from './types-yW-Y2Yox.js';
3
+ export { G as GenerateThemeStylesOptions } from './types-yW-Y2Yox.js';
4
+ export { ThemeActionsState, ThemeDataState, ThemeMode, applyThemeCSSVariables, buildCacheKey, themeToCSS, useDhemeClient, useGenerateTheme, useTheme, useThemeActions } from '@dheme/react';
5
+ export { ColorTokens, GenerateThemeRequest, GenerateThemeResponse, HSLColor } from '@dheme/sdk';
6
+
7
+ declare function DhemeProvider({ children, cookieSync, onThemeChange, onModeChange, theme: primaryColor, themeParams, ...props }: DhemeProviderProps): React.ReactElement;
8
+
9
+ declare function DhemeScript({ apiKey, theme, themeParams, defaultMode, baseUrl, nonce, }: DhemeScriptProps): Promise<React.ReactElement>;
10
+
11
+ export { DhemeProvider, DhemeProviderProps, DhemeScript, DhemeScriptProps };
package/dist/index.js ADDED
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DhemeProvider: () => DhemeProvider,
34
+ DhemeScript: () => DhemeScript,
35
+ applyThemeCSSVariables: () => import_react6.applyThemeCSSVariables,
36
+ buildCacheKey: () => import_react6.buildCacheKey,
37
+ themeToCSS: () => import_react6.themeToCSS,
38
+ useDhemeClient: () => import_react5.useDhemeClient,
39
+ useGenerateTheme: () => import_react5.useGenerateTheme,
40
+ useTheme: () => import_react5.useTheme,
41
+ useThemeActions: () => import_react5.useThemeActions
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/components/DhemeProvider.tsx
46
+ var import_react = __toESM(require("react"));
47
+ var import_react2 = require("@dheme/react");
48
+ var COOKIE_MAX_AGE = 31536e3;
49
+ function setCookie(name, value) {
50
+ document.cookie = `${name}=${value};path=/;max-age=${COOKIE_MAX_AGE};SameSite=Lax`;
51
+ }
52
+ function DhemeProvider({
53
+ children,
54
+ cookieSync = true,
55
+ onThemeChange,
56
+ onModeChange,
57
+ theme: primaryColor,
58
+ themeParams,
59
+ ...props
60
+ }) {
61
+ const paramsRef = (0, import_react.useRef)({ theme: primaryColor, ...themeParams });
62
+ paramsRef.current = { theme: primaryColor, ...themeParams };
63
+ const handleThemeChange = (0, import_react.useCallback)(
64
+ (theme) => {
65
+ if (cookieSync) {
66
+ try {
67
+ setCookie("dheme-params", btoa(JSON.stringify(paramsRef.current)));
68
+ } catch {
69
+ }
70
+ }
71
+ onThemeChange?.(theme);
72
+ },
73
+ [cookieSync, onThemeChange]
74
+ );
75
+ const handleModeChange = (0, import_react.useCallback)(
76
+ (mode) => {
77
+ if (cookieSync) {
78
+ setCookie("dheme-mode", mode);
79
+ }
80
+ onModeChange?.(mode);
81
+ },
82
+ [cookieSync, onModeChange]
83
+ );
84
+ return import_react.default.createElement(import_react2.DhemeProvider, {
85
+ ...props,
86
+ theme: primaryColor,
87
+ themeParams,
88
+ persist: true,
89
+ onThemeChange: handleThemeChange,
90
+ onModeChange: handleModeChange,
91
+ children
92
+ });
93
+ }
94
+
95
+ // src/components/DhemeScript.tsx
96
+ var import_react3 = __toESM(require("react"));
97
+ var import_sdk = require("@dheme/sdk");
98
+ var import_react4 = require("@dheme/react");
99
+
100
+ // src/server/cache.ts
101
+ var ThemeCache = class {
102
+ constructor(maxSize = 100, ttlMs = 36e5) {
103
+ this.cache = /* @__PURE__ */ new Map();
104
+ this.maxSize = maxSize;
105
+ this.ttl = ttlMs;
106
+ }
107
+ get(key) {
108
+ const entry = this.cache.get(key);
109
+ if (!entry) return null;
110
+ if (Date.now() - entry.timestamp > this.ttl) {
111
+ this.cache.delete(key);
112
+ return null;
113
+ }
114
+ return entry.data;
115
+ }
116
+ set(key, data) {
117
+ if (this.cache.size >= this.maxSize) {
118
+ const oldestKey = this.cache.keys().next().value;
119
+ if (oldestKey !== void 0) {
120
+ this.cache.delete(oldestKey);
121
+ }
122
+ }
123
+ this.cache.set(key, { data, timestamp: Date.now() });
124
+ }
125
+ clear() {
126
+ this.cache.clear();
127
+ }
128
+ };
129
+ var themeCache = new ThemeCache();
130
+
131
+ // src/components/DhemeScript.tsx
132
+ async function DhemeScript({
133
+ apiKey,
134
+ theme,
135
+ themeParams,
136
+ defaultMode = "light",
137
+ baseUrl,
138
+ nonce
139
+ }) {
140
+ const params = { theme, ...themeParams };
141
+ const cacheKey = (0, import_react4.buildCacheKey)(params);
142
+ let themeData = themeCache.get(cacheKey);
143
+ if (!themeData) {
144
+ const client = new import_sdk.DhemeClient({ apiKey, baseUrl });
145
+ const response = await client.generateTheme(params);
146
+ themeData = response.data;
147
+ themeCache.set(cacheKey, themeData);
148
+ }
149
+ const lightCSS = (0, import_react4.themeToCSS)(themeData, "light");
150
+ const darkCSS = (0, import_react4.themeToCSS)(themeData, "dark");
151
+ const styleContent = `:root{${lightCSS}}.dark{${darkCSS}}`;
152
+ const scriptContent = (0, import_react4.getNextBlockingScriptPayload)(defaultMode);
153
+ return import_react3.default.createElement(
154
+ import_react3.default.Fragment,
155
+ null,
156
+ import_react3.default.createElement("style", {
157
+ nonce,
158
+ dangerouslySetInnerHTML: { __html: styleContent }
159
+ }),
160
+ import_react3.default.createElement("script", {
161
+ nonce,
162
+ dangerouslySetInnerHTML: { __html: scriptContent }
163
+ })
164
+ );
165
+ }
166
+
167
+ // src/index.ts
168
+ var import_react5 = require("@dheme/react");
169
+ var import_react6 = require("@dheme/react");
170
+ // Annotate the CommonJS export names for ESM import in node:
171
+ 0 && (module.exports = {
172
+ DhemeProvider,
173
+ DhemeScript,
174
+ applyThemeCSSVariables,
175
+ buildCacheKey,
176
+ themeToCSS,
177
+ useDhemeClient,
178
+ useGenerateTheme,
179
+ useTheme,
180
+ useThemeActions
181
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,107 @@
1
+ import {
2
+ themeCache
3
+ } from "./chunk-7HW3FXOF.mjs";
4
+
5
+ // src/components/DhemeProvider.tsx
6
+ import React, { useCallback, useRef } from "react";
7
+ import { DhemeProvider as ReactDhemeProvider } from "@dheme/react";
8
+ var COOKIE_MAX_AGE = 31536e3;
9
+ function setCookie(name, value) {
10
+ document.cookie = `${name}=${value};path=/;max-age=${COOKIE_MAX_AGE};SameSite=Lax`;
11
+ }
12
+ function DhemeProvider({
13
+ children,
14
+ cookieSync = true,
15
+ onThemeChange,
16
+ onModeChange,
17
+ theme: primaryColor,
18
+ themeParams,
19
+ ...props
20
+ }) {
21
+ const paramsRef = useRef({ theme: primaryColor, ...themeParams });
22
+ paramsRef.current = { theme: primaryColor, ...themeParams };
23
+ const handleThemeChange = useCallback(
24
+ (theme) => {
25
+ if (cookieSync) {
26
+ try {
27
+ setCookie("dheme-params", btoa(JSON.stringify(paramsRef.current)));
28
+ } catch {
29
+ }
30
+ }
31
+ onThemeChange?.(theme);
32
+ },
33
+ [cookieSync, onThemeChange]
34
+ );
35
+ const handleModeChange = useCallback(
36
+ (mode) => {
37
+ if (cookieSync) {
38
+ setCookie("dheme-mode", mode);
39
+ }
40
+ onModeChange?.(mode);
41
+ },
42
+ [cookieSync, onModeChange]
43
+ );
44
+ return React.createElement(ReactDhemeProvider, {
45
+ ...props,
46
+ theme: primaryColor,
47
+ themeParams,
48
+ persist: true,
49
+ onThemeChange: handleThemeChange,
50
+ onModeChange: handleModeChange,
51
+ children
52
+ });
53
+ }
54
+
55
+ // src/components/DhemeScript.tsx
56
+ import React2 from "react";
57
+ import { DhemeClient } from "@dheme/sdk";
58
+ import { themeToCSS, buildCacheKey, getNextBlockingScriptPayload } from "@dheme/react";
59
+ async function DhemeScript({
60
+ apiKey,
61
+ theme,
62
+ themeParams,
63
+ defaultMode = "light",
64
+ baseUrl,
65
+ nonce
66
+ }) {
67
+ const params = { theme, ...themeParams };
68
+ const cacheKey = buildCacheKey(params);
69
+ let themeData = themeCache.get(cacheKey);
70
+ if (!themeData) {
71
+ const client = new DhemeClient({ apiKey, baseUrl });
72
+ const response = await client.generateTheme(params);
73
+ themeData = response.data;
74
+ themeCache.set(cacheKey, themeData);
75
+ }
76
+ const lightCSS = themeToCSS(themeData, "light");
77
+ const darkCSS = themeToCSS(themeData, "dark");
78
+ const styleContent = `:root{${lightCSS}}.dark{${darkCSS}}`;
79
+ const scriptContent = getNextBlockingScriptPayload(defaultMode);
80
+ return React2.createElement(
81
+ React2.Fragment,
82
+ null,
83
+ React2.createElement("style", {
84
+ nonce,
85
+ dangerouslySetInnerHTML: { __html: styleContent }
86
+ }),
87
+ React2.createElement("script", {
88
+ nonce,
89
+ dangerouslySetInnerHTML: { __html: scriptContent }
90
+ })
91
+ );
92
+ }
93
+
94
+ // src/index.ts
95
+ import { useTheme, useThemeActions, useGenerateTheme, useDhemeClient } from "@dheme/react";
96
+ import { themeToCSS as themeToCSS2, applyThemeCSSVariables, buildCacheKey as buildCacheKey2 } from "@dheme/react";
97
+ export {
98
+ DhemeProvider,
99
+ DhemeScript,
100
+ applyThemeCSSVariables,
101
+ buildCacheKey2 as buildCacheKey,
102
+ themeToCSS2 as themeToCSS,
103
+ useDhemeClient,
104
+ useGenerateTheme,
105
+ useTheme,
106
+ useThemeActions
107
+ };
@@ -0,0 +1,21 @@
1
+ import { G as GenerateThemeStylesOptions } from './types-yW-Y2Yox.mjs';
2
+ import { ThemeMode } from '@dheme/react';
3
+ import { GenerateThemeResponse } from '@dheme/sdk';
4
+
5
+ declare function generateThemeStyles(options: GenerateThemeStylesOptions): Promise<string>;
6
+
7
+ declare function getModeFromCookie(): Promise<ThemeMode | null>;
8
+ declare function getParamsFromCookie(): Promise<string | null>;
9
+
10
+ declare class ThemeCache {
11
+ private cache;
12
+ private maxSize;
13
+ private ttl;
14
+ constructor(maxSize?: number, ttlMs?: number);
15
+ get(key: string): GenerateThemeResponse | null;
16
+ set(key: string, data: GenerateThemeResponse): void;
17
+ clear(): void;
18
+ }
19
+ declare const themeCache: ThemeCache;
20
+
21
+ export { GenerateThemeStylesOptions, generateThemeStyles, getModeFromCookie, getParamsFromCookie, themeCache };
@@ -0,0 +1,21 @@
1
+ import { G as GenerateThemeStylesOptions } from './types-yW-Y2Yox.js';
2
+ import { ThemeMode } from '@dheme/react';
3
+ import { GenerateThemeResponse } from '@dheme/sdk';
4
+
5
+ declare function generateThemeStyles(options: GenerateThemeStylesOptions): Promise<string>;
6
+
7
+ declare function getModeFromCookie(): Promise<ThemeMode | null>;
8
+ declare function getParamsFromCookie(): Promise<string | null>;
9
+
10
+ declare class ThemeCache {
11
+ private cache;
12
+ private maxSize;
13
+ private ttl;
14
+ constructor(maxSize?: number, ttlMs?: number);
15
+ get(key: string): GenerateThemeResponse | null;
16
+ set(key: string, data: GenerateThemeResponse): void;
17
+ clear(): void;
18
+ }
19
+ declare const themeCache: ThemeCache;
20
+
21
+ export { GenerateThemeStylesOptions, generateThemeStyles, getModeFromCookie, getParamsFromCookie, themeCache };
package/dist/server.js ADDED
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/server.ts
31
+ var server_exports = {};
32
+ __export(server_exports, {
33
+ generateThemeStyles: () => generateThemeStyles,
34
+ getModeFromCookie: () => getModeFromCookie,
35
+ getParamsFromCookie: () => getParamsFromCookie,
36
+ themeCache: () => themeCache
37
+ });
38
+ module.exports = __toCommonJS(server_exports);
39
+
40
+ // src/server/generateThemeStyles.ts
41
+ var import_sdk = require("@dheme/sdk");
42
+ var import_react = require("@dheme/react");
43
+
44
+ // src/server/cache.ts
45
+ var ThemeCache = class {
46
+ constructor(maxSize = 100, ttlMs = 36e5) {
47
+ this.cache = /* @__PURE__ */ new Map();
48
+ this.maxSize = maxSize;
49
+ this.ttl = ttlMs;
50
+ }
51
+ get(key) {
52
+ const entry = this.cache.get(key);
53
+ if (!entry) return null;
54
+ if (Date.now() - entry.timestamp > this.ttl) {
55
+ this.cache.delete(key);
56
+ return null;
57
+ }
58
+ return entry.data;
59
+ }
60
+ set(key, data) {
61
+ if (this.cache.size >= this.maxSize) {
62
+ const oldestKey = this.cache.keys().next().value;
63
+ if (oldestKey !== void 0) {
64
+ this.cache.delete(oldestKey);
65
+ }
66
+ }
67
+ this.cache.set(key, { data, timestamp: Date.now() });
68
+ }
69
+ clear() {
70
+ this.cache.clear();
71
+ }
72
+ };
73
+ var themeCache = new ThemeCache();
74
+
75
+ // src/server/generateThemeStyles.ts
76
+ async function generateThemeStyles(options) {
77
+ const { apiKey, theme, themeParams, mode = "light", baseUrl } = options;
78
+ const params = { theme, ...themeParams };
79
+ const cacheKey = (0, import_react.buildCacheKey)(params);
80
+ const cached = themeCache.get(cacheKey);
81
+ if (cached) {
82
+ return (0, import_react.themeToCSS)(cached, mode);
83
+ }
84
+ const client = new import_sdk.DhemeClient({ apiKey, baseUrl });
85
+ const response = await client.generateTheme(params);
86
+ const data = response.data;
87
+ themeCache.set(cacheKey, data);
88
+ return (0, import_react.themeToCSS)(data, mode);
89
+ }
90
+
91
+ // src/server/cookies.ts
92
+ var COOKIE_MODE_KEY = "dheme-mode";
93
+ var COOKIE_PARAMS_KEY = "dheme-params";
94
+ async function getModeFromCookie() {
95
+ try {
96
+ const { cookies } = await import("next/headers");
97
+ const cookieStore = await cookies();
98
+ const value = cookieStore.get(COOKIE_MODE_KEY)?.value;
99
+ if (value === "light" || value === "dark") return value;
100
+ return null;
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+ async function getParamsFromCookie() {
106
+ try {
107
+ const { cookies } = await import("next/headers");
108
+ const cookieStore = await cookies();
109
+ const value = cookieStore.get(COOKIE_PARAMS_KEY)?.value;
110
+ if (!value) return null;
111
+ return atob(value);
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ // Annotate the CommonJS export names for ESM import in node:
117
+ 0 && (module.exports = {
118
+ generateThemeStyles,
119
+ getModeFromCookie,
120
+ getParamsFromCookie,
121
+ themeCache
122
+ });
@@ -0,0 +1,53 @@
1
+ import {
2
+ themeCache
3
+ } from "./chunk-7HW3FXOF.mjs";
4
+
5
+ // src/server/generateThemeStyles.ts
6
+ import { DhemeClient } from "@dheme/sdk";
7
+ import { themeToCSS, buildCacheKey } from "@dheme/react";
8
+ async function generateThemeStyles(options) {
9
+ const { apiKey, theme, themeParams, mode = "light", baseUrl } = options;
10
+ const params = { theme, ...themeParams };
11
+ const cacheKey = buildCacheKey(params);
12
+ const cached = themeCache.get(cacheKey);
13
+ if (cached) {
14
+ return themeToCSS(cached, mode);
15
+ }
16
+ const client = new DhemeClient({ apiKey, baseUrl });
17
+ const response = await client.generateTheme(params);
18
+ const data = response.data;
19
+ themeCache.set(cacheKey, data);
20
+ return themeToCSS(data, mode);
21
+ }
22
+
23
+ // src/server/cookies.ts
24
+ var COOKIE_MODE_KEY = "dheme-mode";
25
+ var COOKIE_PARAMS_KEY = "dheme-params";
26
+ async function getModeFromCookie() {
27
+ try {
28
+ const { cookies } = await import("next/headers");
29
+ const cookieStore = await cookies();
30
+ const value = cookieStore.get(COOKIE_MODE_KEY)?.value;
31
+ if (value === "light" || value === "dark") return value;
32
+ return null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+ async function getParamsFromCookie() {
38
+ try {
39
+ const { cookies } = await import("next/headers");
40
+ const cookieStore = await cookies();
41
+ const value = cookieStore.get(COOKIE_PARAMS_KEY)?.value;
42
+ if (!value) return null;
43
+ return atob(value);
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ export {
49
+ generateThemeStyles,
50
+ getModeFromCookie,
51
+ getParamsFromCookie,
52
+ themeCache
53
+ };
@@ -0,0 +1,23 @@
1
+ import { GenerateThemeRequest } from '@dheme/sdk';
2
+ import { DhemeProviderProps as DhemeProviderProps$1, ThemeMode } from '@dheme/react';
3
+
4
+ interface DhemeProviderProps extends DhemeProviderProps$1 {
5
+ cookieSync?: boolean;
6
+ }
7
+ interface DhemeScriptProps {
8
+ apiKey: string;
9
+ theme: string;
10
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
11
+ defaultMode?: ThemeMode;
12
+ baseUrl?: string;
13
+ nonce?: string;
14
+ }
15
+ interface GenerateThemeStylesOptions {
16
+ apiKey: string;
17
+ theme: string;
18
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
19
+ mode?: ThemeMode;
20
+ baseUrl?: string;
21
+ }
22
+
23
+ export type { DhemeProviderProps as D, GenerateThemeStylesOptions as G, DhemeScriptProps as a };
@@ -0,0 +1,23 @@
1
+ import { GenerateThemeRequest } from '@dheme/sdk';
2
+ import { DhemeProviderProps as DhemeProviderProps$1, ThemeMode } from '@dheme/react';
3
+
4
+ interface DhemeProviderProps extends DhemeProviderProps$1 {
5
+ cookieSync?: boolean;
6
+ }
7
+ interface DhemeScriptProps {
8
+ apiKey: string;
9
+ theme: string;
10
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
11
+ defaultMode?: ThemeMode;
12
+ baseUrl?: string;
13
+ nonce?: string;
14
+ }
15
+ interface GenerateThemeStylesOptions {
16
+ apiKey: string;
17
+ theme: string;
18
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
19
+ mode?: ThemeMode;
20
+ baseUrl?: string;
21
+ }
22
+
23
+ export type { DhemeProviderProps as D, GenerateThemeStylesOptions as G, DhemeScriptProps as a };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@dheme/next",
3
+ "version": "1.0.0",
4
+ "description": "Next.js App Router bindings for Dheme SDK with server-side theme generation",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./server": {
15
+ "types": "./dist/server.d.ts",
16
+ "import": "./dist/server.mjs",
17
+ "require": "./dist/server.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup src/index.ts src/server.ts --format cjs,esm --dts --clean --external react --external react-dom --external next --external @dheme/sdk --external @dheme/react --tsconfig tsconfig.build.json",
27
+ "dev": "tsup src/index.ts src/server.ts --format cjs,esm --dts --watch --external react --external react-dom --external next --external @dheme/sdk --external @dheme/react",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "lint": "eslint src --ext .ts,.tsx",
31
+ "typecheck": "tsc --noEmit",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "keywords": [
35
+ "dheme",
36
+ "theme",
37
+ "nextjs",
38
+ "app-router",
39
+ "ssr",
40
+ "shadcn",
41
+ "tailwind",
42
+ "fouc",
43
+ "server-components"
44
+ ],
45
+ "author": "Dheme Team",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/dheme/sdk"
50
+ },
51
+ "homepage": "https://dheme.com",
52
+ "peerDependencies": {
53
+ "next": ">=14.0.0",
54
+ "react": ">=18.0.0",
55
+ "react-dom": ">=18.0.0"
56
+ },
57
+ "dependencies": {
58
+ "@dheme/sdk": "^1.1.0",
59
+ "@dheme/react": "^2.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@types/react": "^18.2.0",
63
+ "@types/react-dom": "^18.2.0",
64
+ "next": "^14.0.0",
65
+ "tsup": "^8.0.1",
66
+ "typescript": "^5.3.0",
67
+ "vitest": "^1.2.0",
68
+ "react": "^18.2.0",
69
+ "react-dom": "^18.2.0"
70
+ }
71
+ }