@fakhrirafiki/theme-engine 0.4.14 → 0.4.17

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
@@ -8,6 +8,9 @@ Theme system for **Next.js (App Router)**: mode (`light | dark | system`) + them
8
8
  ![npm downloads](https://img.shields.io/npm/dm/@fakhrirafiki/theme-engine)
9
9
  ![license](https://img.shields.io/npm/l/@fakhrirafiki/theme-engine)
10
10
 
11
+ Live demo: https://theme-engine-example.vercel.app/
12
+ Example repo: https://github.com/fakhrirafiki/theme-engine-example
13
+
11
14
  ## ✨ Why use this?
12
15
 
13
16
  - ⚡ **Fast setup**: 1 CSS import + 1 provider
@@ -407,7 +410,7 @@ export function ThemePresetSelect({
407
410
 
408
411
  {isActive && (
409
412
  <span className="inline-flex items-center rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-foreground">
410
- Aktif
413
+ Active
411
414
  </span>
412
415
  )}
413
416
  </span>
@@ -415,7 +418,7 @@ export function ThemePresetSelect({
415
418
  );
416
419
  })}
417
420
 
418
- {presets.length === 0 && <p className="text-xs text-muted-foreground">Belum ada tema yang tersedia.</p>}
421
+ {presets.length === 0 && <p className="text-xs text-muted-foreground">No themes available yet.</p>}
419
422
  </div>
420
423
  );
421
424
  }
@@ -503,6 +506,14 @@ Note: the thrown error string might mention `useTheme` because `useThemeEngine()
503
506
 
504
507
  Ensure your `globals.css` imports `@fakhrirafiki/theme-engine/styles` (and Tailwind v4 is configured if you rely on Tailwind utilities).
505
508
 
509
+ ### Turbopack: “module factory is not available” (HMR) after upgrading
510
+
511
+ This is a Next.js Turbopack dev/HMR issue that can happen after updating dependencies in `node_modules` (or when using a locally linked package that rebuilds `dist/` while `next dev` is running).
512
+
513
+ - Restart `next dev` (often enough).
514
+ - If it persists: delete `.next/` and restart.
515
+ - Workaround: run dev server with webpack: `next dev --webpack`
516
+
506
517
  ---
507
518
 
508
519
  ## License
package/dist/index.d.mts CHANGED
@@ -460,16 +460,27 @@ interface ThemeScriptProps {
460
460
  * @default 'theme-preset'
461
461
  */
462
462
  presetStorageKey?: string;
463
+ /**
464
+ * Storage key for appearance mode persistence
465
+ * @default 'theme-engine-theme'
466
+ */
467
+ modeStorageKey?: string;
468
+ /**
469
+ * Default appearance mode when no stored preference exists
470
+ * @default 'system'
471
+ */
472
+ defaultMode?: Mode;
463
473
  /**
464
474
  * Default preset ID to apply when no stored preset exists
465
475
  */
466
476
  defaultPreset?: string;
467
477
  }
468
478
  /**
469
- * Simplified theme script that only handles preset restoration
470
- * Works in harmony with ThemeProvider for dark/light mode
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.
471
482
  */
472
- declare function ThemeScript({ presetStorageKey, defaultPreset }: ThemeScriptProps): react_jsx_runtime.JSX.Element;
483
+ declare function ThemeScript({ presetStorageKey, modeStorageKey, defaultMode, defaultPreset, }: ThemeScriptProps): react_jsx_runtime.JSX.Element;
473
484
 
474
485
  declare const ThemeToggle: react.ForwardRefExoticComponent<ThemeToggleProps & react.RefAttributes<HTMLButtonElement>>;
475
486
 
package/dist/index.d.ts CHANGED
@@ -460,16 +460,27 @@ interface ThemeScriptProps {
460
460
  * @default 'theme-preset'
461
461
  */
462
462
  presetStorageKey?: string;
463
+ /**
464
+ * Storage key for appearance mode persistence
465
+ * @default 'theme-engine-theme'
466
+ */
467
+ modeStorageKey?: string;
468
+ /**
469
+ * Default appearance mode when no stored preference exists
470
+ * @default 'system'
471
+ */
472
+ defaultMode?: Mode;
463
473
  /**
464
474
  * Default preset ID to apply when no stored preset exists
465
475
  */
466
476
  defaultPreset?: string;
467
477
  }
468
478
  /**
469
- * Simplified theme script that only handles preset restoration
470
- * Works in harmony with ThemeProvider for dark/light mode
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.
471
482
  */
472
- declare function ThemeScript({ presetStorageKey, defaultPreset }: ThemeScriptProps): react_jsx_runtime.JSX.Element;
483
+ declare function ThemeScript({ presetStorageKey, modeStorageKey, defaultMode, defaultPreset, }: ThemeScriptProps): react_jsx_runtime.JSX.Element;
473
484
 
474
485
  declare const ThemeToggle: react.ForwardRefExoticComponent<ThemeToggleProps & react.RefAttributes<HTMLButtonElement>>;
475
486
 
package/dist/index.js CHANGED
@@ -3838,7 +3838,12 @@ function logValidationResult(result, context = "Custom presets") {
3838
3838
  // src/components/UnifiedThemeScript.tsx
3839
3839
  var import_react = require("react");
3840
3840
  var import_jsx_runtime = require("react/jsx-runtime");
3841
- function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3841
+ function ThemeScript({
3842
+ presetStorageKey = "theme-preset",
3843
+ modeStorageKey = "theme-engine-theme",
3844
+ defaultMode = "system",
3845
+ defaultPreset
3846
+ }) {
3842
3847
  const defaultPresetData = (0, import_react.useMemo)(() => {
3843
3848
  if (!defaultPreset) return null;
3844
3849
  const preset = getPresetById(defaultPreset);
@@ -3850,10 +3855,12 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3850
3855
  }, [defaultPreset]);
3851
3856
  const scriptContent = (0, import_react.useMemo)(
3852
3857
  () => `
