@fakhrirafiki/theme-engine 0.2.5 β†’ 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,582 +1,242 @@
1
- # 🎨 Theme Engine
1
+ # Theme Engine
2
2
 
3
- Elegant theming system with smooth transitions and custom color presets for modern React applications.
3
+ Theme system for React: **mode** (`light | dark | system`) + **color presets** (CSS variables) with optional View Transition ripple.
4
4
 
5
- ## ✨ Features
6
-
7
- - πŸŒ“ **Unified Theming** - Seamlessly coordinate dark/light mode with custom color presets
8
- - 🎨 **Beautiful Presets** - 9 curated color themes with smooth animated selection
9
- - ✨ **Smooth Transitions** - Ripple effects using View Transition API
10
- - πŸ’Ύ **Persistent** - localStorage integration with SSR-safe restoration
11
- - β™Ώ **Accessible** - Full ARIA support and reduced motion compatibility
12
- - πŸš€ **Zero Config** - Works out of the box with sensible defaults
13
- - πŸ“¦ **Lightweight** - Optimized bundle size and direct source consumption
14
-
15
- ## πŸš€ Quick Start
5
+ ## Install
16
6
 
17
7
  ```bash
18
- # Install from npm
19
- npm install theme-engine
20
-
21
- # Or with yarn/pnpm
22
- yarn add theme-engine
23
- pnpm add theme-engine
24
- ```
25
-
26
- ```tsx
27
- import { ThemeProvider, ThemeScript, ThemeToggle } from 'theme-engine'
28
- import 'theme-engine/styles'
29
-
30
- function App() {
31
- return (
32
- <html lang="en" suppressHydrationWarning>
33
- <head>
34
- <ThemeScript
35
- presetStorageKey="my-app-theme-preset"
36
- defaultPreset="twitter"
37
- />
38
- </head>
39
- <body>
40
- <ThemeProvider
41
- defaultMode="system"
42
- defaultPreset="twitter"
43
- modeStorageKey="my-app-mode"
44
- presetStorageKey="my-app-theme-preset"
45
- enablePresets={true}
46
- >
47
- <header>
48
- <ThemeToggle />
49
- </header>
50
- <main>
51
- Your app content
52
- </main>
53
- </ThemeProvider>
54
- </body>
55
- </html>
56
- )
57
- }
8
+ pnpm add @fakhrirafiki/theme-engine
58
9
  ```
59
10
 
60
- ## 🎨 Theme Presets
11
+ ## Setup (Next.js App Router)
61
12
 
62
- Add beautiful color presets with smooth animations:
13
+ ### 1) Import CSS once
63
14
 
64
- ```tsx
65
- import { ThemePresetButtons } from 'theme-engine'
15
+ In `app/globals.css`:
66
16
 
67
- function ThemeSelector() {
68
- return (
69
- <ThemePresetButtons
70
- animation={{
71
- enabled: true,
72
- duration: 4,
73
- rowCount: 2,
74
- scrollSpeed: 1,
75
- }}
76
- layout={{
77
- buttonWidth: 140,
78
- buttonGap: 12,
79
- showColorBoxes: true,
80
- colorBoxCount: 3,
81
- }}
82
- />
83
- )
84
- }
17
+ ```css
18
+ @import "@fakhrirafiki/theme-engine/styles";
85
19
  ```
86
20
 
87
- ## πŸ“¦ Core Components
21
+ Notes:
88
22
 
89
- ### ThemeProvider
23
+ - If you use Tailwind v4, you will typically also want:
90
24
 
91
- The heart of the theming system - coordinates dark/light mode with custom presets elegantly.
25
+ ```css
26
+ @import "tailwindcss";
27
+ @import "@fakhrirafiki/theme-engine/styles";
92
28
 
