@dheme/react 2.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,393 @@
1
+ # @dheme/react
2
+
3
+ React bindings for the [Dheme](https://dheme.com) Theme Generator API. Apply production-ready themes to your React app with a single provider — zero FOUC on cached visits.
4
+
5
+ Built for **React SPAs** (Vite, CRA, Remix SPA mode). For **Next.js App Router**, use [`@dheme/next`](https://www.npmjs.com/package/@dheme/next) instead.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @dheme/react @dheme/sdk
11
+ ```
12
+
13
+ ```bash
14
+ yarn add @dheme/react @dheme/sdk
15
+ ```
16
+
17
+ ```bash
18
+ pnpm add @dheme/react @dheme/sdk
19
+ ```
20
+
21
+ ### Requirements
22
+
23
+ | Dependency | Version |
24
+ | ------------ | -------- |
25
+ | `react` | >= 18 |
26
+ | `react-dom` | >= 18 |
27
+ | `@dheme/sdk` | >= 1.1.0 |
28
+
29
+ `@dheme/sdk` is included as a dependency and will be installed automatically. You only need to install it explicitly if you want to use the SDK client directly.
30
+
31
+ ## Quick Start
32
+
33
+ ```tsx
34
+ import { DhemeProvider, DhemeScript } from '@dheme/react';
35
+
36
+ function App() {
37
+ return (
38
+ <>
39
+ <DhemeScript />
40
+ <DhemeProvider apiKey="dheme_abc12345_..." theme="#3b82f6">
41
+ <YourApp />
42
+ </DhemeProvider>
43
+ </>
44
+ );
45
+ }
46
+ ```
47
+
48
+ That's it. Your app now has 19 CSS variables applied to `:root` — fully compatible with shadcn/ui and Tailwind CSS.
49
+
50
+ ## How It Works
51
+
52
+ ### First visit (no cache)
53
+
54
+ 1. React mounts, `DhemeProvider` calls the Dheme API
55
+ 2. Theme is applied as CSS variables on `:root`
56
+ 3. Theme is cached in `localStorage` for next visit
57
+
58
+ ### Subsequent visits (cached — zero FOUC)
59
+
60
+ 1. `<DhemeScript>` runs a blocking `<script>` **before React mounts**
61
+ 2. The script reads the cached theme from `localStorage` and applies CSS variables immediately
62
+ 3. React mounts, `DhemeProvider` serves the cached theme and revalidates in background
63
+
64
+ The blocking script is ~800 bytes and runs synchronously, ensuring the page never flashes without styles.
65
+
66
+ ## Components
67
+
68
+ ### `<DhemeProvider>`
69
+
70
+ The main provider. Manages theme state, API calls, caching, and CSS variable application.
71
+
72
+ ```tsx
73
+ <DhemeProvider
74
+ apiKey="dheme_abc12345_..." // Required — your Dheme API key
75
+ theme="#3b82f6" // Primary color (auto-generates on mount)
76
+ themeParams={{ // Optional generation params
77
+ radius: 0.75,
78
+ saturationAdjust: 10,
79
+ secondaryColor: '#10b981',
80
+ }}
81
+ defaultMode="light" // 'light' | 'dark' (default: 'light')
82
+ persist={true} // Cache in localStorage (default: true)
83
+ autoApply={true} // Apply CSS vars automatically (default: true)
84
+ onThemeChange={(theme) => {}} // Callback when theme changes
85
+ onModeChange={(mode) => {}} // Callback when mode changes
86
+ onError={(error) => {}} // Callback on error
87
+ >
88
+ <App />
89
+ </DhemeProvider>
90
+ ```
91
+
92
+ | Prop | Type | Default | Description |
93
+ | --------------- | ---------------------------------------- | --------- | -------------------------------------- |
94
+ | `apiKey` | `string` | - | **Required.** Your Dheme API key. |
95
+ | `theme` | `string` | - | Primary HEX color. Auto-generates on mount. |
96
+ | `themeParams` | `Omit<GenerateThemeRequest, 'theme'>` | - | Additional generation parameters. |
97
+ | `defaultMode` | `'light' \| 'dark'` | `'light'` | Initial color mode. |
98
+ | `baseUrl` | `string` | - | Override API base URL. |
99
+ | `persist` | `boolean` | `true` | Cache theme in localStorage. |
100
+ | `autoApply` | `boolean` | `true` | Apply CSS variables to `:root`. |
101
+ | `onThemeChange` | `(theme: GenerateThemeResponse) => void` | - | Called when theme data changes. |
102
+ | `onModeChange` | `(mode: ThemeMode) => void` | - | Called when mode changes. |
103
+ | `onError` | `(error: Error) => void` | - | Called on API errors. |
104
+
105
+ ### `<DhemeScript>`
106
+
107
+ Blocking script that prevents FOUC by applying cached theme CSS variables before React hydrates.
108
+
109
+ ```tsx
110
+ <DhemeScript
111
+ defaultMode="light" // Fallback mode (default: 'light')
112
+ nonce="abc123" // CSP nonce (optional)
113
+ />
114
+ ```
115
+
116
+ Place it **before** `<DhemeProvider>` in your component tree, as high as possible.
117
+
118
+ | Prop | Type | Default | Description |
119
+ | ------------- | -------------------- | --------- | ------------------------- |
120
+ | `defaultMode` | `'light' \| 'dark'` | `'light'` | Fallback if no cache. |
121
+ | `nonce` | `string` | - | CSP nonce for the script. |
122
+
123
+ ## Hooks
124
+
125
+ ### `useTheme()`
126
+
127
+ Read theme data. Only re-renders when theme data or mode changes — **not** when loading state changes.
128
+
129
+ ```tsx
130
+ import { useTheme } from '@dheme/react';
131
+
132
+ function MyComponent() {
133
+ const { theme, mode, isReady } = useTheme();
134
+
135
+ if (!isReady) return <Skeleton />;
136
+
137
+ return (
138
+ <p>Primary: {theme.colors[mode].primary.h}°</p>
139
+ );
140
+ }
141
+ ```
142
+
143
+ | Return | Type | Description |
144
+ | --------- | --------------------------- | ------------------------------------- |
145
+ | `theme` | `GenerateThemeResponse \| null` | The full theme data. |
146
+ | `mode` | `'light' \| 'dark'` | Current color mode. |
147
+ | `isReady` | `boolean` | `true` once theme is loaded. |
148
+
149
+ ### `useThemeActions()`
150
+
151
+ Access actions and loading state. Components using this hook re-render on action state changes — components using only `useTheme()` do not.
152
+
153
+ ```tsx
154
+ import { useThemeActions } from '@dheme/react';
155
+
156
+ function ThemeToggle() {
157
+ const { setMode, isLoading } = useThemeActions();
158
+
159
+ return (
160
+ <button
161
+ disabled={isLoading}
162
+ onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}
163
+ >
164
+ Toggle
165
+ </button>
166
+ );
167
+ }
168
+ ```
169
+
170
+ | Return | Type | Description |
171
+ | --------------- | ------------------------------------------------ | ------------------------------- |
172
+ | `generateTheme` | `(params: GenerateThemeRequest) => Promise<void>` | Generate a new theme. |
173
+ | `setMode` | `(mode: ThemeMode) => void` | Switch light/dark mode. |
174
+ | `clearTheme` | `() => void` | Clear theme and cache. |
175
+ | `isLoading` | `boolean` | `true` during API call. |
176
+ | `error` | `Error \| null` | Last error, if any. |
177
+ | `client` | `DhemeClient` | Raw SDK client instance. |
178
+
179
+ ### `useGenerateTheme()`
180
+
181
+ Convenience hook with local loading state — useful when multiple components trigger generation independently.
182
+
183
+ ```tsx
184
+ import { useGenerateTheme } from '@dheme/react';
185
+
186
+ function ColorPicker() {
187
+ const { generateTheme, isGenerating, error } = useGenerateTheme();
188
+
189
+ return (
190
+ <button
191
+ disabled={isGenerating}
192
+ onClick={() => generateTheme({ theme: '#ef4444' })}
193
+ >
194
+ {isGenerating ? 'Generating...' : 'Apply Red'}
195
+ </button>
196
+ );
197
+ }
198
+ ```
199
+
200
+ ### `useDhemeClient()`
201
+
202
+ Direct access to the `DhemeClient` instance for advanced operations (e.g., `getUsage()`).
203
+
204
+ ```tsx
205
+ import { useDhemeClient } from '@dheme/react';
206
+
207
+ function UsageInfo() {
208
+ const client = useDhemeClient();
209
+ const [usage, setUsage] = useState(null);
210
+
211
+ useEffect(() => {
212
+ client.getUsage().then(({ data }) => setUsage(data));
213
+ }, [client]);
214
+
215
+ return usage ? <p>{usage.remaining} requests left</p> : null;
216
+ }
217
+ ```
218
+
219
+ ## Context Splitting (Performance)
220
+
221
+ The provider uses **two separate React contexts** to minimize re-renders:
222
+
223
+ | Context | Contains | Changes when |
224
+ | --------------------- | ------------------------------------ | ------------------------------ |
225
+ | `ThemeDataContext` | `theme`, `mode`, `isReady` | Theme data or mode changes |
226
+ | `ThemeActionsContext` | `generateTheme`, `setMode`, `isLoading`, `error` | Actions are triggered |
227
+
228
+ Components using `useTheme()` (data) **do not re-render** when `isLoading` changes.
229
+ Components using `useThemeActions()` (actions) **do not re-render** when theme data changes.
230
+
231
+ This prevents cascading re-renders in large component trees.
232
+
233
+ ## Caching
234
+
235
+ Themes are cached in `localStorage` with deterministic keys based on input parameters:
236
+
237
+ ```
238
+ same input params → same cache key → same theme
239
+ ```
240
+
241
+ The cache key is derived from: `theme`, `secondaryColor`, `radius`, `saturationAdjust`, `lightnessAdjust`, `contrastAdjust`, `cardIsColored`, `backgroundIsColored`.
242
+
243
+ ### Stale-while-revalidate
244
+
245
+ On cached visits, the provider:
246
+ 1. Serves the cached theme **immediately** (zero latency)
247
+ 2. Fires a background API request to check for updates
248
+ 3. Only updates the UI if the response differs from the cache
249
+
250
+ This ensures instant page loads while keeping themes fresh.
251
+
252
+ ## Mode Switching
253
+
254
+ Switching between light and dark mode **does not make an API call**. Both `colors.light` and `colors.dark` are included in a single API response, so mode switching is instant.
255
+
256
+ ```tsx
257
+ const { setMode } = useThemeActions();
258
+
259
+ // Instant — no network request
260
+ setMode('dark');
261
+ ```
262
+
263
+ The provider also syncs the `dark` class on `<html>` automatically.
264
+
265
+ ## CSS Variables
266
+
267
+ The provider sets 19 CSS variables + `--radius` on `:root`:
268
+
269
+ ```css
270
+ :root {
271
+ --background: 0 0% 100%;
272
+ --foreground: 222.2 84% 4.9%;
273
+ --primary: 221.2 83.2% 53.3%;
274
+ --primary-foreground: 210 40% 98%;
275
+ /* ... 15 more tokens */
276
+ --radius: 0.5rem;
277
+ }
278
+ ```
279
+
280
+ Values are in shadcn/ui format (`h s% l%`), directly compatible with Tailwind CSS `hsl()` usage.
281
+
282
+ ## Utilities
283
+
284
+ ### `themeToCSS(theme, mode)`
285
+
286
+ Convert a `GenerateThemeResponse` to a CSS variable assignment string.
287
+
288
+ ```typescript
289
+ import { themeToCSS } from '@dheme/react';
290
+
291
+ const css = themeToCSS(theme, 'light');
292
+ // "--background:0 0% 100%;--foreground:222.2 84% 4.9%;..."
293
+ ```
294
+
295
+ ### `applyThemeCSSVariables(theme, mode)`
296
+
297
+ Manually apply CSS variables to `:root`.
298
+
299
+ ```typescript
300
+ import { applyThemeCSSVariables } from '@dheme/react';
301
+
302
+ applyThemeCSSVariables(theme, 'dark');
303
+ ```
304
+
305
+ ### `removeThemeCSSVariables()`
306
+
307
+ Remove all Dheme CSS variables from `:root`.
308
+
309
+ ### `buildCacheKey(params)`
310
+
311
+ Generate the deterministic cache key for a set of params.
312
+
313
+ ```typescript
314
+ import { buildCacheKey } from '@dheme/react';
315
+
316
+ const key = buildCacheKey({ theme: '#3b82f6', radius: 0.75 });
317
+ ```
318
+
319
+ ## Full Example (Vite)
320
+
321
+ ```tsx
322
+ // main.tsx
323
+ import React from 'react';
324
+ import ReactDOM from 'react-dom/client';
325
+ import { DhemeProvider, DhemeScript } from '@dheme/react';
326
+ import App from './App';
327
+
328
+ ReactDOM.createRoot(document.getElementById('root')!).render(
329
+ <React.StrictMode>
330
+ <DhemeScript />
331
+ <DhemeProvider
332
+ apiKey={import.meta.env.VITE_DHEME_API_KEY}
333
+ theme="#3b82f6"
334
+ themeParams={{ radius: 0.5 }}
335
+ >
336
+ <App />
337
+ </DhemeProvider>
338
+ </React.StrictMode>,
339
+ );
340
+ ```
341
+
342
+ ```tsx
343
+ // App.tsx
344
+ import { useTheme, useThemeActions } from '@dheme/react';
345
+
346
+ export default function App() {
347
+ const { theme, mode, isReady } = useTheme();
348
+ const { setMode } = useThemeActions();
349
+
350
+ if (!isReady) return <div>Loading theme...</div>;
351
+
352
+ return (
353
+ <div>
354
+ <h1 style={{ color: `hsl(${theme.colors[mode].primary.h} ${theme.colors[mode].primary.s}% ${theme.colors[mode].primary.l}%)` }}>
355
+ Dheme Theme
356
+ </h1>
357
+ <button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
358
+ {mode === 'light' ? 'Dark' : 'Light'} Mode
359
+ </button>
360
+ </div>
361
+ );
362
+ }
363
+ ```
364
+
365
+ ## TypeScript
366
+
367
+ All types are exported:
368
+
369
+ ```typescript
370
+ import type {
371
+ ThemeMode,
372
+ ThemeDataState,
373
+ ThemeActionsState,
374
+ DhemeProviderProps,
375
+ DhemeScriptProps,
376
+ GenerateThemeRequest,
377
+ GenerateThemeResponse,
378
+ ColorTokens,
379
+ HSLColor,
380
+ } from '@dheme/react';
381
+ ```
382
+
383
+ ## Related Packages
384
+
385
+ | Package | Description | When to use |
386
+ | -------------- | ------------------------------- | ------------------------------ |
387
+ | `@dheme/sdk` | Core TypeScript SDK | Direct API access, Node.js |
388
+ | `@dheme/react` | React bindings (this package) | Vite, CRA, React SPAs |
389
+ | `@dheme/next` | Next.js App Router bindings | Next.js 14+ with SSR |
390
+
391
+ ## License
392
+
393
+ MIT
@@ -0,0 +1,68 @@
1
+ import * as React$1 from 'react';
2
+ import React__default from 'react';
3
+ import { GenerateThemeRequest, GenerateThemeResponse, DhemeClient } from '@dheme/sdk';
4
+ export { ColorTokens, GenerateThemeRequest, GenerateThemeResponse, HSLColor } from '@dheme/sdk';
5
+
6
+ type ThemeMode = 'light' | 'dark';
7
+ interface ThemeDataState {
8
+ theme: GenerateThemeResponse | null;
9
+ mode: ThemeMode;
10
+ isReady: boolean;
11
+ }
12
+ interface ThemeActionsState {
13
+ generateTheme: (params: GenerateThemeRequest) => Promise<void>;
14
+ setMode: (mode: ThemeMode) => void;
15
+ clearTheme: () => void;
16
+ isLoading: boolean;
17
+ error: Error | null;
18
+ client: DhemeClient;
19
+ }
20
+ interface DhemeProviderProps {
21
+ apiKey: string;
22
+ theme?: string;
23
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
24
+ defaultMode?: ThemeMode;
25
+ baseUrl?: string;
26
+ persist?: boolean;
27
+ autoApply?: boolean;
28
+ onThemeChange?: (theme: GenerateThemeResponse) => void;
29
+ onModeChange?: (mode: ThemeMode) => void;
30
+ onError?: (error: Error) => void;
31
+ children: React.ReactNode;
32
+ }
33
+ interface DhemeScriptProps {
34
+ defaultMode?: ThemeMode;
35
+ nonce?: string;
36
+ }
37
+
38
+ declare function DhemeProvider({ apiKey, theme: primaryColor, themeParams, defaultMode, baseUrl, persist, autoApply, onThemeChange, onModeChange, onError, children, }: DhemeProviderProps): React__default.ReactElement;
39
+
40
+ declare function DhemeScript({ defaultMode, nonce, }: DhemeScriptProps): React__default.ReactElement;
41
+
42
+ declare function useTheme(): ThemeDataState;
43
+
44
+ declare function useThemeActions(): ThemeActionsState;
45
+
46
+ declare function useGenerateTheme(): {
47
+ generateTheme: (params: GenerateThemeRequest) => Promise<void>;
48
+ isGenerating: boolean;
49
+ error: Error | null;
50
+ };
51
+
52
+ declare function useDhemeClient(): DhemeClient;
53
+
54
+ declare function themeToCSS(theme: GenerateThemeResponse, mode: ThemeMode): string;
55
+ declare function themeToCSSBothModes(theme: GenerateThemeResponse): string;
56
+ declare function applyThemeCSSVariables(theme: GenerateThemeResponse, mode: ThemeMode): void;
57
+ declare function removeThemeCSSVariables(): void;
58
+
59
+ declare function buildCacheKey(params: GenerateThemeRequest): string;
60
+
61
+ declare function getBlockingScriptPayload(defaultMode: ThemeMode): string;
62
+ declare function getNextBlockingScriptPayload(defaultMode: ThemeMode): string;
63
+
64
+ declare const ThemeDataContext: React$1.Context<ThemeDataState | null>;
65
+
66
+ declare const ThemeActionsContext: React$1.Context<ThemeActionsState | null>;
67
+
68
+ export { DhemeProvider, type DhemeProviderProps, DhemeScript, type DhemeScriptProps, ThemeActionsContext, type ThemeActionsState, ThemeDataContext, type ThemeDataState, type ThemeMode, applyThemeCSSVariables, buildCacheKey, getBlockingScriptPayload, getNextBlockingScriptPayload, removeThemeCSSVariables, themeToCSS, themeToCSSBothModes, useDhemeClient, useGenerateTheme, useTheme, useThemeActions };
@@ -0,0 +1,68 @@
1
+ import * as React$1 from 'react';
2
+ import React__default from 'react';
3
+ import { GenerateThemeRequest, GenerateThemeResponse, DhemeClient } from '@dheme/sdk';
4
+ export { ColorTokens, GenerateThemeRequest, GenerateThemeResponse, HSLColor } from '@dheme/sdk';
5
+
6
+ type ThemeMode = 'light' | 'dark';
7
+ interface ThemeDataState {
8
+ theme: GenerateThemeResponse | null;
9
+ mode: ThemeMode;
10
+ isReady: boolean;
11
+ }
12
+ interface ThemeActionsState {
13
+ generateTheme: (params: GenerateThemeRequest) => Promise<void>;
14
+ setMode: (mode: ThemeMode) => void;
15
+ clearTheme: () => void;
16
+ isLoading: boolean;
17
+ error: Error | null;
18
+ client: DhemeClient;
19
+ }
20
+ interface DhemeProviderProps {
21
+ apiKey: string;
22
+ theme?: string;
23
+ themeParams?: Omit<GenerateThemeRequest, 'theme'>;
24
+ defaultMode?: ThemeMode;
25
+ baseUrl?: string;
26
+ persist?: boolean;
27
+ autoApply?: boolean;
28
+ onThemeChange?: (theme: GenerateThemeResponse) => void;
29
+ onModeChange?: (mode: ThemeMode) => void;
30
+ onError?: (error: Error) => void;
31
+ children: React.ReactNode;
32
+ }
33
+ interface DhemeScriptProps {
34
+ defaultMode?: ThemeMode;
35
+ nonce?: string;
36
+ }
37
+
38
+ declare function DhemeProvider({ apiKey, theme: primaryColor, themeParams, defaultMode, baseUrl, persist, autoApply, onThemeChange, onModeChange, onError, children, }: DhemeProviderProps): React__default.ReactElement;
39
+
40
+ declare function DhemeScript({ defaultMode, nonce, }: DhemeScriptProps): React__default.ReactElement;
41
+
42
+ declare function useTheme(): ThemeDataState;
43
+
44
+ declare function useThemeActions(): ThemeActionsState;
45
+
46
+ declare function useGenerateTheme(): {
47
+ generateTheme: (params: GenerateThemeRequest) => Promise<void>;
48
+ isGenerating: boolean;
49
+ error: Error | null;
50
+ };
51
+
52
+ declare function useDhemeClient(): DhemeClient;
53
+
54
+ declare function themeToCSS(theme: GenerateThemeResponse, mode: ThemeMode): string;
55
+ declare function themeToCSSBothModes(theme: GenerateThemeResponse): string;
56
+ declare function applyThemeCSSVariables(theme: GenerateThemeResponse, mode: ThemeMode): void;
57
+ declare function removeThemeCSSVariables(): void;
58
+
59
+ declare function buildCacheKey(params: GenerateThemeRequest): string;
60
+
61
+ declare function getBlockingScriptPayload(defaultMode: ThemeMode): string;
62
+ declare function getNextBlockingScriptPayload(defaultMode: ThemeMode): string;
63
+
64
+ declare const ThemeDataContext: React$1.Context<ThemeDataState | null>;
65
+
66
+ declare const ThemeActionsContext: React$1.Context<ThemeActionsState | null>;
67
+
68
+ export { DhemeProvider, type DhemeProviderProps, DhemeScript, type DhemeScriptProps, ThemeActionsContext, type ThemeActionsState, ThemeDataContext, type ThemeDataState, type ThemeMode, applyThemeCSSVariables, buildCacheKey, getBlockingScriptPayload, getNextBlockingScriptPayload, removeThemeCSSVariables, themeToCSS, themeToCSSBothModes, useDhemeClient, useGenerateTheme, useTheme, useThemeActions };