@fakhrirafiki/theme-engine 0.4.8 → 0.4.9

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.
Files changed (2) hide show
  1. package/README.md +100 -13
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -25,6 +25,7 @@ Theme system for **Next.js (App Router)**: mode (`light | dark | system`) + them
25
25
  - [Built-in presets](#-built-in-presets)
26
26
  - [Tailwind tokens](#-tailwind-tokens-you-get)
27
27
  - [Components](#-components)
28
+ - [Recipe: ThemePresetSelect](#-recipe-themepresetselect-simple-list)
28
29
  - [API reference](#-api-reference)
29
30
  - [Troubleshooting](#-troubleshooting)
30
31
 
@@ -166,8 +167,8 @@ export function TypedPresetButtons() {
166
167
 
167
168
  ### SSR & flashes
168
169
 
169
- - `ThemeProvider` injects `ThemeScript` to restore **preset colors** before hydration (reduces flashes).
170
- - `ThemeScript` restores **preset colors only** (it does not set the `.dark` / `.light` class).
170
+ - `ThemeProvider` injects a small pre-hydration script to restore **preset colors** before hydration (reduces flashes).
171
+ - The pre-hydration script restores **preset colors only** (it does not set the `.dark` / `.light` class).
171
172
  - `defaultPreset="..."` pre-hydration only works for **built-in presets**. Custom `defaultPreset` still works after hydration.
172
173
 
173
174
  ### Persistence
@@ -325,6 +326,101 @@ export function PresetPicker() {
325
326
  }
326
327
  ```
327
328
 
329
+ ### 🧾 Recipe: `ThemePresetSelect` (simple list)
330
+
331
+ Want a simple, scrollable preset list (e.g. for a settings modal)? Copy-paste this component and style it however you like.
332
+
333
+ > Note: this snippet uses Tailwind utility classes. If you don’t use Tailwind, replace the classes with your own styles/UI components.
334
+
335
+ ```tsx
336
+ 'use client';
337
+
338
+ import { formatColor, useThemeEngine } from '@fakhrirafiki/theme-engine';
339
+
340
+ type ThemePresetSelectProps = {
341
+ allowedPresetIds?: string[];
342
+ };
343
+
344
+ export function ThemePresetSelect({
345
+ allowedPresetIds = [
346
+ 'modern-minimal',
347
+ 'violet-bloom',
348
+ 'vercel',
349
+ 'mono',
350
+ ],
351
+ }: ThemePresetSelectProps) {
352
+ const { currentTheme, applyThemeById, availablePresets, resolvedMode } = useThemeEngine();
353
+
354
+ const presets = allowedPresetIds
355
+ .map((id) => {
356
+ const preset = availablePresets[id];
357
+ if (!preset) return null;
358
+ return { id, label: preset.label };
359
+ })
360
+ .filter((preset): preset is { id: string; label: string } => preset !== null);
361
+
362
+ const getPreviewColors = (presetId: string): string[] => {
363
+ const preset = availablePresets[presetId];
364
+ if (!preset) return [];
365
+
366
+ const scheme = resolvedMode === 'dark' ? preset.styles.dark : preset.styles.light;
367
+ const primary = (scheme as any).primary as string | undefined;
368
+ const secondary = (scheme as any).secondary as string | undefined;
369
+ const accent = (scheme as any).accent as string | undefined;
370
+
371
+ return [primary, secondary, accent].filter(Boolean) as string[];
372
+ };
373
+
374
+ return (
375
+ <div className="mt-4 max-h-[70vh] space-y-2 overflow-y-auto pr-1">
376
+ {presets.map((preset) => {
377
+ const isActive = currentTheme?.presetId === preset.id;
378
+ const previewColors = getPreviewColors(preset.id).slice(0, 3);
379
+
380
+ return (
381
+ <button
382
+ key={preset.id}
383
+ type="button"
384
+ className={`w-full rounded-full border px-3 py-2 text-xs transition-colors ${
385
+ isActive
386
+ ? 'border-primary/70 bg-primary/10 text-foreground'
387
+ : 'border-border bg-muted/40 text-muted-foreground hover:border-muted-foreground/40 hover:bg-muted/60'
388
+ }`}
389
+ onClick={() => applyThemeById(preset.id)}
390
+ >
391
+ <span className="flex items-center justify-between gap-3">
392
+ <span className="flex items-center gap-2">
393
+ {previewColors.length > 0 && (
394
+ <span className="flex gap-1">
395
+ {previewColors.map((color, index) => (
396
+ <span
397
+ key={index}
398
+ className="inline-block h-2.5 w-2.5 rounded-full border border-foreground/10 shadow-sm"
399
+ style={{ backgroundColor: formatColor(color, 'hex') }}
400
+ />
401
+ ))}
402
+ </span>
403
+ )}
404
+
405
+ <span className="text-xs font-medium text-foreground">{preset.label}</span>
406
+ </span>
407
+
408
+ {isActive && (
409
+ <span className="inline-flex items-center rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-foreground">
410
+ Aktif
411
+ </span>
412
+ )}
413
+ </span>
414
+ </button>
415
+ );
416
+ })}
417
+
418
+ {presets.length === 0 && <p className="text-xs text-muted-foreground">Belum ada tema yang tersedia.</p>}
419
+ </div>
420
+ );
421
+ }
422
+ ```
423
+
328
424
  ---
329
425
 
330
426
  ## 🧾 API Reference
@@ -349,7 +445,7 @@ export function PresetPicker() {
349
445
  | `modeStorageKey` | `string` | `'theme-engine-theme'` | `localStorage` key for mode |
350
446
  | `presetStorageKey` | `string` | `'theme-preset'` | `localStorage` key for preset |
351
447
  | `customPresets` | `Record<string, TweakCNThemePreset>` | `undefined` | Add your own presets (can override built-ins by ID) |
352
- | `ThemeScript` | n/a | always on | `ThemeProvider` always injects `ThemeScript` for pre-hydration preset restoration |
448
+ | `Pre-hydration script` | n/a | always on | `ThemeProvider` always injects a pre-hydration script for preset restoration |
353
449
 
354
450
  ### `useThemeEngine()`
355
451
 
@@ -382,15 +478,6 @@ Return fields:
382
478
  | `builtInPresets` | `Record<string, TweakCNThemePreset>` | Built-in only |
383
479
  | `customPresets` | `Record<string, TweakCNThemePreset>` | Custom only |
384
480
 
385
- ### `ThemeScript`
386
-
387
- Normally you don’t need this (it’s injected by `ThemeProvider` by default).
388
-
389
- | Prop | Type | Default | Description |
390
- | --- | --- | --- | --- |
391
- | `presetStorageKey` | `string` | `'theme-preset'` | Must match `ThemeProvider` |
392
- | `defaultPreset` | `string` | `undefined` | Built-in preset ID for pre-hydration default |
393
-
394
481
  ### Utilities
395
482
 
396
483
  | Export | Description |
@@ -410,7 +497,7 @@ Note: the thrown error string might mention `useTheme` because `useThemeEngine()
410
497
 
411
498
  ### Preset doesn’t apply on refresh
412
499
 
413
- `ThemeProvider` injects `ThemeScript` automatically. Avoid rendering `ThemeScript` manually (you may end up with duplicates).
500
+ `ThemeProvider` injects a pre-hydration script automatically. Avoid injecting another preset-restoration script manually (you may end up with duplicates).
414
501
 
415
502
  ### Styles don’t load / components look unstyled
416
503
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fakhrirafiki/theme-engine",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Elegant theming system with smooth transitions, custom presets, semantic accent colors, and complete shadcn/ui support for modern React applications",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",