93
- ```tsx
94
- <ThemeProvider
95
- defaultMode="system" // "light" | "dark" | "system"
96
- defaultPreset="twitter" // Default preset ID to apply
97
- modeStorageKey="app-mode" // localStorage key for theme mode
98
- presetStorageKey="app-preset" // localStorage key for color preset
99
- enablePresets={true} // Enable preset functionality
100
- >
101
- {children}
102
- </ThemeProvider>
29
+ @custom-variant dark (&:is(.dark *));
103
30
  ```
104
31
 
105
- ### Custom Presets
106
-
107
- Add your own theme presets alongside the built-in TweakCN collection:
108
-
109
- ```tsx
110
- import { ThemeProvider, type TweakCNThemePreset } from 'theme-engine'
111
-
112
- const customPresets: Record<string, TweakCNThemePreset> = {
113
- "my-brand": {
114
- label: "My Brand Theme",
115
- styles: {
116
- light: {
117
- background: "#ffffff",
118
- foreground: "#1a1a1a",
119
- primary: "#0066cc",
120
- "primary-foreground": "#ffffff",
121
- secondary: "#f0f8ff",
122
- "secondary-foreground": "#004499",
123
- // ... all 36+ CSS properties
124
- border: "#e2e8f0",
125
- input: "#f1f5f9",
126
- ring: "#0066cc",
127
- radius: "0.5rem",
128
- },
129
- dark: {
130
- background: "#0a0a0a",
131
- foreground: "#fafafa",
132
- primary: "#3399ff",
133
- "primary-foreground": "#000000",
134
- // ... all 36+ CSS properties
135
- }
136
- }
137
- },
138
- "retro-gaming": {
139
- label: "Retro Gaming",
140
- styles: {
141
- light: {
142
- background: "#f5f5dc",
143
- foreground: "#2a1810",
144
- primary: "#ff6b35",
145
- "primary-foreground": "#ffffff",
146
- accent: "#06ffa5",
147
- "accent-foreground": "#2a1810",
148
- // ... complete theme definition
149
- },
150
- dark: {
151
- background: "#1a1a2e",
152
- foreground: "#ffd23f",
153
- primary: "#ff6b35",
154
- "primary-foreground": "#000000",
155
- // ... complete theme definition
156
- }
157
- }
158
- }
159
- }
32
+ - `@fakhrirafiki/theme-engine/styles` includes Tailwind v4 directives (`@theme inline`). If you are not using Tailwind v4, import only the non-tailwind CSS modules instead:
160
33
 
161
- function App() {
162
- return (
163
- <ThemeProvider
164
- customPresets={customPresets}
165
- defaultPreset="my-brand"
166
- >
167
- {/* Custom presets automatically appear in ThemePresetButtons */}
168
- <ThemePresetButtons
169
- showBuiltIn={true}
170
- showCustom={true}
171
- groupBy="provider"
172
- labels={{
173
- builtIn: "Built-in Themes",
174
- custom: "🎨 Brand Themes"
175
- }}
176
- />
177
- </ThemeProvider>
178
- )
179
- }
34
+ ```css
35
+ @import "@fakhrirafiki/theme-engine/styles/base.css";
36
+ @import "@fakhrirafiki/theme-engine/styles/animations.css";
37
+ @import "@fakhrirafiki/theme-engine/styles/components.css";
38
+ @import "@fakhrirafiki/theme-engine/styles/utilities.css";
180
39
  ```
181
40
 
182
- ### Preset Validation
41
+ ### 2) Wrap with `ThemeProvider`
183
42
 
184
- Validate your custom presets before using them to ensure they work correctly:
43
+ In `app/layout.tsx`:
185
44
 
186
45
  ```tsx
187
- import { validateCustomPresets, logValidationResult } from 'theme-engine'
188
-
189
- const customPresets = {
190
- "my-theme": {
191
- label: "My Theme",
192
- styles: {
193
- light: { /* theme properties */ },
194
- dark: { /* theme properties */ }
195
- }
196
- }
197
- }
46
+ import { ThemeProvider } from "@fakhrirafiki/theme-engine";
47
+ import "./globals.css";
198
48
 
