@fakhrirafiki/theme-engine 0.4.20 → 0.4.22

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
@@ -19,6 +19,9 @@ Dark mode + theme presets (semantic tokens via CSS variables).
19
19
  - 🎨 **Theme presets**: built-in presets + your own presets
20
20
  - 🧩 **Tailwind v4 friendly**: `@theme inline` tokens included (works with shadcn-style semantic tokens)
21
21
 
22
+ > [!TIP]
23
+ > If you’re using **shadcn/ui** with **Next.js App Router**, you should use this package — it provides a complete, production-ready theme layer (mode + presets) that plugs straight into shadcn’s semantic tokens.
24
+
22
25
  ## 📚 Table of contents
23
26
 
24
27
  - [Install](#-install)
package/dist/index.d.mts CHANGED
@@ -234,15 +234,37 @@ interface ThemePresetButtonsProps {
234
234
  showSectionHeaders?: boolean;
235
235
  }
236
236
 
237
+ /**
238
+ * Appearance mode.
239
+ *
240
+ * - `"system"` follows `prefers-color-scheme`
241
+ * - `resolvedMode` (from hooks/provider) is always `"light"` or `"dark"`
242
+ *
243
+ * @public
244
+ */
237
245
  type Mode = "light" | "dark" | "system";
246
+ /**
247
+ * Screen coordinates used for the optional view-transition ripple when toggling modes.
248
+ *
249
+ * @public
250
+ */
238
251
  interface Coordinates {
239
252
  x: number;
240
253
  y: number;
241
254
  }
255
+ /**
256
+ * Props for `ThemeToggle`.
257
+ *
258
+ * @public
259
+ */
242
260
  interface ThemeToggleProps {
261
+ /** Additional class name(s) applied to the button element */
243
262
  className?: string;
263
+ /** Styling hook exposed via `data-size` */
244
264
  size?: "sm" | "md" | "lg";
265
+ /** Styling hook exposed via `data-variant` */
245
266
  variant?: "default" | "outline" | "ghost";
267
+ /** Optional custom icon/content (overrides the default icon) */
246
268
  children?: ReactNode;
247
269
  }
248
270
 
@@ -366,13 +388,33 @@ type PresetId<TCustomPresets> = BuiltInPresetId | CustomPresetId$2<TCustomPreset
366
388
  interface UnifiedThemeProviderProps<TCustomPresets extends CustomPresetsRecord$2 | undefined = undefined> {
367
389
  /** React children to wrap with theming context */
368
390
  children: react__default.ReactNode;
369
- /** Default appearance mode when no stored preference exists */
391
+ /**
392
+ * Default appearance mode when no stored preference exists.
393
+ *
394
+ * Notes for SSR/App Router:
395
+ * - The initial render must be deterministic between server and client to avoid hydration mismatches.
396
+ * - Persisted mode is restored after hydration (and also pre-hydration via the injected `ThemeScript`).
397
+ */
370
398
  defaultMode?: Mode;
371
- /** Default preset ID to use when no stored preset exists or when resetting */
399
+ /**
400
+ * Default preset ID to use when no stored preset exists or when resetting.
401
+ *
402
+ * If provided, the preset will be applied when:
403
+ * - there is no persisted preset in `localStorage`, or
404
+ * - `clearPreset()` is called.
405
+ */
372
406
  defaultPreset?: PresetId<TCustomPresets>;
373
- /** localStorage key for appearance mode persistence */
407
+ /**
408
+ * `localStorage` key for appearance mode persistence.
409
+ *
410
+ * Stored value is one of: `"light" | "dark" | "system"`.
411
+ */
374
412
  modeStorageKey?: string;
375
- /** localStorage key for color preset persistence */
413
+ /**
414
+ * `localStorage` key for color preset persistence.
415
+ *
416
+ * Stored value is a JSON blob written by this provider and restored on subsequent loads.
417
+ */
376
418
  presetStorageKey?: string;
377
419
  /** Custom presets to add to the available collection */
378
420
  customPresets?: TCustomPresets;
@@ -391,6 +433,14 @@ interface UnifiedThemeProviderProps<TCustomPresets extends CustomPresetsRecord$2
391
433
  * - 🎨 **CSS `!important`** ensures presets override mode defaults
392
434
  * - 👀 **MutationObserver** automatically reapplies presets on mode changes
393
435
  *
436
+ * ## SSR / hydration behavior
437
+ * This provider is designed for Next.js App Router where Client Components are still SSR-ed.
438
+ * To avoid hydration mismatches:
439
+ * - The initial render does not read `localStorage`.
440
+ * - A pre-hydration `ThemeScript` is injected to apply the correct `html` mode class (`light`/`dark`)
441
+ * and restore preset CSS variables as early as possible.
442
+ * - Persisted mode and preset are then reconciled after hydration.
443
+ *
394
444
  * @example
395
445
  * ```tsx
396
446
  * <ThemeProvider
@@ -411,6 +461,8 @@ declare function ThemeProvider<const TCustomPresets extends CustomPresetsRecord$
411
461
  * Provides access to both appearance mode controls and preset management
412
462
  * in a single, coordinated interface.
413
463
  *
464
+ * Prefer `useThemeEngine()` for a DX-first API with aliases and typed preset IDs.
465
+ *
414
466
  * @example
415
467
  * ```tsx
416
468
  * // Mode controls
@@ -476,17 +528,79 @@ interface ThemeScriptProps {
476
528
  defaultPreset?: string;
477
529
  }
478
530
  /**
479
- * Pre-hydration theme script.
480
- * - Restores appearance mode (light/dark/system) to avoid hydration mismatch + FOUC.
481
- * - Restores preset CSS variables early so Tailwind/shadcn tokens render correctly on first paint.
531
+ * Pre-hydration theme script injected by `ThemeProvider`.
532
+ *
533
+ * This runs before React hydration and is intentionally implemented as an inline script so it can:
534
+ * - restore the `html` mode class (`light`/`dark`) and `color-scheme` as early as possible
535
+ * - restore preset CSS variables early to prevent FOUC (unstyled/incorrect tokens on first paint)
536
+ *
537
+ * It reads:
538
+ * - `localStorage[modeStorageKey]` (mode persistence)
539
+ * - `localStorage[presetStorageKey]` (preset persistence)
540
+ *
541
+ * It writes:
542
+ * - `document.documentElement.classList` (`light`/`dark`)
543
+ * - `document.documentElement.style.colorScheme`
544
+ * - `document.documentElement.dataset.themeEngineMode` and `dataset.themeEngineResolvedMode` (best-effort)
545
+ *
546
+ * You typically do not render this manually — `ThemeProvider` includes it automatically.
482
547
  */
483
548
  declare function ThemeScript({ presetStorageKey, modeStorageKey, defaultMode, defaultPreset, }: ThemeScriptProps): react_jsx_runtime.JSX.Element;
484
549
 
550
+ /**
551
+ * Button that toggles the current appearance mode (light ↔ dark) using Theme Engine.
552
+ *
553
+ * - Reads `mode` / `resolvedMode` from `ThemeProvider` via `useTheme()`
554
+ * - On click, calls `toggleMode({ x, y })` to enable the optional view-transition ripple
555
+ * - Renders an icon that reflects the current mode (`light`/`dark`/`system`) unless you pass `children`
556
+ *
557
+ * Data attributes:
558
+ * - `data-size`: `"sm" | "md" | "lg"` (for styling hooks)
559
+ * - `data-variant`: `"default" | "outline" | "ghost"`
560
+ * - `data-mode`: resolved mode (`"light" | "dark"`) for CSS hooks
561
+ * - `data-theme`: alias of `data-mode`
562
+ *
563
+ * @example
564
+ * ```tsx
565
+ * import { ThemeToggle } from "@fakhrirafiki/theme-engine";
566
+ *
567
+ * export function Header() {
568
+ * return <ThemeToggle className="ml-auto" />;
569
+ * }
570
+ * ```
571
+ */
485
572
  declare const ThemeToggle: react.ForwardRefExoticComponent<ThemeToggleProps & react.RefAttributes<HTMLButtonElement>>;
486
573
 
487
574
  /**
488
575
  * Main ThemePresetButtons component
489
576
  */
577
+ /**
578
+ * Preset picker UI for Theme Engine.
579
+ *
580
+ * Renders a horizontally scrolling set of preset buttons (optionally in multiple rows) and applies
581
+ * the selected preset via `ThemeProvider` context.
582
+ *
583
+ * Requirements:
584
+ * - Must be used under `ThemeProvider` (it reads `availablePresets`/`currentPreset` from context).
585
+ *
586
+ * Behavior:
587
+ * - Built-in + custom presets are merged from context and displayed (custom presets are shown first).
588
+ * - Selecting a preset calls `applyPreset()` from the provider, which also persists it to `localStorage`.
589
+ * - Supports infinite marquee animation; disable via `animation={{ enabled: false }}`.
590
+ *
591
+ * Customization:
592
+ * - Use `renderPreset` to fully control the button UI (selection handling is still managed internally).
593
+ * - Use `renderColorBox` to customize the color dots while keeping the default layout.
594
+ *
595
+ * @example
596
+ * ```tsx
597
+ * import { ThemePresetButtons } from "@fakhrirafiki/theme-engine";
598
+ *
599
+ * export function PresetsSection() {
600
+ * return <ThemePresetButtons className="mt-6" maxPresets={24} />;
601
+ * }
602
+ * ```
603
+ */
490
604
  declare const ThemePresetButtons: ({ animation: animationOverrides, layout: layoutOverrides, renderPreset, renderColorBox, className, categories, maxPresets, showBuiltIn, showCustom, }: ThemePresetButtonsProps) => react_jsx_runtime.JSX.Element;
491
605
 
492
606
  type CustomPresetsRecord$1 = Record<string, TweakCNThemePreset>;
@@ -500,6 +614,28 @@ type LooseString$1 = string & {};
500
614
  * - Custom IDs are inferred from the keys of the `customPresets` argument
501
615
  *
502
616
  * The resulting `setThemePresetById()` still accepts any string, but VS Code will suggest known IDs first.
617
+ *
618
+ * Notes:
619
+ * - `customPresets` is only used for TypeScript inference (no runtime effect).
620
+ * - Prefer `useThemeEngine()` if you want a higher-level DX API with aliases.
621
+ *
622
+ * @example
623
+ * ```tsx
624
+ * "use client";
625
+ *
626
+ * import { useTypedTheme } from "@fakhrirafiki/theme-engine";
627
+ * import { customPresets } from "./custom-presets";
628
+ *
629
+ * export function PresetPicker() {
630
+ * const { currentPreset, setThemePresetById } = useTypedTheme(customPresets);
631
+ *
632
+ * return (
633
+ * <button onClick={() => setThemePresetById("my-brand")}>
634
+ * Active: {currentPreset?.presetName ?? "default"}
635
+ * </button>
636
+ * );
637
+ * }
638
+ * ```
503
639
  */
504
640
  declare function useTypedTheme<const TCustomPresets extends CustomPresetsRecord$1 | undefined = undefined>(customPresets?: TCustomPresets): {
505
641
  setThemePresetById: (presetId: LooseString$1 | ThemePresetId$1<TCustomPresets>) => void;
@@ -528,11 +664,26 @@ type LooseString = string & {};
528
664
  /**
529
665
  * Type helper to "register" your presets for autocomplete.
530
666
  *
531
- * Usage:
532
- * `useThemeEngine<ThemePresets<typeof customPresets>>()`
667
+ * @example
668
+ * ```ts
669
+ * import { type ThemePresets, useThemeEngine } from "@fakhrirafiki/theme-engine";
670
+ * import { customPresets } from "./custom-presets";
671
+ *
672
+ * type PresetRegistry = ThemePresets<typeof customPresets>;
673
+ *
674
+ * const theme = useThemeEngine<PresetRegistry>();
675
+ * // theme.applyThemeById("my-custom-id") // ✅ autocomplete for keys in customPresets + built-ins
676
+ * ```
533
677
  */
534
678
  type ThemePresets<T> = T extends CustomPresetsRecord ? T : never;
535
679
  type ThemeEnginePresetId<TCustomPresets extends CustomPresetsRecord | undefined = undefined> = ThemePresetId<TCustomPresets>;
680
+ /**
681
+ * Accepts either:
682
+ * - a typed preset ID (built-in + inferred custom preset IDs), or
683
+ * - any string (runtime safety / forwards compatibility)
684
+ *
685
+ * This is useful when you receive preset IDs dynamically (e.g. from a URL param).
686
+ */
536
687
  type ThemeId<TCustomPresets extends CustomPresetsRecord | undefined = undefined> = ThemeEnginePresetId<TCustomPresets> | LooseString;
537
688
  /**
538
689
  * DX-first hook for Theme Engine.
@@ -543,6 +694,45 @@ type ThemeId<TCustomPresets extends CustomPresetsRecord | undefined = undefined>
543
694
  *
544
695
  * For typed preset ID autocomplete (built-in + your custom IDs):
545
696
  * `useThemeEngine<ThemePresets<typeof customPresets>>()`
697
+ *
698
+ * Naming:
699
+ * - `applyThemeById` and `applyPresetById` are aliases
700
+ * - `clearTheme` and `clearPreset` are aliases
701
+ *
702
+ * @example
703
+ * ```tsx
704
+ * "use client";
705
+ *
706
+ * import { ThemeProvider, useThemeEngine, type ThemePresets } from "@fakhrirafiki/theme-engine";
707
+ * import { customPresets } from "./custom-presets";
708
+ *
709
+ * type Presets = ThemePresets<typeof customPresets>;
710
+ *
711
+ * function Controls() {
712
+ * const { mode, resolvedMode, setDarkMode, applyThemeById, clearTheme } = useThemeEngine<Presets>();
713
+ *
714
+ * return (
715
+ * <div>
716
+ * <button onClick={() => setDarkMode("system")}>System</button>
717
+ * <button onClick={() => setDarkMode("light")}>Light</button>
718
+ * <button onClick={() => setDarkMode("dark")}>Dark</button>
719
+ *
720
+ * <button onClick={() => applyThemeById("modern-minimal")}>Modern Minimal</button>
721
+ * <button onClick={() => clearTheme()}>Reset</button>
722
+ *
723
+ * <div>mode: {mode} · resolved: {resolvedMode}</div>
724
+ * </div>
725
+ * );
726
+ * }
727
+ *
728
+ * export default function Page() {
729
+ * return (
730
+ * <ThemeProvider customPresets={customPresets} defaultPreset="modern-minimal">
731
+ * <Controls />
732
+ * </ThemeProvider>
733
+ * );
734
+ * }
735
+ * ```
546
736
  */
547
737
  declare function useThemeEngine<const TCustomPresets extends CustomPresetsRecord | undefined = undefined>(): {
548
738
  darkMode: boolean;
@@ -585,7 +775,14 @@ type ColorFormat = 'hsl' | 'rgb' | 'hex';
585
775
  */
586
776
  declare function formatColor(colorInput: string, outputFormat?: ColorFormat, includeFunctionWrapper?: boolean): string;
587
777
  /**
588
- * Create color with alpha transparency
778
+ * Create a color with alpha transparency.
779
+ *
780
+ * Notes:
781
+ * - This helper only supports HSL-like inputs that `parseHSL()` can parse
782
+ * (e.g. `"hsl(210 40% 98%)"` or `"210 40% 98%"`).
783
+ * - For hex/rgb inputs, convert first with `formatColor(color, "hsl")`.
784
+ *
785
+ * @public
589
786
  */
590
787
  declare function withAlpha(colorInput: string, alpha: number): string;
591
788
 
@@ -595,7 +792,12 @@ declare function withAlpha(colorInput: string, alpha: number): string;
595
792
  */
596
793
 
597
794
  /**
598
- * Validation result type
795
+ * Validation result type.
796
+ *
797
+ * - `errors` should be treated as invalid input (preset should be rejected)
798
+ * - `warnings` indicate potentially incomplete presets but may still be usable
799
+ *
800
+ * @public
599
801
  */
600
802
  interface ValidationResult {
601
803
  isValid: boolean;
@@ -603,15 +805,30 @@ interface ValidationResult {
603
805
  warnings: string[];
604
806
  }
605
807
  /**
606
- * Validate a single TweakCN preset
808
+ * Validate a single preset in the TweakCN-compatible shape.
809
+ *
810
+ * Intended usage:
811
+ * - validating user-provided presets before passing them to `ThemeProvider`
812
+ * - debugging preset issues in development
813
+ *
814
+ * Notes:
815
+ * - This is a lightweight validator (it does not fully parse/compute CSS colors)
816
+ *
817
+ * @public
607
818
  */
608
819
  declare function validateTweakCNPreset(preset: any, presetId?: string): ValidationResult;
609
820
  /**
610
- * Validate a collection of custom presets
821
+ * Validate a collection of custom presets (record keyed by preset ID).
822
+ *
823
+ * @public
611
824
  */
612
825
  declare function validateCustomPresets(customPresets: Record<string, TweakCNThemePreset>): ValidationResult;
613
826
  /**
614
- * Helper function to log validation results
827
+ * Convenience logger for `ValidationResult`.
828
+ *
829
+ * This is primarily intended for local development diagnostics.
830
+ *
831
+ * @public
615
832
  */
616
833
  declare function logValidationResult(result: ValidationResult, context?: string): void;
617
834
 
package/dist/index.d.ts CHANGED
@@ -234,15 +234,37 @@ interface ThemePresetButtonsProps {
234
234
  showSectionHeaders?: boolean;
235
235
  }
236
236
 
237
+ /**
238
+ * Appearance mode.
239
+ *
240
+ * - `"system"` follows `prefers-color-scheme`
241
+ * - `resolvedMode` (from hooks/provider) is always `"light"` or `"dark"`
242
+ *
243
+ * @public
244
+ */
237
245
  type Mode = "light" | "dark" | "system";
246
+ /**
247
+ * Screen coordinates used for the optional view-transition ripple when toggling modes.
248
+ *
249
+ * @public
250
+ */
238
251
  interface Coordinates {
239
252
  x: number;
240
253
  y: number;
241
254
  }
255
+ /**
256
+ * Props for `ThemeToggle`.
257
+ *
258
+ * @public
259
+ */
242
260
  interface ThemeToggleProps {
261
+ /** Additional class name(s) applied to the button element */
243
262
  className?: string;
263
+ /** Styling hook exposed via `data-size` */
244
264
  size?: "sm" | "md" | "lg";
265
+ /** Styling hook exposed via `data-variant` */
245
266
  variant?: "default" | "outline" | "ghost";
267
+ /** Optional custom icon/content (overrides the default icon) */
246
268
  children?: ReactNode;
247
269
  }
248
270
 
@@ -366,13 +388,33 @@ type PresetId<TCustomPresets> = BuiltInPresetId | CustomPresetId$2<TCustomPreset
366
388
  interface UnifiedThemeProviderProps<TCustomPresets extends CustomPresetsRecord$2 | undefined = undefined> {
367
389
  /** React children to wrap with theming context */
368
390
  children: react__default.ReactNode;
369
- /** Default appearance mode when no stored preference exists */
391
+ /**
392
+ * Default appearance mode when no stored preference exists.
393
+ *
394
+ * Notes for SSR/App Router:
395
+ * - The initial render must be deterministic between server and client to avoid hydration mismatches.
396
+ * - Persisted mode is restored after hydration (and also pre-hydration via the injected `ThemeScript`).
397
+ */
370
398
  defaultMode?: Mode;
371
- /** Default preset ID to use when no stored preset exists or when resetting */
399
+ /**
400
+ * Default preset ID to use when no stored preset exists or when resetting.
401
+ *
402
+ * If provided, the preset will be applied when:
403
+ * - there is no persisted preset in `localStorage`, or
404
+ * - `clearPreset()` is called.
405
+ */
372
406
  defaultPreset?: PresetId<TCustomPresets>;
373
- /** localStorage key for appearance mode persistence */
407
+ /**
408
+ * `localStorage` key for appearance mode persistence.
409
+ *
410
+ * Stored value is one of: `"light" | "dark" | "system"`.
411
+ */
374
412
  modeStorageKey?: string;
375
- /** localStorage key for color preset persistence */
413
+ /**
414
+ * `localStorage` key for color preset persistence.
415
+ *
416
+ * Stored value is a JSON blob written by this provider and restored on subsequent loads.
417
+ */
376
418
  presetStorageKey?: string;
377
419
  /** Custom presets to add to the available collection */
378
420
  customPresets?: TCustomPresets;
@@ -391,6 +433,14 @@ interface UnifiedThemeProviderProps<TCustomPresets extends CustomPresetsRecord$2
391
433
  * - 🎨 **CSS `!important`** ensures presets override mode defaults
392
434
  * - 👀 **MutationObserver** automatically reapplies presets on mode changes
393
435
  *
436
+ * ## SSR / hydration behavior
437
+ * This provider is designed for Next.js App Router where Client Components are still SSR-ed.
438
+ * To avoid hydration mismatches:
439
+ * - The initial render does not read `localStorage`.
440
+ * - A pre-hydration `ThemeScript` is injected to apply the correct `html` mode class (`light`/`dark`)
441
+ * and restore preset CSS variables as early as possible.
442
+ * - Persisted mode and preset are then reconciled after hydration.
443
+ *
394
444
  * @example
395
445
  * ```tsx
396
446
  * <ThemeProvider
@@ -411,6 +461,8 @@ declare function ThemeProvider<const TCustomPresets extends CustomPresetsRecord$
411
461
  * Provides access to both appearance mode controls and preset management
412
462
  * in a single, coordinated interface.
413
463
  *
464
+ * Prefer `useThemeEngine()` for a DX-first API with aliases and typed preset IDs.
465
+ *
414
466
  * @example
415
467
  * ```tsx
416
468
  * // Mode controls
@@ -476,17 +528,79 @@ interface ThemeScriptProps {
476
528
  defaultPreset?: string;
477
529
  }
478
530
  /**
479
- * Pre-hydration theme script.
480
- * - Restores appearance mode (light/dark/system) to avoid hydration mismatch + FOUC.
481
- * - Restores preset CSS variables early so Tailwind/shadcn tokens render correctly on first paint.
531
+ * Pre-hydration theme script injected by `ThemeProvider`.
532
+ *
533
+ * This runs before React hydration and is intentionally implemented as an inline script so it can:
534
+ * - restore the `html` mode class (`light`/`dark`) and `color-scheme` as early as possible
535
+ * - restore preset CSS variables early to prevent FOUC (unstyled/incorrect tokens on first paint)
536
+ *
537
+ * It reads:
538
+ * - `localStorage[modeStorageKey]` (mode persistence)
539
+ * - `localStorage[presetStorageKey]` (preset persistence)
540
+ *
541
+ * It writes:
542
+ * - `document.documentElement.classList` (`light`/`dark`)
543
+ * - `document.documentElement.style.colorScheme`
544
+ * - `document.documentElement.dataset.themeEngineMode` and `dataset.themeEngineResolvedMode` (best-effort)
545
+ *
546
+ * You typically do not render this manually — `ThemeProvider` includes it automatically.
482
547
  */
483
548
  declare function ThemeScript({ presetStorageKey, modeStorageKey, defaultMode, defaultPreset, }: ThemeScriptProps): react_jsx_runtime.JSX.Element;
484
549
 
550
+ /**
551
+ * Button that toggles the current appearance mode (light ↔ dark) using Theme Engine.
552
+ *
553
+ * - Reads `mode` / `resolvedMode` from `ThemeProvider` via `useTheme()`
554
+ * - On click, calls `toggleMode({ x, y })` to enable the optional view-transition ripple
555
+ * - Renders an icon that reflects the current mode (`light`/`dark`/`system`) unless you pass `children`
556
+ *
557
+ * Data attributes:
558
+ * - `data-size`: `"sm" | "md" | "lg"` (for styling hooks)
559
+ * - `data-variant`: `"default" | "outline" | "ghost"`
560
+ * - `data-mode`: resolved mode (`"light" | "dark"`) for CSS hooks
561
+ * - `data-theme`: alias of `data-mode`
562
+ *
563
+ * @example
564
+ * ```tsx
565
+ * import { ThemeToggle } from "@fakhrirafiki/theme-engine";
566
+ *
567
+ * export function Header() {
568
+ * return <ThemeToggle className="ml-auto" />;
569
+ * }
570
+ * ```
571
+ */
485
572
  declare const ThemeToggle: react.ForwardRefExoticComponent<ThemeToggleProps & react.RefAttributes<HTMLButtonElement>>;
486
573
 
487
574
  /**
488
575
  * Main ThemePresetButtons component
489
576
  */
577
+ /**
578
+ * Preset picker UI for Theme Engine.
579
+ *
580
+ * Renders a horizontally scrolling set of preset buttons (optionally in multiple rows) and applies
581
+ * the selected preset via `ThemeProvider` context.
582
+ *
583
+ * Requirements:
584
+ * - Must be used under `ThemeProvider` (it reads `availablePresets`/`currentPreset` from context).
585
+ *
586
+ * Behavior:
587
+ * - Built-in + custom presets are merged from context and displayed (custom presets are shown first).
588
+ * - Selecting a preset calls `applyPreset()` from the provider, which also persists it to `localStorage`.
589
+ * - Supports infinite marquee animation; disable via `animation={{ enabled: false }}`.
590
+ *
591
+ * Customization:
592
+ * - Use `renderPreset` to fully control the button UI (selection handling is still managed internally).
593
+ * - Use `renderColorBox` to customize the color dots while keeping the default layout.
594
+ *
595
+ * @example
596
+ * ```tsx
597
+ * import { ThemePresetButtons } from "@fakhrirafiki/theme-engine";
598
+ *
599
+ * export function PresetsSection() {
600
+ * return <ThemePresetButtons className="mt-6" maxPresets={24} />;
601
+ * }
602
+ * ```
603
+ */
490
604
  declare const ThemePresetButtons: ({ animation: animationOverrides, layout: layoutOverrides, renderPreset, renderColorBox, className, categories, maxPresets, showBuiltIn, showCustom, }: ThemePresetButtonsProps) => react_jsx_runtime.JSX.Element;
491
605
 
492
606
  type CustomPresetsRecord$1 = Record<string, TweakCNThemePreset>;
@@ -500,6 +614,28 @@ type LooseString$1 = string & {};
500
614
  * - Custom IDs are inferred from the keys of the `customPresets` argument
501
615
  *
502
616
  * The resulting `setThemePresetById()` still accepts any string, but VS Code will suggest known IDs first.
617
+ *
618
+ * Notes:
619
+ * - `customPresets` is only used for TypeScript inference (no runtime effect).
620
+ * - Prefer `useThemeEngine()` if you want a higher-level DX API with aliases.
621
+ *
622
+ * @example
623
+ * ```tsx
624
+ * "use client";
625
+ *
626
+ * import { useTypedTheme } from "@fakhrirafiki/theme-engine";
627
+ * import { customPresets } from "./custom-presets";
628
+ *
629
+ * export function PresetPicker() {
630
+ * const { currentPreset, setThemePresetById } = useTypedTheme(customPresets);
631
+ *
632
+ * return (
633
+ * <button onClick={() => setThemePresetById("my-brand")}>
634
+ * Active: {currentPreset?.presetName ?? "default"}
635
+ * </button>
636
+ * );
637
+ * }
638
+ * ```
503
639
  */
504
640
  declare function useTypedTheme<const TCustomPresets extends CustomPresetsRecord$1 | undefined = undefined>(customPresets?: TCustomPresets): {
505
641
  setThemePresetById: (presetId: LooseString$1 | ThemePresetId$1<TCustomPresets>) => void;
@@ -528,11 +664,26 @@ type LooseString = string & {};
528
664
  /**
529
665
  * Type helper to "register" your presets for autocomplete.
530
666
  *
531
- * Usage:
532
- * `useThemeEngine<ThemePresets<typeof customPresets>>()`
667
+ * @example
668
+ * ```ts
669
+ * import { type ThemePresets, useThemeEngine } from "@fakhrirafiki/theme-engine";
670
+ * import { customPresets } from "./custom-presets";
671
+ *
672
+ * type PresetRegistry = ThemePresets<typeof customPresets>;
673
+ *
674
+ * const theme = useThemeEngine<PresetRegistry>();
675
+ * // theme.applyThemeById("my-custom-id") // ✅ autocomplete for keys in customPresets + built-ins
676
+ * ```
533
677
  */
534
678
  type ThemePresets<T> = T extends CustomPresetsRecord ? T : never;
535
679
  type ThemeEnginePresetId<TCustomPresets extends CustomPresetsRecord | undefined = undefined> = ThemePresetId<TCustomPresets>;
680
+ /**
681
+ * Accepts either:
682
+ * - a typed preset ID (built-in + inferred custom preset IDs), or
683
+ * - any string (runtime safety / forwards compatibility)
684
+ *
685
+ * This is useful when you receive preset IDs dynamically (e.g. from a URL param).
686
+ */
536
687
  type ThemeId<TCustomPresets extends CustomPresetsRecord | undefined = undefined> = ThemeEnginePresetId<TCustomPresets> | LooseString;
537
688
  /**
538
689
  * DX-first hook for Theme Engine.
@@ -543,6 +694,45 @@ type ThemeId<TCustomPresets extends CustomPresetsRecord | undefined = undefined>
543
694
  *
544
695
  * For typed preset ID autocomplete (built-in + your custom IDs):
545
696
  * `useThemeEngine<ThemePresets<typeof customPresets>>()`
697
+ *
698
+ * Naming:
699
+ * - `applyThemeById` and `applyPresetById` are aliases
700
+ * - `clearTheme` and `clearPreset` are aliases
701
+ *
702
+ * @example
703
+ * ```tsx
704
+ * "use client";
705
+ *
706
+ * import { ThemeProvider, useThemeEngine, type ThemePresets } from "@fakhrirafiki/theme-engine";
707
+ * import { customPresets } from "./custom-presets";
708
+ *
709
+ * type Presets = ThemePresets<typeof customPresets>;
710
+ *
711
+ * function Controls() {
712
+ * const { mode, resolvedMode, setDarkMode, applyThemeById, clearTheme } = useThemeEngine<Presets>();
713
+ *
714
+ * return (
715
+ * <div>
716
+ * <button onClick={() => setDarkMode("system")}>System</button>
717
+ * <button onClick={() => setDarkMode("light")}>Light</button>
718
+ * <button onClick={() => setDarkMode("dark")}>Dark</button>
719
+ *
720
+ * <button onClick={() => applyThemeById("modern-minimal")}>Modern Minimal</button>
721
+ * <button onClick={() => clearTheme()}>Reset</button>
722
+ *
723
+ * <div>mode: {mode} · resolved: {resolvedMode}</div>
724
+ * </div>
725
+ * );
726
+ * }
727
+ *
728
+ * export default function Page() {
729
+ * return (
730
+ * <ThemeProvider customPresets={customPresets} defaultPreset="modern-minimal">
731
+ * <Controls />
732
+ * </ThemeProvider>
733
+ * );
734
+ * }
735
+ * ```
546
736
  */
547
737
  declare function useThemeEngine<const TCustomPresets extends CustomPresetsRecord | undefined = undefined>(): {
548
738
  darkMode: boolean;
@@ -585,7 +775,14 @@ type ColorFormat = 'hsl' | 'rgb' | 'hex';
585
775
  */
586
776
  declare function formatColor(colorInput: string, outputFormat?: ColorFormat, includeFunctionWrapper?: boolean): string;
587
777
  /**
588
- * Create color with alpha transparency
778
+ * Create a color with alpha transparency.
779
+ *
780
+ * Notes:
781
+ * - This helper only supports HSL-like inputs that `parseHSL()` can parse
782
+ * (e.g. `"hsl(210 40% 98%)"` or `"210 40% 98%"`).
783
+ * - For hex/rgb inputs, convert first with `formatColor(color, "hsl")`.
784
+ *
785
+ * @public
589
786
  */
590
787
  declare function withAlpha(colorInput: string, alpha: number): string;
591
788
 
@@ -595,7 +792,12 @@ declare function withAlpha(colorInput: string, alpha: number): string;
595
792
  */
596
793
 
597
794
  /**
598
- * Validation result type
795
+ * Validation result type.
796
+ *
797
+ * - `errors` should be treated as invalid input (preset should be rejected)
798
+ * - `warnings` indicate potentially incomplete presets but may still be usable
799
+ *
800
+ * @public
599
801
  */
600
802
  interface ValidationResult {
601
803
  isValid: boolean;
@@ -603,15 +805,30 @@ interface ValidationResult {
603
805
  warnings: string[];
604
806
  }
605
807
  /**
606
- * Validate a single TweakCN preset
808
+ * Validate a single preset in the TweakCN-compatible shape.
809
+ *
810
+ * Intended usage:
811
+ * - validating user-provided presets before passing them to `ThemeProvider`
812
+ * - debugging preset issues in development
813
+ *
814
+ * Notes:
815
+ * - This is a lightweight validator (it does not fully parse/compute CSS colors)
816
+ *
817
+ * @public
607
818
  */
608
819
  declare function validateTweakCNPreset(preset: any, presetId?: string): ValidationResult;
609
820
  /**
610
- * Validate a collection of custom presets
821
+ * Validate a collection of custom presets (record keyed by preset ID).
822
+ *
823
+ * @public
611
824
  */
612
825
  declare function validateCustomPresets(customPresets: Record<string, TweakCNThemePreset>): ValidationResult;
613
826
  /**
614
- * Helper function to log validation results
827
+ * Convenience logger for `ValidationResult`.
828
+ *
829
+ * This is primarily intended for local development diagnostics.
830
+ *
831
+ * @public
615
832
  */
616
833
  declare function logValidationResult(result: ValidationResult, context?: string): void;
617
834
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fakhrirafiki/theme-engine",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
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",