3853
- // Unified Theme Engine: Restore preset colors before hydration
3858
+ // Unified Theme Engine: Restore mode + preset colors before hydration
3854
3859
  (function() {
3855
3860
  try {
3856
3861
  const presetStorageKey = "${presetStorageKey}";
3862
+ const modeStorageKey = "${modeStorageKey}";
3863
+ const defaultMode = "${defaultMode}";
3857
3864
  const isDev = (function() {
3858
3865
  try {
3859
3866
  return location.hostname === 'localhost' || location.hostname === '127.0.0.1';
@@ -3861,6 +3868,39 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3861
3868
  return false;
3862
3869
  }
3863
3870
  })();
3871
+
3872
+ // ---- Mode restoration (pre-hydration) ----
3873
+ (function() {
3874
+ try {
3875
+ const root = document.documentElement;
3876
+ let storedMode = null;
3877
+ try {
3878
+ storedMode = localStorage.getItem(modeStorageKey);
3879
+ } catch {}
3880
+
3881
+ const isValidMode = storedMode === 'light' || storedMode === 'dark' || storedMode === 'system';
3882
+ const mode = isValidMode ? storedMode : defaultMode;
3883
+
3884
+ let systemMode = 'light';
3885
+ try {
3886
+ systemMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
3887
+ } catch {}
3888
+
3889
+ const resolvedMode = mode === 'system' ? systemMode : mode;
3890
+
3891
+ root.classList.remove('light', 'dark');
3892
+ root.classList.add(resolvedMode);
3893
+ root.style.colorScheme = resolvedMode;
3894
+
3895
+ // Expose for runtime consumers (optional)
3896
+ try {
3897
+ root.dataset.themeEngineMode = mode;
3898
+ root.dataset.themeEngineResolvedMode = resolvedMode;
3899
+ } catch {}
3900
+ } catch (error) {
3901
+ if (isDev) console.warn('\u{1F3A8} UnifiedThemeScript: Mode restoration failed:', error);
3902
+ }
3903
+ })();
3864
3904
 
3865
3905
  // CSS property categories (inline for script)
3866
3906
  const CSS_CATEGORIES = {
@@ -4067,7 +4107,7 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
4067
4107
 
4068
4108
  }
4069
4109
 
4070
- // Load and apply persisted preset or default preset
4110
+ // ---- Preset restoration (pre-hydration) ----
4071
4111
  const storedPreset = localStorage.getItem(presetStorageKey);
4072
4112
  let presetToApply = null;
4073
4113
 
@@ -4085,10 +4125,9 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
4085
4125
  }
4086
4126
 
4087
4127
  if (presetToApply) {
4088
- // Determine current mode (will be set by ThemeProvider)
4089
- // Default to light if no theme class is present yet
4090
- const isDark = document.documentElement.classList.contains('dark');
4091
- const mode = isDark ? 'dark' : 'light';
4128
+ const root = document.documentElement;
4129
+ const resolved = (root.dataset && root.dataset.themeEngineResolvedMode) || (root.classList.contains('dark') ? 'dark' : 'light');
4130
+ const mode = resolved === 'dark' ? 'dark' : 'light';
4092
4131
  const colors = presetToApply.colors && presetToApply.colors[mode];
4093
4132
 
4094
4133
  if (colors) {
@@ -4315,15 +4354,17 @@ function ThemeProvider({
4315
4354
  }) {
4316
4355
  const normalizedCustomPresets = (0, import_react2.useMemo)(() => customPresets ?? {}, [customPresets]);
4317
4356
  const [mode, setMode] = (0, import_react2.useState)(() => {
4318
- const stored = getStoredMode(modeStorageKey);
4319
- return stored || defaultMode;
4357
+ return defaultMode;
4320
4358
  });
4321
4359
  const [resolvedMode, setResolvedMode] = (0, import_react2.useState)(() => {
4322
- if (mode === "system") {
4323
- return getSystemTheme();
4324
- }
4325
- return mode;
4360
+ if (defaultMode === "dark") return "dark";
4361
+ return "light";
4326
4362
  });
4363
+ (0, import_react2.useEffect)(() => {
4364
+ if (typeof window === "undefined") return;
4365
+ const stored = getStoredMode(modeStorageKey);
4366
+ if (stored) setMode(stored);
4367
+ }, [modeStorageKey]);
4327
4368
  const availablePresets = (0, import_react2.useMemo)(() => {
4328
4369
  const merged = {};
4329
4370
  Object.assign(merged, tweakcnPresets);
@@ -4591,7 +4632,15 @@ function ThemeProvider({
4591
4632
  });
4592
4633
  }
4593
4634
  }, [presetStorageKey, defaultPreset, getAvailablePresetById, applyPresetColors, resolvedMode]);
4594
- const scriptElement = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ThemeScript, { presetStorageKey, defaultPreset });
4635
+ const scriptElement = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4636
+ ThemeScript,
4637
+ {
4638
+ presetStorageKey,
4639
+ modeStorageKey,
4640
+ defaultMode,
4641
+ defaultPreset
4642
+ }
4643
+ );
4595
4644
  const isUsingDefaultPreset = !!defaultPreset && currentPreset?.presetId === defaultPreset;
4596
4645
  const contextValue = {
4597
4646
  mode,
@@ -4666,29 +4715,10 @@ var MoonIcon = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4666
4715
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" })
4667
4716
  }
4668
4717
  );
4669
- var SystemIcon = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4670
- "svg",
4671
- {
4672
- xmlns: "http://www.w3.org/2000/svg",
4673
- width: "16",
4674
- height: "16",
4675
- viewBox: "0 0 24 24",
4676
- fill: "none",
4677
- stroke: "currentColor",
4678
- strokeWidth: "2",
4679
- strokeLinecap: "round",
4680
- strokeLinejoin: "round",
4681
- className: "theme-toggle-icon",
4682
- children: [
4683
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }),
4684
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "8", x2: "16", y1: "21", y2: "21" }),
4685
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "17", y2: "21" })
4686
- ]
4687
- }
4688
- );
4689
4718
  var ThemeToggle = (0, import_react3.forwardRef)(
4690
4719
  ({ className, size = "md", variant = "default", children, ...props }, ref) => {
4691
4720
  const { mode, resolvedMode, toggleMode } = useTheme();
4721
+ const nextResolvedMode = resolvedMode === "light" ? "dark" : "light";
4692
4722
  const handleClick = (event) => {
4693
4723
  const { clientX: x, clientY: y } = event;
4694
4724
  toggleMode({ x, y });
@@ -4718,16 +4748,7 @@ var ThemeToggle = (0, import_react3.forwardRef)(
4718
4748
  );
4719
4749
  const renderIcon = () => {
4720
4750
  if (children) return children;
4721
- switch (mode) {
4722
- case "light":
4723
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SunIcon, {});
4724
- case "dark":
4725
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoonIcon, {});
4726
- case "system":
4727
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SystemIcon, {});
4728
- default:
4729
- return resolvedMode === "dark" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoonIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SunIcon, {});
4730
- }
4751
+ return nextResolvedMode === "dark" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoonIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SunIcon, {});
4731
4752
  };