199
- // Validate presets
200
- const validationResult = validateCustomPresets(customPresets);
201
-
202
- if (validationResult.isValid) {
203
- console.log('βœ… All presets are valid!');
204
- } else {
205
- console.error('❌ Invalid presets:', validationResult.errors);
49
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
50
+ return (
51
+ <html lang="en" suppressHydrationWarning>
52
+ <body>
53
+ <ThemeProvider defaultMode="system" defaultPreset="modern-minimal">
54
+ {children}
55
+ </ThemeProvider>
56
+ </body>
57
+ </html>
58
+ );
206
59
  }
207
-
208
- // Log detailed validation results
209
- logValidationResult(validationResult, 'My Custom Presets');
210
60
  ```
211
61
 
212
- **Required Properties** (minimum for a valid preset):
213
- - `background`, `foreground`
214
- - `primary`, `primary-foreground`
215
- - `secondary`, `secondary-foreground`
216
- - `card`, `card-foreground`
217
-
218
- **Recommended Properties** for complete themes:
219
- - `border`, `input`, `ring`
220
- - `muted`, `muted-foreground`
221
- - `accent`, `accent-foreground`
222
- - `destructive`, `destructive-foreground`
223
- - All 36+ CSS properties for full compatibility
62
+ Notes:
224
63
 
225
- ### ThemeScript
64
+ - By default, `ThemeProvider` injects `ThemeScript` for preset restoration (to reduce flashes).
65
+ - `ThemeScript` restores **preset colors** only (it does not set the `dark`/`light` class).
66
+ - `defaultPreset="..."` pre-hydration only works for **built-in presets** (because `ThemeScript` uses `getPresetById()` internally). Custom `defaultPreset` still works after hydration.
226
67
 
227
- Pre-hydration script for seamless SSR theme restoration. Place in `<head>`:
228
-
229
- ```tsx
230
- <ThemeScript
231
- presetStorageKey="app-preset"
232
- defaultPreset="twitter" // Apply default preset if none stored
233
- />
234
- ```
68
+ ## Day-to-day usage
235
69
 
236
- ### ThemeToggle
70
+ ### Toggle dark/light mode
237
71
 
238
- Beautiful dark/light mode toggle with ripple animation:
72
+ Use the ready-made button:
239
73
 
240
74
  ```tsx
