@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 +401 -0
- package/dist/chunk-7HW3FXOF.mjs +34 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +181 -0
- package/dist/index.mjs +107 -0
- package/dist/server.d.mts +21 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.js +122 -0
- package/dist/server.mjs +53 -0
- package/dist/types-yW-Y2Yox.d.mts +23 -0
- package/dist/types-yW-Y2Yox.d.ts +23 -0
- package/package.json +71 -0
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
|
+
};
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|
package/dist/server.d.ts
ADDED
|
@@ -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
|
+
});
|
package/dist/server.mjs
ADDED
|
@@ -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
|
+
}
|