4732
4753
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4733
4754
  "button",
@@ -4735,10 +4756,10 @@ var ThemeToggle = (0, import_react3.forwardRef)(
4735
4756
  ref,
4736
4757
  className: baseClasses,
4737
4758
  onClick: handleClick,
4738
- "data-mode": resolvedMode,
4759
+ "data-mode": mode,
4739
4760
  "data-theme": resolvedMode,
4740
- "aria-label": `Switch to ${resolvedMode === "light" ? "dark" : "light"} mode`,
4741
- title: `Switch to ${resolvedMode === "light" ? "dark" : "light"} mode`,
4761
+ "aria-label": `Switch to ${nextResolvedMode} mode`,
4762
+ title: `Switch to ${nextResolvedMode} mode`,
4742
4763
  ...props,
4743
4764
  children: renderIcon()
4744
4765
  }
package/dist/index.mjs CHANGED
@@ -3794,7 +3794,12 @@ function logValidationResult(result, context = "Custom presets") {
3794
3794
  // src/components/UnifiedThemeScript.tsx
3795
3795
  import { useMemo } from "react";
3796
3796
  import { jsx } from "react/jsx-runtime";
3797
- function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3797
+ function ThemeScript({
3798
+ presetStorageKey = "theme-preset",
3799
+ modeStorageKey = "theme-engine-theme",
3800
+ defaultMode = "system",
3801
+ defaultPreset
3802
+ }) {
3798
3803
  const defaultPresetData = useMemo(() => {
3799
3804
  if (!defaultPreset) return null;
3800
3805
  const preset = getPresetById(defaultPreset);
@@ -3806,10 +3811,12 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3806
3811
  }, [defaultPreset]);
3807
3812
  const scriptContent = useMemo(
3808
3813
  () => `
3809
- // Unified Theme Engine: Restore preset colors before hydration
3814
+ // Unified Theme Engine: Restore mode + preset colors before hydration
3810
3815
  (function() {
3811
3816
  try {
3812
3817
  const presetStorageKey = "${presetStorageKey}";
3818
+ const modeStorageKey = "${modeStorageKey}";
3819
+ const defaultMode = "${defaultMode}";
3813
3820
  const isDev = (function() {
3814
3821
  try {
3815
3822
  return location.hostname === 'localhost' || location.hostname === '127.0.0.1';
@@ -3817,6 +3824,39 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3817
3824
  return false;
3818
3825
  }
3819
3826
  })();
3827
+
3828
+ // ---- Mode restoration (pre-hydration) ----
3829
+ (function() {
3830
+ try {
3831
+ const root = document.documentElement;
3832
+ let storedMode = null;
3833
+ try {
3834
+ storedMode = localStorage.getItem(modeStorageKey);
3835
+ } catch {}
3836
+
3837
+ const isValidMode = storedMode === 'light' || storedMode === 'dark' || storedMode === 'system';
3838
+ const mode = isValidMode ? storedMode : defaultMode;
3839
+
3840
+ let systemMode = 'light';
3841
+ try {
3842
+ systemMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
3843
+ } catch {}
3844
+
3845
+ const resolvedMode = mode === 'system' ? systemMode : mode;
3846
+
3847
+ root.classList.remove('light', 'dark');
3848
+ root.classList.add(resolvedMode);
3849
+ root.style.colorScheme = resolvedMode;
3850
+
3851
+ // Expose for runtime consumers (optional)
3852
+ try {
3853
+ root.dataset.themeEngineMode = mode;
3854
+ root.dataset.themeEngineResolvedMode = resolvedMode;
3855
+ } catch {}
3856
+ } catch (error) {
3857
+ if (isDev) console.warn('\u{1F3A8} UnifiedThemeScript: Mode restoration failed:', error);
3858
+ }
3859
+ })();
3820
3860
 
3821
3861
  // CSS property categories (inline for script)
3822
3862
  const CSS_CATEGORIES = {
@@ -4023,7 +4063,7 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
4023
4063
 
4024
4064
  }
4025
4065
 
4026
- // Load and apply persisted preset or default preset
4066
+ // ---- Preset restoration (pre-hydration) ----
4027
4067
  const storedPreset = localStorage.getItem(presetStorageKey);
4028
4068
  let presetToApply = null;
4029
4069
 
@@ -4041,10 +4081,9 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
4041
4081
  }
4042
4082
 
4043
4083
  if (presetToApply) {
4044
- // Determine current mode (will be set by ThemeProvider)
4045
- // Default to light if no theme class is present yet
4046
- const isDark = document.documentElement.classList.contains('dark');
4047
- const mode = isDark ? 'dark' : 'light';
4084
+ const root = document.documentElement;
4085
+ const resolved = (root.dataset && root.dataset.themeEngineResolvedMode) || (root.classList.contains('dark') ? 'dark' : 'light');
4086
+ const mode = resolved === 'dark' ? 'dark' : 'light';
4048
4087
  const colors = presetToApply.colors && presetToApply.colors[mode];
4049
4088
 
4050
4089
  if (colors) {
@@ -4271,15 +4310,17 @@ function ThemeProvider({
4271
4310
  }) {
4272
4311
  const normalizedCustomPresets = useMemo2(() => customPresets ?? {}, [customPresets]);
4273
4312
  const [mode, setMode] = useState(() => {
4274
- const stored = getStoredMode(modeStorageKey);
4275
- return stored || defaultMode;
4313
+ return defaultMode;
4276
4314
  });
4277
4315
  const [resolvedMode, setResolvedMode] = useState(() => {
4278
- if (mode === "system") {
4279
- return getSystemTheme();
4280
- }
4281
- return mode;
4316
+ if (defaultMode === "dark") return "dark";
4317
+ return "light";
4282
4318
  });
4319
+ useEffect(() => {
4320
+ if (typeof window === "undefined") return;
4321
+ const stored = getStoredMode(modeStorageKey);
4322
+ if (stored) setMode(stored);
4323
+ }, [modeStorageKey]);
4283
4324
  const availablePresets = useMemo2(() => {
4284
4325
  const merged = {};
4285
4326
  Object.assign(merged, tweakcnPresets);
@@ -4547,7 +4588,15 @@ function ThemeProvider({
4547
4588
  });
4548
4589
  }
4549
4590
  }, [presetStorageKey, defaultPreset, getAvailablePresetById, applyPresetColors, resolvedMode]);
4550
- const scriptElement = /* @__PURE__ */ jsx2(ThemeScript, { presetStorageKey, defaultPreset });
4591
+ const scriptElement = /* @__PURE__ */ jsx2(
4592
+ ThemeScript,
4593
+ {
4594
+ presetStorageKey,
4595
+ modeStorageKey,
4596
+ defaultMode,
4597
+ defaultPreset
4598
+ }
4599
+ );
4551
4600
  const isUsingDefaultPreset = !!defaultPreset && currentPreset?.presetId === defaultPreset;
4552
4601
  const contextValue = {
4553
4602
  mode,
@@ -4622,29 +4671,10 @@ var MoonIcon = () => /* @__PURE__ */ jsx3(
4622
4671
  children: /* @__PURE__ */ jsx3("path", { d: "m12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" })
4623
4672
  }
4624
4673
  );
4625
- var SystemIcon = () => /* @__PURE__ */ jsxs2(
4626
- "svg",
4627
- {
4628
- xmlns: "http://www.w3.org/2000/svg",
4629
- width: "16",
4630
- height: "16",
4631
- viewBox: "0 0 24 24",
4632
- fill: "none",
4633
- stroke: "currentColor",
4634
- strokeWidth: "2",
4635
- strokeLinecap: "round",
4636
- strokeLinejoin: "round",
4637
- className: "theme-toggle-icon",
4638
- children: [
4639
- /* @__PURE__ */ jsx3("rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }),
4640
- /* @__PURE__ */ jsx3("line", { x1: "8", x2: "16", y1: "21", y2: "21" }),
4641
- /* @__PURE__ */ jsx3("line", { x1: "12", x2: "12", y1: "17", y2: "21" })
4642
- ]
4643
- }
4644
- );
4645
4674
  var ThemeToggle = forwardRef(
4646
4675
  ({ className, size = "md", variant = "default", children, ...props }, ref) => {
4647
4676
  const { mode, resolvedMode, toggleMode } = useTheme();
4677
+ const nextResolvedMode = resolvedMode === "light" ? "dark" : "light";
4648
4678
  const handleClick = (event) => {
4649
4679
  const { clientX: x, clientY: y } = event;
4650
4680
  toggleMode({ x, y });
@@ -4674,16 +4704,7 @@ var ThemeToggle = forwardRef(
4674
4704
  );
4675
4705
  const renderIcon = () => {
4676
4706
  if (children) return children;
4677
- switch (mode) {
4678
- case "light":
4679
- return /* @__PURE__ */ jsx3(SunIcon, {});
4680
- case "dark":
4681
- return /* @__PURE__ */ jsx3(MoonIcon, {});
4682
- case "system":
4683
- return /* @__PURE__ */ jsx3(SystemIcon, {});
4684
- default:
4685
- return resolvedMode === "dark" ? /* @__PURE__ */ jsx3(MoonIcon, {}) : /* @__PURE__ */ jsx3(SunIcon, {});
4686
- }
4707
+ return nextResolvedMode === "dark" ? /* @__PURE__ */ jsx3(MoonIcon, {}) : /* @__PURE__ */ jsx3(SunIcon, {});
4687
4708
  };
4688
4709
  return /* @__PURE__ */ jsx3(
4689
4710
  "button",
@@ -4691,10 +4712,10 @@ var ThemeToggle = forwardRef(
4691
4712
  ref,
4692
4713
  className: baseClasses,
4693
4714
  onClick: handleClick,
4694
- "data-mode": resolvedMode,
4715
+ "data-mode": mode,
4695
4716
  "data-theme": resolvedMode,
4696
- "aria-label": `Switch to ${resolvedMode === "light" ? "dark" : "light"} mode`,
4697
- title: `Switch to ${resolvedMode === "light" ? "dark" : "light"} mode`,
4717
+ "aria-label": `Switch to ${nextResolvedMode} mode`,
4718
+ title: `Switch to ${nextResolvedMode} mode`,
4698
4719
  ...props,
4699
4720
  children: renderIcon()
4700
4721
  }
@@ -26,16 +26,17 @@
26
26
 
27
27
  /* Icon transition for theme toggle */
28
28
  .theme-toggle-icon {
29
+ display: block;
29
30
  transition: all 0.3s ease-in-out;
30
31
  color: hsl(var(--foreground));
31
32
  }
32
33
 
33
34
  .theme-toggle[data-theme="light"] .theme-toggle-icon {
34
- rotate: 0deg;
35
+ rotate: 180deg;
35
36
  }
36
37
 
37
38
  .theme-toggle[data-theme="dark"] .theme-toggle-icon {
38
- rotate: 180deg;
39
+ rotate: 0deg;
39
40
  }
40
41
 
41
42
  /* ThemePresetButtons: lightweight CSS-based animations and hover affordances */
@@ -59,10 +60,7 @@
59
60
  box-shadow: 0 1px 0 hsl(var(--border) / 0.35);
60
61
  cursor: pointer;
61
62
 
62
- transition:
63
- transform 0.2s ease-out,
64
- background-color 0.2s ease-out,
65
- border-color 0.2s ease-out,
63
+ transition: transform 0.2s ease-out, background-color 0.2s ease-out, border-color 0.2s ease-out,
66
64
  box-shadow 0.2s ease-out;
67
65
  }
68
66
 
@@ -111,9 +109,7 @@
111
109
  z-index: 20;
112
110
  background: hsl(var(--card) / 0.95);
113
111
  border-color: hsl(var(--primary) / 0.2);
114
- box-shadow:
115
- 0 10px 25px hsl(0 0% 0% / 0.12),
116
- 0 0 0 1px hsl(var(--border) / 0.35);
112
+ box-shadow: 0 10px 25px hsl(0 0% 0% / 0.12), 0 0 0 1px hsl(var(--border) / 0.35);
117
113
  }
118
114
 
119
115
  /* Color boxes within preset buttons - fallback only */
@@ -192,7 +188,7 @@
192
188
  .theme-preset-button {
193
189
  min-width: 120px;
194
190
  }
195
-
191
+
196
192
  .color-palette-grid {
197
193
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
198
194
  }
@@ -204,7 +200,7 @@
204
200
  .theme-preset-button {
205
201
  border-width: 2px;
206
202
  }
207
-
203
+
208
204
  .theme-color-box {
209
205
  border-width: 2px;
210
206
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fakhrirafiki/theme-engine",
3
- "version": "0.4.14",
3
+ "version": "0.4.17",
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",
@@ -37,7 +37,7 @@
37
37
  "type": "git",
38
38
  "url": "git+https://github.com/fakhrirafiki/theme-engine.git"
39
39
  },
40
- "homepage": "https://github.com/fakhrirafiki/theme-engine",
40
+ "homepage": "https://theme-engine-example.vercel.app/",
41
41
  "bugs": {
42
42
  "url": "https://github.com/fakhrirafiki/theme-engine/issues"
43
43
  },