241
- <ThemeToggle
242
- size="sm" // "sm" | "md" | "lg"
243
- variant="ghost" // "default" | "outline" | "ghost"
244
- className="custom-class"
245
- />
246
- ```
75
+ "use client";
247
76
 
248
- ### ThemePresetButtons
77
+ import { ThemeToggle } from "@fakhrirafiki/theme-engine";
249
78
 
250
- Zero-config animated preset selector with beautiful color previews. State management is handled automatically via ThemeProvider context:
251
-
252
- ```tsx
253
- {/* Basic usage - shows all presets */}
254
- <ThemePresetButtons />
255
-
256
- {/* Advanced usage with categorization */}
257
- <ThemePresetButtons
258
- categories={["nature", "vibrant"]} // Filter presets by category
259
- maxPresets={20} // Limit number of presets shown
260
- showBuiltIn={true} // Show built-in presets (default: true)
261
- showCustom={true} // Show custom presets (default: true)
262
- groupBy="provider" // Group by: 'none' | 'category' | 'provider'
263
- labels={{
264
- builtIn: "Built-in Themes",
265
- custom: "🎨 Your Themes"
266
- }}
267
- showSectionHeaders={true} // Show section headers when grouping
268
- animation={{
269
- enabled: true,
270
- duration: 4, // Animation duration in seconds
271
- rowCount: 2, // Number of animation rows
272
- scrollSpeed: 1, // Scroll speed multiplier
273
- }}
274
- layout={{
275
- buttonWidth: 140,
276
- buttonGap: 12,
277
- rowGap: 12,
278
- showColorBoxes: true,
279
- colorBoxCount: 3,
280
- }}
281
- />
282
- ```
283
-
284
- ## 🎣 Hooks
285
-
286
- ### useTheme
287
-
288
- Access both theme mode and preset state:
289
-
290
- ```tsx
291
- function ThemeStatus() {
292
- const {
293
- mode, // Current theme setting
294
- setMode, // Change theme mode
295
- currentPreset, // Current color preset
296
- applyPreset, // Apply new preset
297
- clearPreset // Clear current preset
298
- } = useTheme()
299
-
300
- return (
301
- <div>
302
- <p>Mode: {mode}</p>
303
- <p>Preset: {currentPreset?.presetName || 'Default'}</p>
304
- </div>
305
- )
79
+ export function HeaderThemeToggle() {
80
+ return <ThemeToggle size="md" variant="ghost" />;
306
81
  }
307
82
  ```
308
83
 
309
- ## 🎨 TweakCN Preset Collection
310
-
311
- Theme Engine includes a complete collection of TweakCN-compatible presets with automatic built-in integration:
84
+ Or control it yourself:
312
85
 
313
86
  ```tsx
314
- import { tweakcnPresets, getPresetById, getPresetIds } from 'theme-engine'
315
-
316
- // Get all preset IDs
317
- const allPresetIds = getPresetIds()
318
-
319
- // Get specific preset
320
- const twitterPreset = getPresetById('twitter')
321
-
322
- // Access raw TweakCN data
323
- const rawPresets = tweakcnPresets
324
-
325
- // Available presets: "twitter", "modern-minimal", "ocean-blue", etc.
326
- ```
87
+ "use client";
327
88
 
328
- ## πŸ”§ Customization
89
+ import { useTheme } from "@fakhrirafiki/theme-engine";
329
90
 
330
- ### Zero-Config TweakCN Integration
331
-
332
- ThemePresetButtons automatically uses the built-in TweakCN preset collection - no configuration needed:
333
-
334
- ```tsx
335
- import { ThemePresetButtons } from 'theme-engine'
336
-
337
- function App() {
91
+ export function ModeButtons() {
92
+ const { mode, setMode, toggleMode } = useTheme();
338
93
  return (
339
94
  <div>
340
- {/* Zero config - automatically uses TweakCN presets */}
341
- <ThemePresetButtons />
342
-
343
- {/* Filter specific presets if needed */}
344
- <ThemePresetButtons
345
- categories={["blue", "nature"]}
346
- maxPresets={10}
347
- />
95
+ <button onClick={() => setMode("system")}>System</button>
96
+ <button onClick={() => setMode("light")}>Light</button>
97
+ <button onClick={() => setMode("dark")}>Dark</button>
98
+ <button onClick={() => toggleMode()}>Toggle</button>
99
+ <div>Current: {mode}</div>
348
100
  </div>
349
- )
101
+ );
350
102
  }
351
103
  ```
352
104
 
353
- ### Custom Preset Integration
354
-
355
- Add your own presets to the TweakCN collection:
105
+ ### Pick a preset (by ID)
356
106
 
357
107
  ```tsx
358
- import { ThemePreset } from 'theme-engine'
359
-
360
- const myCustomPreset: ThemePreset = {
361
- id: "my-brand",
362
- name: "Brand Theme",
363
- colors: {
364
- light: {
365
- primary: "#0066cc",
366
- secondary: "#f0f0f0",
367
- accent: "#ff6600",
368
- background: "#ffffff",
369
- foreground: "#1a1a1a",
370
- // ... all 36+ CSS properties
371
- },
372
- dark: {
373
- primary: "#3399ff",
374
- secondary: "#2a2a2a",
375
- accent: "#ff8833",
376
- background: "#1a1a1a",
377
- foreground: "#ffffff",
378
- // ... all 36+ CSS properties
379
- }
380
- }
381
- }
108
+ "use client";
382
109
 
383
- // Apply programmatically via useTheme hook
384
- function CustomControls() {
385
- const { applyPreset } = useTheme()
386
-
387
- return (
388
- <button onClick={() => applyPreset(myCustomPreset)}>
389
- Apply Brand Theme
390
- </button>
391
- )
392
- }
110
+ import { useTypedTheme } from "@fakhrirafiki/theme-engine";
111
+ import { customPresets } from "./custom-theme-presets";
393
112
 
394
- // Or apply a preset by ID (from built-in or custom presets)
395
- function QuickPresetSwitcher() {
396
- const { setThemePresetById } = useTheme()
113
+ export function PresetButtons() {
114
+ const { setThemePresetById, clearPreset, currentPreset } = useTypedTheme(customPresets);
397
115
 
398
116
  return (
399
117
  <div>
400
- <button onClick={() => setThemePresetById('modern-minimal')}>
401
- Modern Minimal
402
- </button>
403
- <button onClick={() => setThemePresetById('tiket-ngobrol')}>
404
- Ngobrol Blue
405
- </button>
118
+ <button onClick={() => setThemePresetById("my-brand")}>My Brand</button>
119
+ <button onClick={() => setThemePresetById("modern-minimal")}>Modern Minimal</button>
120
+ <button onClick={() => clearPreset()}>Reset</button>
121
+ <div>Active: {currentPreset?.presetName ?? "Default"}</div>
406
122
  </div>
407
- )
123
+ );
408
124
  }
409
125
  ```
410
126
 
411
- ## πŸ”„ Migration Guide
412
-
413
- ### Current API Usage
127
+ ### Use the animated preset picker
414
128
 
415
- Use the clean theming system for all projects:
416
129
  ```tsx
417
- import { ThemeProvider, useTheme, ThemeScript } from 'theme-engine'
130
+ "use client";
418
131
 
419
- function App() {
420
- return (
421
- <html>
422
- <head>
423
- <ThemeScript presetStorageKey="app-preset" />
424
- </head>
425
- <body>
426
- <ThemeProvider
427
- defaultMode="system"
428
- defaultPreset="twitter"
429
- enablePresets={true}
430
- >
431
- {children}
432
- </ThemeProvider>
433
- </body>
434
- </html>
435
- )
436
- }
132
+ import { ThemePresetButtons } from "@fakhrirafiki/theme-engine";
437
133
 
438
- function MyComponent() {
439
- const { applyPreset } = useTheme()
440
- // Clean and simple!
134
+ export function PresetPicker() {
135
+ return <ThemePresetButtons />;
441
136
  }
442
137
  ```
443
138
 
444
- ### Benefits of Clean API
139
+ ## Presets
445
140
 
446
- - βœ… **Zero configuration** - Works out of the box with sensible defaults
447
- - βœ… **Perfect coordination** - Dark/light mode works seamlessly with presets
448
- - βœ… **Optimal performance** - Eliminates theme conflicts and infinite loops
449
- - βœ… **Reliable persistence** - Separate localStorage keys prevent conflicts
141
+ ### Built-in presets
450
142
 
451
- ## 🎨 CSS Utilities & Modular Styles
143
+ The package ships with a built-in preset collection:
452
144
 
453
- Theme engine provides modular CSS imports for advanced usage:
145
+ ```tsx
146
+ import { getPresetIds, getPresetById } from "@fakhrirafiki/theme-engine";
454
147
 
455
- ```css
456
- /* Complete theme system (recommended) */
457
- @import "theme-engine/styles";
458
-
459
- /* Or import specific modules */
460
- @import "theme-engine/styles/base.css"; /* CSS variables & defaults */
461
- @import "theme-engine/styles/animations.css"; /* View transition effects */
462
- @import "theme-engine/styles/components.css"; /* Component styles */
463
- @import "theme-engine/styles/utilities.css"; /* Dynamic utilities */
464
- @import "theme-engine/styles/tailwind.css"; /* Complete Tailwind integration */
148
+ const ids = getPresetIds();
149
+ const modernMinimal = getPresetById("modern-minimal");
465
150
  ```
466
151
 
467
- ### Complete CSS Property System
468
-
469
- Theme engine manages 36+ CSS custom properties across categories:
470
-
471
- **🎨 Colors (28 properties)**
472
- - Base: `background`, `foreground`, `card`, `popover`
473
- - Brand: `primary`, `secondary`, `accent`, `muted`
474
- - System: `destructive`, `border`, `input`, `ring`
475
- - Charts: `chart-1` through `chart-5`
476
- - Sidebar: `sidebar`, `sidebar-primary`, `sidebar-accent`
477
-
478
- **✍️ Typography (3 properties)**
479
- - `font-sans`, `font-serif`, `font-mono`
480
-
481
- **πŸ“ Layout (1 property)**
482
- - `radius` - Border radius system
483
-
484
- **πŸŒ‘ Shadows (6 properties)**
485
- - `shadow-color`, `shadow-opacity`, `shadow-blur`
486
- - `shadow-spread`, `shadow-offset-x`, `shadow-offset-y`
152
+ ### Custom presets (recommended pattern)
487
153
 
488
- **πŸ“ Spacing (2 properties)**
489
- - `letter-spacing`, `spacing`
154
+ Create your presets in TweakCN-compatible format and pass them into `ThemeProvider`:
490
155
 
491
- ### Dynamic Utilities
156
+ ```tsx
157
+ import { ThemeProvider, type TweakCNThemePreset } from "@fakhrirafiki/theme-engine";
492
158
 
493
- CSS utilities that adapt to theme variables:
159
+ // Tip: use `satisfies` (instead of `Record<string, ...>`) to keep literal keys for TS autocomplete
160
+ const customPresets = {
161
+ "my-brand": {
162
+ label: "My Brand",
163
+ styles: {
164
+ light: {
165
+ background: "#ffffff",
166
+ foreground: "#111827",
167
+ card: "#ffffff",
168
+ "card-foreground": "#111827",
169
+ primary: "#2563eb",
170
+ "primary-foreground": "#ffffff",
171
+ secondary: "#e5e7eb",
172
+ "secondary-foreground": "#111827",
173
+ },
174
+ dark: {
175
+ background: "#0b1020",
176
+ foreground: "#f9fafb",
177
+ card: "#111827",
178
+ "card-foreground": "#f9fafb",
179
+ primary: "#60a5fa",
180
+ "primary-foreground": "#0b1020",
181
+ secondary: "#1f2937",
182
+ "secondary-foreground": "#f9fafb",
183
+ },
184
+ },
185
+ },
186
+ } satisfies Record<string, TweakCNThemePreset>;
494
187
 
495
- ```css
496
- /* Dynamic border radius */
497
- .rounded-theme { border-radius: var(--radius); }
498
- .rounded-theme-sm { border-radius: calc(var(--radius) * 0.5); }
499
- .rounded-theme-lg { border-radius: calc(var(--radius) * 2); }
500
-
501
- /* Dynamic shadows */
502
- .shadow-theme {
503
- box-shadow: 0px var(--shadow-offset-y, 4px) var(--shadow-blur, 8px) 0px
504
- rgba(0,0,0,var(--shadow-opacity, 0.1));
188
+ export function AppRoot({ children }: { children: React.ReactNode }) {
189
+ return (
190
+ <ThemeProvider customPresets={customPresets} defaultPreset="my-brand">
191
+ {children}
192
+ </ThemeProvider>
193
+ );
505
194
  }
506
-
507
- /* Dynamic spacing */
508
- .tracking-theme { letter-spacing: var(--letter-spacing); }
509
- .gap-theme { gap: var(--spacing, 0.25rem); }
510
195
  ```
511
196
 
512
- ## πŸ€” Troubleshooting
197
+ Validation behavior:
513
198
 
514
- ### Theme not persisting on refresh
199
+ - Custom presets are validated in `ThemeProvider`.
200
+ - Invalid custom presets are skipped; warnings are allowed.
201
+ - You can validate manually via `validateCustomPresets()` / `logValidationResult()`.
515
202
 
516
- Make sure you're using `ThemeScript` in your `<head>`:
203
+ ## Persistence keys
517
204
 
518
- ```tsx
519
- <head>
520
- <ThemeScript presetStorageKey="your-key" />
521
- </head>
522
- ```
205
+ By default:
523
206
 
524
- ### Preset colors not applying
207
+ - Mode is stored in `localStorage['theme-engine-theme']`.
208
+ - Preset is stored in `localStorage['theme-preset']`.
525
209
 
526
- Ensure you're using `ThemeProvider`:
210
+ If you run multiple apps on the same domain, override the keys:
527
211
 
528
212
  ```tsx
529
- <ThemeProvider enablePresets={true}>
213
+ <ThemeProvider modeStorageKey="my-app:mode" presetStorageKey="my-app:preset">
530
214
  {children}
531
215
  </ThemeProvider>
532
216
  ```
533
217
 
534
- ### SSR hydration mismatch
218
+ ## Tailwind tokens you get
535
219
 
536
- Add `suppressHydrationWarning` to your `<html>` tag:
220
+ After importing `@fakhrirafiki/theme-engine/styles`, you can use semantic tokens like:
537
221
 
538
- ```tsx
539
- <html lang="en" suppressHydrationWarning>
540
- ```
222
+ - `bg-background`, `text-foreground`, `border-border`
223
+ - `bg-primary`, `text-primary-foreground`
224
+ - `bg-accent-success`, `text-accent-danger`, etc (if your presets include the `accent-*` variables)
541
225
 
542
- ## πŸ“¦ Package Contents
226
+ ## Troubleshooting
543
227
 
544
- The npm package includes:
228
+ ### `useTheme must be used within a ThemeProvider`
545
229
 
546
- - **ESM and CommonJS builds** - Universal compatibility
547
- - **TypeScript declarations** - Full type safety
548
- - **Complete CSS system** - All styles included
549
- - **Source maps** - Enhanced debugging
550
- - **Tree-shaking support** - Optimal bundle size
551
-
552
- ```
553
- theme-engine/
554
- β”œβ”€β”€ dist/
555
- β”‚ β”œβ”€β”€ index.js # CommonJS build
556
- β”‚ β”œβ”€β”€ index.mjs # ESM build
557
- β”‚ β”œβ”€β”€ index.d.ts # TypeScript declarations
558
- β”‚ └── styles/ # CSS files
559
- β”‚ β”œβ”€β”€ index.css # Complete system
560
- β”‚ β”œβ”€β”€ base.css # CSS variables
561
- β”‚ β”œβ”€β”€ animations.css
562
- β”‚ β”œβ”€β”€ components.css
563
- β”‚ β”œβ”€β”€ utilities.css
564
- β”‚ └── tailwind.css
565
- └── README.md
566
- ```
230
+ Wrap your component tree with `ThemeProvider` (and ensure the component is a client component).
567
231
 
568
- ## πŸ—οΈ Architecture
232
+ ### Preset doesn’t apply on refresh
569
233
 
570
- The theme engine elegantly coordinates two theming concerns:
234
+ If you render `ThemeScript` manually (using `disableScript`), make sure both use the same `presetStorageKey`.
571
235
 
572
- - **Theme Mode** (light/dark/system) - Controlled by `ThemeProvider`
573
- - **Color Presets** - Managed by `ThemeProvider` using CSS variables with `!important`
574
- - **Coordination** - MutationObserver detects mode changes and reapplies presets
575
- - **Persistence** - Separate localStorage keys prevent conflicts
576
- - **Zero-Config** - Built-in TweakCN presets work automatically
236
+ ### `ThemePresetButtons` breaks
577
237
 
578
- This architecture ensures seamless coordination between appearance modes and color presets without conflicts.
238
+ Ensure you imported `@fakhrirafiki/theme-engine/styles` (or at least `animations.css` + `components.css`).
579
239
 
580
- ## πŸ“„ License
240
+ ## License
581
241
 
582
- MIT License - see the monorepo root for details.
242
+ MIT