@fakhrirafiki/theme-engine 0.2.6 → 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/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  ThemeProvider: () => ThemeProvider,
26
26
  ThemeScript: () => ThemeScript,
27
27
  ThemeToggle: () => ThemeToggle,
28
+ builtInPresetIds: () => builtInPresetIds,
28
29
  formatColor: () => formatColor,
29
30
  getPresetById: () => getPresetById,
30
31
  getPresetEntries: () => getPresetEntries,
@@ -35,6 +36,7 @@ __export(index_exports, {
35
36
  searchPresets: () => searchPresets,
36
37
  tweakcnPresets: () => tweakcnPresets,
37
38
  useTheme: () => useTheme,
39
+ useTypedTheme: () => useTypedTheme,
38
40
  validateCustomPresets: () => validateCustomPresets,
39
41
  validateTweakCNPreset: () => validateTweakCNPreset,
40
42
  withAlpha: () => withAlpha
@@ -42,7 +44,7 @@ __export(index_exports, {
42
44
  module.exports = __toCommonJS(index_exports);
43
45
 
44
46
  // src/providers/UnifiedThemeProvider.tsx
45
- var import_react = require("react");
47
+ var import_react2 = require("react");
46
48
 
47
49
  // src/types/presets.ts
48
50
  var CSS_PROPERTY_CATEGORIES = {
@@ -124,6 +126,34 @@ var ALL_CSS_PROPERTIES = [
124
126
  ...CSS_PROPERTY_CATEGORIES.spacing
125
127
  ];
126
128
 
129
+ // src/data/built-in-preset-ids.ts
130
+ var builtInPresetIds = [
131
+ "amber-minimal",
132
+ "amethyst-haze",
133
+ "bold-tech",
134
+ "clean-slate",
135
+ "cosmic-night",
136
+ "doom-64",
137
+ "elegant-luxury",
138
+ "kodama-grove",
139
+ "midnight-bloom",
140
+ "mocha-mousse",
141
+ "modern-minimal",
142
+ "neo-brutalism",
143
+ "northern-lights",
144
+ "ocean-breeze",
145
+ "pastel-dreams",
146
+ "quantum-rose",
147
+ "retro-arcade",
148
+ "soft-pop",
149
+ "solar-dusk",
150
+ "starry-night",
151
+ "sunset-horizon",
152
+ "t3-chat",
153
+ "vintage-paper",
154
+ "violet-bloom"
155
+ ];
156
+
127
157
  // src/data/tweakcn-presets.ts
128
158
  var DEFAULT_ACCENT_COLORS = {
129
159
  DARK: {
@@ -3637,7 +3667,7 @@ var tweakcnPresets = {
3637
3667
  }
3638
3668
  };
3639
3669
  function getPresetIds() {
3640
- return Object.keys(tweakcnPresets);
3670
+ return [...builtInPresetIds];
3641
3671
  }
3642
3672
  function getPresetById(id) {
3643
3673
  return tweakcnPresets[id] || null;
@@ -3647,13 +3677,15 @@ function getPresetLabels() {
3647
3677
  }
3648
3678
  function searchPresets(query) {
3649
3679
  const lowerQuery = query.toLowerCase();
3650
- return Object.entries(tweakcnPresets).filter(([id, preset]) => preset.label.toLowerCase().includes(lowerQuery) || id.toLowerCase().includes(lowerQuery)).map(([id, preset]) => ({ id, preset }));
3680
+ return builtInPresetIds.map((id) => ({ id, preset: tweakcnPresets[id] })).filter(
3681
+ ({ id, preset }) => preset.label.toLowerCase().includes(lowerQuery) || id.toLowerCase().includes(lowerQuery)
3682
+ );
3651
3683
  }
3652
3684
  function getPresetsCount() {
3653
- return Object.keys(tweakcnPresets).length;
3685
+ return builtInPresetIds.length;
3654
3686
  }
3655
3687
  function getPresetEntries() {
3656
- return Object.entries(tweakcnPresets);
3688
+ return builtInPresetIds.map((id) => [id, tweakcnPresets[id]]);
3657
3689
  }
3658
3690
 
3659
3691
  // src/utils/preset-validation.ts
@@ -3802,9 +3834,154 @@ function logValidationResult(result, context = "Custom presets") {
3802
3834
  }
3803
3835
  }
3804
3836
 
3805
- // src/providers/UnifiedThemeProvider.tsx
3837
+ // src/components/UnifiedThemeScript.tsx
3838
+ var import_react = require("react");
3806
3839
  var import_jsx_runtime = require("react/jsx-runtime");
3807
- var UnifiedThemeContext = (0, import_react.createContext)(void 0);
3840
+ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3841
+ const defaultPresetData = (0, import_react.useMemo)(() => {
3842
+ if (!defaultPreset) return null;
3843
+ const preset = getPresetById(defaultPreset);
3844
+ return preset ? {
3845
+ presetId: defaultPreset,
3846
+ presetName: preset.label,
3847
+ colors: preset.styles
3848
+ } : null;
3849
+ }, [defaultPreset]);
3850
+ const scriptContent = (0, import_react.useMemo)(
3851
+ () => `
3852
+ // Unified Theme Engine: Restore preset colors before hydration
3853
+ (function() {
3854
+ try {
3855
+ const presetStorageKey = "${presetStorageKey}";
3856
+ const isDev = (function() {
3857
+ try {
3858
+ return location.hostname === 'localhost' || location.hostname === '127.0.0.1';
3859
+ } catch {
3860
+ return false;
3861
+ }
3862
+ })();
3863
+
3864
+ // CSS property categories (inline for script)
3865
+ const CSS_CATEGORIES = {
3866
+ colors: [
3867
+ 'background', 'foreground', 'card', 'card-foreground', 'popover', 'popover-foreground',
3868
+ 'primary', 'primary-foreground', 'secondary', 'secondary-foreground',
3869
+ 'muted', 'muted-foreground', 'accent', 'accent-foreground',
3870
+ 'destructive', 'destructive-foreground', 'border', 'input', 'ring',
3871
+ 'chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5',
3872
+ 'sidebar', 'sidebar-foreground', 'sidebar-primary', 'sidebar-primary-foreground',
3873
+ 'sidebar-accent', 'sidebar-accent-foreground', 'sidebar-border', 'sidebar-ring'
3874
+ ],
3875
+ typography: ['font-sans', 'font-serif', 'font-mono'],
3876
+ layout: ['radius'],
3877
+ shadows: ['shadow-color', 'shadow-opacity', 'shadow-blur', 'shadow-spread', 'shadow-offset-x', 'shadow-offset-y'],
3878
+ spacing: ['letter-spacing', 'spacing']
3879
+ };
3880
+
3881
+ // Function to apply all preset properties - with proper clearing and defaults
3882
+ function applyPresetProperties(colors) {
3883
+ if (!colors) return;
3884
+
3885
+ const root = document.documentElement;
3886
+
3887
+ // Default values for essential properties that might be missing
3888
+ const defaultValues = {
3889
+ 'spacing': '0.25rem',
3890
+ 'letter-spacing': 'normal'
3891
+ };
3892
+
3893
+ // Get all possible CSS properties
3894
+ const allCategories = ['colors', 'typography', 'layout', 'shadows', 'spacing'];
3895
+ const allProperties = [];
3896
+ allCategories.forEach(function(category) {
3897
+ CSS_CATEGORIES[category].forEach(function(prop) {
3898
+ allProperties.push(prop);
3899
+ });
3900
+ });
3901
+
3902
+ // First, clear all properties to prevent leftover values
3903
+ let clearedCount = 0;
3904
+ allProperties.forEach(function(prop) {
3905
+ const cssVar = '--' + prop;
3906
+ root.style.removeProperty(cssVar);
3907
+ clearedCount++;
3908
+ });
3909
+
3910
+ // Apply all properties with defaults for missing ones
3911
+ let appliedCount = 0;
3912
+ allProperties.forEach(function(prop) {
3913
+ let value = colors[prop];
3914
+
3915
+ // Apply default value if property is missing and we have a default
3916
+ if (!value && defaultValues[prop]) {
3917
+ value = defaultValues[prop];
3918
+ }
3919
+
3920
+ if (value) {
3921
+ const cssVar = '--' + prop;
3922
+ // Apply directly like TweakCN does - no conversion, no !important
3923
+ root.style.setProperty(cssVar, value);
3924
+ appliedCount++;
3925
+ }
3926
+ });
3927
+
3928
+ }
3929
+
3930
+ // Load and apply persisted preset or default preset
3931
+ const storedPreset = localStorage.getItem(presetStorageKey);
3932
+ let presetToApply = null;
3933
+
3934
+ if (storedPreset) {
3935
+ try {
3936
+ presetToApply = JSON.parse(storedPreset);
3937
+ } catch (error) {
3938
+ if (isDev) console.warn('\u{1F3A8} UnifiedThemeScript: Failed to parse stored preset:', error);
3939
+ }
3940
+ }
3941
+
3942
+ // Use default preset if no stored preset
3943
+ if (!presetToApply && ${JSON.stringify(defaultPresetData)}) {
3944
+ presetToApply = ${JSON.stringify(defaultPresetData)};
3945
+ }
3946
+
3947
+ if (presetToApply) {
3948
+ // Determine current mode (will be set by ThemeProvider)
3949
+ // Default to light if no theme class is present yet
3950
+ const isDark = document.documentElement.classList.contains('dark');
3951
+ const mode = isDark ? 'dark' : 'light';
3952
+ const colors = presetToApply.colors && presetToApply.colors[mode];
3953
+
3954
+ if (colors) {
3955
+ // Font inheritance logic: inherit missing fonts from other mode
3956
+ const fontProperties = ['font-sans', 'font-serif', 'font-mono'];
3957
+ const otherMode = mode === 'light' ? 'dark' : 'light';
3958
+ const otherModeColors = presetToApply.colors && presetToApply.colors[otherMode];
3959
+
3960
+ if (otherModeColors) {
3961
+ fontProperties.forEach(function(fontProp) {
3962
+ if (!colors[fontProp] && otherModeColors[fontProp]) {
3963
+ colors[fontProp] = otherModeColors[fontProp];
3964
+ }
3965
+ });
3966
+ }
3967
+
3968
+ applyPresetProperties(colors);
3969
+ }
3970
+ }
3971
+
3972
+ } catch (error) {
3973
+ if (isDev) console.error('\u{1F3A8} UnifiedThemeScript: Initialization failed:', error);
3974
+ }
3975
+ })();
3976
+ `,
3977
+ [presetStorageKey, defaultPresetData]
3978
+ );
3979
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { dangerouslySetInnerHTML: { __html: scriptContent }, suppressHydrationWarning: true });
3980
+ }
3981
+
3982
+ // src/providers/UnifiedThemeProvider.tsx
3983
+ var import_jsx_runtime2 = require("react/jsx-runtime");
3984
+ var UnifiedThemeContext = (0, import_react2.createContext)(void 0);
3808
3985
  var THEME_STORAGE_KEY = "theme-engine-theme";
3809
3986
  function getSystemTheme() {
3810
3987
  if (typeof window === "undefined") return "light";
@@ -3832,32 +4009,37 @@ function ThemeProvider({
3832
4009
  modeStorageKey = THEME_STORAGE_KEY,
3833
4010
  presetStorageKey = "theme-preset",
3834
4011
  enablePresets = true,
3835
- customPresets = {},
3836
- includeBuiltInPresets = true
4012
+ customPresets,
4013
+ includeBuiltInPresets = true,
4014
+ disableScript = false,
4015
+ deferRenderUntilReady
3837
4016
  }) {
3838
- const [mode, setMode] = (0, import_react.useState)(() => {
4017
+ const shouldInjectScript = !disableScript;
4018
+ const shouldDeferRenderUntilReady = deferRenderUntilReady ?? disableScript;
4019
+ const normalizedCustomPresets = (0, import_react2.useMemo)(() => customPresets ?? {}, [customPresets]);
4020
+ const [mode, setMode] = (0, import_react2.useState)(() => {
3839
4021
  const stored = getStoredMode(modeStorageKey);
3840
4022
  return stored || defaultMode;
3841
4023
  });
3842
- const [resolvedMode, setResolvedMode] = (0, import_react.useState)(() => {
4024
+ const [resolvedMode, setResolvedMode] = (0, import_react2.useState)(() => {
3843
4025
  if (mode === "system") {
3844
4026
  return getSystemTheme();
3845
4027
  }
3846
4028
  return mode;
3847
4029
  });
3848
- const availablePresets = (0, import_react.useMemo)(() => {
4030
+ const availablePresets = (0, import_react2.useMemo)(() => {
3849
4031
  const merged = {};
3850
4032
  if (includeBuiltInPresets) {
3851
4033
  Object.assign(merged, tweakcnPresets);
3852
4034
  }
3853
- if (customPresets && Object.keys(customPresets).length > 0) {
3854
- const validationResult = validateCustomPresets(customPresets);
4035
+ if (Object.keys(normalizedCustomPresets).length > 0) {
4036
+ const validationResult = validateCustomPresets(normalizedCustomPresets);
3855
4037
  const isDevelopment = typeof window !== "undefined" && window.location?.hostname === "localhost";
3856
4038
  if (isDevelopment) {
3857
4039
  logValidationResult(validationResult, "Custom presets");
3858
4040
  }
3859
4041
  if (validationResult.isValid || validationResult.errors.length === 0) {
3860
- Object.assign(merged, customPresets);
4042
+ Object.assign(merged, normalizedCustomPresets);
3861
4043
  } else {
3862
4044
  if (isDevelopment) {
3863
4045
  console.error("\u{1F3A8} ThemeProvider: Skipping invalid custom presets");
@@ -3865,18 +4047,17 @@ function ThemeProvider({
3865
4047
  }
3866
4048
  }
3867
4049
  return merged;
3868
- }, [includeBuiltInPresets, customPresets]);
3869
- const builtInPresets = (0, import_react.useMemo)(() => includeBuiltInPresets ? tweakcnPresets : {}, [includeBuiltInPresets]);
3870
- const normalizedCustomPresets = (0, import_react.useMemo)(() => customPresets || {}, [customPresets]);
3871
- const getAvailablePresetById = (0, import_react.useCallback)(
4050
+ }, [includeBuiltInPresets, normalizedCustomPresets]);
4051
+ const builtInPresets = (0, import_react2.useMemo)(() => includeBuiltInPresets ? tweakcnPresets : {}, [includeBuiltInPresets]);
4052
+ const getAvailablePresetById = (0, import_react2.useCallback)(
3872
4053
  (id) => {
3873
4054
  return availablePresets[id] || null;
3874
4055
  },
3875
4056
  [availablePresets]
3876
4057
  );
3877
- const [currentPreset, setCurrentPreset] = (0, import_react.useState)(null);
3878
- const [isReady, setIsReady] = (0, import_react.useState)(false);
3879
- (0, import_react.useEffect)(() => {
4058
+ const [currentPreset, setCurrentPreset] = (0, import_react2.useState)(null);
4059
+ const [isReady, setIsReady] = (0, import_react2.useState)(() => !shouldDeferRenderUntilReady);
4060
+ (0, import_react2.useEffect)(() => {
3880
4061
  if (!enablePresets || typeof window === "undefined") {
3881
4062
  setIsReady(true);
3882
4063
  return;
@@ -3937,7 +4118,7 @@ function ThemeProvider({
3937
4118
  setIsReady(true);
3938
4119
  }
3939
4120
  }, [presetStorageKey, enablePresets, defaultPreset, getAvailablePresetById]);
3940
- (0, import_react.useEffect)(() => {
4121
+ (0, import_react2.useEffect)(() => {
3941
4122
  if (mode === "system") {
3942
4123
  const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
3943
4124
  const updateResolvedMode = () => setResolvedMode(getSystemTheme());
@@ -3949,14 +4130,14 @@ function ThemeProvider({
3949
4130
  return;
3950
4131
  }
3951
4132
  }, [mode]);
3952
- (0, import_react.useEffect)(() => {
4133
+ (0, import_react2.useEffect)(() => {
3953
4134
  if (typeof window === "undefined") return;
3954
4135
  const root = document.documentElement;
3955
4136
  root.classList.remove("light", "dark");
3956
4137
  root.classList.add(resolvedMode);
3957
4138
  root.style.colorScheme = resolvedMode;
3958
4139
  }, [resolvedMode]);
3959
- const applyPresetColors = (0, import_react.useCallback)((preset, mode2) => {
4140
+ const applyPresetColors = (0, import_react2.useCallback)((preset, mode2) => {
3960
4141
  if (!preset) return;
3961
4142
  const root = document.documentElement;
3962
4143
  const colors = preset[mode2];
@@ -4001,14 +4182,14 @@ function ThemeProvider({
4001
4182
  }
4002
4183
  });
4003
4184
  }, []);
4004
- const handleModeChange = (0, import_react.useCallback)(
4185
+ const handleModeChange = (0, import_react2.useCallback)(
4005
4186
  (newMode) => {
4006
4187
  setMode(newMode);
4007
4188
  setStoredMode(newMode, modeStorageKey);
4008
4189
  },
4009
4190
  [modeStorageKey]
4010
4191
  );
4011
- const handleModeToggle = (0, import_react.useCallback)(
4192
+ const handleModeToggle = (0, import_react2.useCallback)(
4012
4193
  (coordinates) => {
4013
4194
  const newMode = resolvedMode === "light" ? "dark" : "light";
4014
4195
  const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
@@ -4027,11 +4208,11 @@ function ThemeProvider({
4027
4208
  },
4028
4209
  [resolvedMode, enableTransitions, handleModeChange]
4029
4210
  );
4030
- (0, import_react.useEffect)(() => {
4211
+ (0, import_react2.useEffect)(() => {
4031
4212
  if (!currentPreset || typeof window === "undefined") return;
4032
4213
  applyPresetColors(currentPreset.colors, resolvedMode);
4033
4214
  }, [currentPreset, resolvedMode, applyPresetColors]);
4034
- const applyPreset = (0, import_react.useCallback)(
4215
+ const applyPreset = (0, import_react2.useCallback)(
4035
4216
  (preset) => {
4036
4217
  const presetData = {
4037
4218
  presetId: preset.id,
@@ -4053,7 +4234,7 @@ function ThemeProvider({
4053
4234
  },
4054
4235
  [presetStorageKey, enablePresets, applyPresetColors, resolvedMode]
4055
4236
  );
4056
- const setThemePresetById = (0, import_react.useCallback)(
4237
+ const setThemePresetById = (0, import_react2.useCallback)(
4057
4238
  (presetId) => {
4058
4239
  const preset = getAvailablePresetById(presetId);
4059
4240
  if (!preset) {
@@ -4074,7 +4255,7 @@ function ThemeProvider({
4074
4255
  },
4075
4256
  [getAvailablePresetById, applyPreset]
4076
4257
  );
4077
- const clearPreset = (0, import_react.useCallback)(() => {
4258
+ const clearPreset = (0, import_react2.useCallback)(() => {
4078
4259
  if (enablePresets && typeof window !== "undefined") {
4079
4260
  try {
4080
4261
  localStorage.removeItem(presetStorageKey);
@@ -4118,8 +4299,9 @@ function ThemeProvider({
4118
4299
  }
4119
4300
  }
4120
4301
  }, [presetStorageKey, enablePresets, defaultPreset, applyPresetColors, resolvedMode]);
4121
- if (!isReady) {
4122
- return null;
4302
+ const scriptElement = shouldInjectScript ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ThemeScript, { presetStorageKey, defaultPreset }) : null;
4303
+ if (!isReady && shouldDeferRenderUntilReady) {
4304
+ return scriptElement;
4123
4305
  }
4124
4306
  const isUsingDefaultPreset = !!defaultPreset && currentPreset?.presetId === defaultPreset;
4125
4307
  const contextValue = {
@@ -4136,168 +4318,19 @@ function ThemeProvider({
4136
4318
  builtInPresets,
4137
4319
  customPresets: normalizedCustomPresets
4138
4320
  };
4139
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UnifiedThemeContext.Provider, { value: contextValue, children });
4321
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
4322
+ scriptElement,
4323
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(UnifiedThemeContext.Provider, { value: contextValue, children })
4324
+ ] });
4140
4325
  }
4141
4326
  function useTheme() {
4142
- const context = (0, import_react.useContext)(UnifiedThemeContext);
4327
+ const context = (0, import_react2.useContext)(UnifiedThemeContext);
4143
4328
  if (context === void 0) {
4144
4329
  throw new Error("useTheme must be used within a ThemeProvider");
4145
4330
  }
4146
4331
  return context;
4147
4332
  }
4148
4333
 
4149
- // src/components/UnifiedThemeScript.tsx
4150
- var import_react2 = require("react");
4151
- var import_jsx_runtime2 = require("react/jsx-runtime");
4152
- function ThemeScript({
4153
- presetStorageKey = "theme-preset",
4154
- defaultPreset
4155
- }) {
4156
- const defaultPresetData = (0, import_react2.useMemo)(() => {
4157
- if (!defaultPreset) return null;
4158
- const preset = getPresetById(defaultPreset);
4159
- return preset ? {
4160
- presetId: defaultPreset,
4161
- presetName: preset.label,
4162
- colors: preset.styles
4163
- } : null;
4164
- }, [defaultPreset]);
4165
- const scriptContent = (0, import_react2.useMemo)(() => `
4166
- // Unified Theme Engine: Restore preset colors before hydration
4167
- (function() {
4168
- try {
4169
- const presetStorageKey = "${presetStorageKey}";
4170
-
4171
- // CSS property categories (inline for script)
4172
- const CSS_CATEGORIES = {
4173
- colors: [
4174
- 'background', 'foreground', 'card', 'card-foreground', 'popover', 'popover-foreground',
4175
- 'primary', 'primary-foreground', 'secondary', 'secondary-foreground',
4176
- 'muted', 'muted-foreground', 'accent', 'accent-foreground',
4177
- 'destructive', 'destructive-foreground', 'border', 'input', 'ring',
4178
- 'chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5',
4179
- 'sidebar', 'sidebar-foreground', 'sidebar-primary', 'sidebar-primary-foreground',
4180
- 'sidebar-accent', 'sidebar-accent-foreground', 'sidebar-border', 'sidebar-ring'
4181
- ],
4182
- typography: ['font-sans', 'font-serif', 'font-mono'],
4183
- layout: ['radius'],
4184
- shadows: ['shadow-color', 'shadow-opacity', 'shadow-blur', 'shadow-spread', 'shadow-offset-x', 'shadow-offset-y'],
4185
- spacing: ['letter-spacing', 'spacing']
4186
- };
4187
-
4188
- // Function to apply all preset properties - with proper clearing and defaults
4189
- function applyPresetProperties(colors) {
4190
- if (!colors) return;
4191
-
4192
- const root = document.documentElement;
4193
-
4194
- // Default values for essential properties that might be missing
4195
- const defaultValues = {
4196
- 'spacing': '0.25rem',
4197
- 'letter-spacing': 'normal'
4198
- };
4199
-
4200
- // Get all possible CSS properties
4201
- const allCategories = ['colors', 'typography', 'layout', 'shadows', 'spacing'];
4202
- const allProperties = [];
4203
- allCategories.forEach(function(category) {
4204
- CSS_CATEGORIES[category].forEach(function(prop) {
4205
- allProperties.push(prop);
4206
- });
4207
- });
4208
-
4209
- // First, clear all properties to prevent leftover values
4210
- let clearedCount = 0;
4211
- allProperties.forEach(function(prop) {
4212
- const cssVar = '--' + prop;
4213
- root.style.removeProperty(cssVar);
4214
- clearedCount++;
4215
- });
4216
- console.log('\u{1F3A8} UnifiedThemeScript: Cleared ' + clearedCount + ' CSS properties before applying new theme');
4217
-
4218
- // Apply all properties with defaults for missing ones
4219
- let appliedCount = 0;
4220
- allProperties.forEach(function(prop) {
4221
- let value = colors[prop];
4222
-
4223
- // Apply default value if property is missing and we have a default
4224
- if (!value && defaultValues[prop]) {
4225
- value = defaultValues[prop];
4226
- console.log('\u{1F3A8} Script using default value for ' + prop + ': ' + value);
4227
- }
4228
-
4229
- if (value) {
4230
- const cssVar = '--' + prop;
4231
- // Apply directly like TweakCN does - no conversion, no !important
4232
- root.style.setProperty(cssVar, value);
4233
- appliedCount++;
4234
- console.log('\u{1F3A8} Script applied ' + cssVar + ': ' + value);
4235
- }
4236
- });
4237
-
4238
- console.log('\u{1F3A8} UnifiedThemeScript: Applied ' + appliedCount + ' CSS properties');
4239
- }
4240
-
4241
- // Load and apply persisted preset or default preset
4242
- const storedPreset = localStorage.getItem(presetStorageKey);
4243
- let presetToApply = null;
4244
-
4245
- if (storedPreset) {
4246
- try {
4247
- presetToApply = JSON.parse(storedPreset);
4248
- console.log('\u{1F3A8} UnifiedThemeScript: Using stored preset:', presetToApply.presetName);
4249
- } catch (error) {
4250
- console.warn('\u{1F3A8} UnifiedThemeScript: Failed to parse stored preset:', error);
4251
- }
4252
- }
4253
-
4254
- // Use default preset if no stored preset
4255
- if (!presetToApply && ${JSON.stringify(defaultPresetData)}) {
4256
- presetToApply = ${JSON.stringify(defaultPresetData)};
4257
- console.log('\u{1F3A8} UnifiedThemeScript: Using default preset:', presetToApply.presetName);
4258
- }
4259
-
4260
- if (presetToApply) {
4261
- // Determine current mode (will be set by ThemeProvider)
4262
- // Default to light if no theme class is present yet
4263
- const isDark = document.documentElement.classList.contains('dark');
4264
- const mode = isDark ? 'dark' : 'light';
4265
- const colors = presetToApply.colors && presetToApply.colors[mode];
4266
-
4267
- if (colors) {
4268
- // Font inheritance logic: inherit missing fonts from other mode
4269
- const fontProperties = ['font-sans', 'font-serif', 'font-mono'];
4270
- const otherMode = mode === 'light' ? 'dark' : 'light';
4271
- const otherModeColors = presetToApply.colors && presetToApply.colors[otherMode];
4272
-
4273
- if (otherModeColors) {
4274
- fontProperties.forEach(function(fontProp) {
4275
- if (!colors[fontProp] && otherModeColors[fontProp]) {
4276
- colors[fontProp] = otherModeColors[fontProp];
4277
- console.log('\u{1F3A8} Script inherited ' + fontProp + ': "' + otherModeColors[fontProp] + '" from ' + otherMode + ' mode');
4278
- }
4279
- });
4280
- }
4281
-
4282
- applyPresetProperties(colors);
4283
- console.log('\u{1F3A8} UnifiedThemeScript: Applied preset properties for', mode, 'mode');
4284
- }
4285
- }
4286
-
4287
- } catch (error) {
4288
- console.error('\u{1F3A8} UnifiedThemeScript: Initialization failed:', error);
4289
- }
4290
- })();
4291
- `, [presetStorageKey, defaultPresetData]);
4292
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4293
- "script",
4294
- {
4295
- dangerouslySetInnerHTML: { __html: scriptContent },
4296
- suppressHydrationWarning: true
4297
- }
4298
- );
4299
- }
4300
-
4301
4334
  // src/components/ThemeToggle.tsx
4302
4335
  var import_react3 = require("react");
4303
4336
  var import_clsx = require("clsx");
@@ -4414,6 +4447,7 @@ var ThemeToggle = (0, import_react3.forwardRef)(
4414
4447
  className: baseClasses,
4415
4448
  onClick: handleClick,
4416
4449
  "data-mode": resolvedMode,
4450
+ "data-theme": resolvedMode,
4417
4451
  "aria-label": `Switch to ${resolvedMode === "light" ? "dark" : "light"} mode`,
4418
4452
  title: `Switch to ${resolvedMode === "light" ? "dark" : "light"} mode`,
4419
4453
  ...props,
@@ -4426,7 +4460,6 @@ ThemeToggle.displayName = "ThemeToggle";
4426
4460
 
4427
4461
  // src/components/ThemePresetButtons.tsx
4428
4462
  var import_react4 = require("react");
4429
- var import_framer_motion = require("framer-motion");
4430
4463
  var import_clsx2 = require("clsx");
4431
4464
 
4432
4465
  // src/utils/colors.ts
@@ -4614,33 +4647,25 @@ var PresetButton = ({
4614
4647
  }
4615
4648
  );
4616
4649
  }
4617
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4618
- import_framer_motion.motion.div,
4650
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-shrink-0", style: { minWidth: layout.buttonWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4651
+ "div",
4619
4652
  {
4620
- className: "flex-shrink-0",
4621
- style: { minWidth: layout.buttonWidth },
4622
- whileHover: { scale: 1.02, y: -3, zIndex: 20 },
4623
- transition: { duration: 0.2, ease: "easeOut" },
4624
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4625
- "div",
4626
- {
4627
- className: (0, import_clsx2.clsx)(
4628
- "cursor-pointer bg-card hover:bg-card/80 border border-border rounded-lg",
4629
- "flex w-full h-full items-center justify-center relative transition-all duration-200",
4630
- "hover:shadow-lg hover:border-primary/20 px-4 py-3",
4631
- isSelected ? "ring-2 ring-primary/50 shadow-md bg-primary/5" : ""
4632
- ),
4633
- onClick,
4634
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2.5 text-center", children: [
4635
- layout.showColorBoxes && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex gap-1", children: [colors.primary, colors.secondary, colors.accent].slice(0, layout.colorBoxCount).map(
4636
- (color, index) => renderColorBox ? renderColorBox(color, index) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorBox, { color }, index)
4637
- ) }),
4638
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "capitalize px-1 leading-tight text-sm font-medium text-foreground", children: preset.name.replace(/-/g, " ") })
4639
- ] })
4640
- }
4641
- )
4653
+ className: (0, import_clsx2.clsx)(
4654
+ "theme-preset-button",
4655
+ "cursor-pointer bg-card hover:bg-card/80 border border-border rounded-lg",
4656
+ "flex w-full h-full items-center justify-center relative transition-all duration-200",
4657
+ "hover:shadow-lg hover:border-primary/20 px-4 py-3",
4658
+ isSelected ? "ring-2 ring-primary/50 shadow-md bg-primary/5" : ""
4659
+ ),
4660
+ onClick,
4661
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2.5 text-center", children: [
4662
+ layout.showColorBoxes && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex gap-1", children: [colors.primary, colors.secondary, colors.accent].slice(0, layout.colorBoxCount).map(
4663
+ (color, index) => renderColorBox ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: renderColorBox(color, index) }, index) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorBox, { color }, index)
4664
+ ) }),
4665
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "capitalize px-1 leading-tight text-sm font-medium text-foreground", children: preset.name.replace(/-/g, " ") })
4666
+ ] })
4642
4667
  }
4643
- );
4668
+ ) });
4644
4669
  };
4645
4670
  var AnimatedRow = ({
4646
4671
  presets,
@@ -4652,36 +4677,28 @@ var AnimatedRow = ({
4652
4677
  renderPreset,
4653
4678
  renderColorBox
4654
4679
  }) => {
4655
- const [scope, animate] = (0, import_framer_motion.useAnimate)();
4656
- const controls = (0, import_react4.useRef)(null);
4657
4680
  if (presets.length === 0) return null;
4658
4681
  const duplicatedPresets = Array(animation.duplicationFactor).fill(presets).flat();
4659
4682
  const totalWidth = presets.length * (layout.buttonWidth + layout.buttonGap);
4660
- const animationDuration = presets.length * animation.duration;
4661
- const target = {
4662
- x: [0, -totalWidth]
4663
- };
4664
- const options = {
4665
- duration: animationDuration,
4666
- ease: animation.easing,
4667
- repeat: Infinity
4668
- };
4669
- (0, import_react4.useEffect)(() => {
4670
- if (animation.enabled) {
4671
- controls.current = animate(scope.current, target, options);
4672
- }
4673
- }, [target, options, animate, scope, animation.enabled]);
4683
+ const effectiveScrollSpeed = Math.max(0.1, animation.scrollSpeed || 1);
4684
+ const animationDuration = presets.length * animation.duration / effectiveScrollSpeed;
4674
4685
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4675
- import_framer_motion.motion.div,
4686
+ "div",
4676
4687
  {
4677
- ref: scope,
4678
- className: "flex",
4679
- onHoverStart: () => animation.hoverPause && controls.current?.pause(),
4680
- onHoverEnd: () => animation.hoverPause && controls.current?.play(),
4688
+ className: (0, import_clsx2.clsx)(
4689
+ "theme-preset-row flex",
4690
+ animation.enabled && "theme-preset-row--animated",
4691
+ animation.hoverPause && "theme-preset-row--hover-pause"
4692
+ ),
4693
+ style: {
4694
+ ["--theme-engine-scroll-distance"]: `${totalWidth}px`,
4695
+ ["--theme-engine-scroll-duration"]: `${animationDuration}s`,
4696
+ ["--theme-engine-scroll-easing"]: animation.easing
4697
+ },
4681
4698
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4682
4699
  "div",
4683
4700
  {
4684
- className: "flex flex-shrink-0",
4701
+ className: "theme-preset-track flex flex-shrink-0",
4685
4702
  style: { gap: `${layout.buttonGap}px` },
4686
4703
  children: duplicatedPresets.map((preset, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4687
4704
  PresetButton,
@@ -4710,29 +4727,11 @@ var ThemePresetButtons = ({
4710
4727
  categories,
4711
4728
  maxPresets,
4712
4729
  showBuiltIn = true,
4713
- showCustom = true,
4714
- groupBy = "none",
4715
- labels = {},
4716
- showSectionHeaders = true
4730
+ showCustom = true
4717
4731
  }) => {
4718
4732
  const { currentPreset, applyPreset, resolvedMode, availablePresets, builtInPresets, customPresets } = useTheme();
4719
4733
  const selectedPresetId = currentPreset?.presetId || void 0;
4720
4734
  const onPresetSelect = applyPreset;
4721
- const defaultLabels = {
4722
- builtIn: labels.builtIn || "Built-in Themes",
4723
- custom: labels.custom || "Custom Themes"
4724
- };
4725
- const isGrouped = groupBy !== "none";
4726
- const shouldShowHeaders = showSectionHeaders && isGrouped;
4727
- if (typeof window !== "undefined" && window.location?.hostname === "localhost") {
4728
- console.log("\u{1F3A8} ThemePresetButtons: Categorization features", {
4729
- groupBy,
4730
- showBuiltIn,
4731
- showCustom,
4732
- labels: defaultLabels,
4733
- shouldShowHeaders
4734
- });
4735
- }
4736
4735
  const [presets, setPresets] = (0, import_react4.useState)([]);
4737
4736
  const [loading, setLoading] = (0, import_react4.useState)(true);
4738
4737
  const [error, setError] = (0, import_react4.useState)(null);
@@ -4836,14 +4835,10 @@ var ThemePresetButtons = ({
4836
4835
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: (0, import_clsx2.clsx)("flex items-center justify-center py-8", className), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-sm text-muted-foreground", children: "No presets available" }) });
4837
4836
  }
4838
4837
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4839
- import_framer_motion.motion.div,
4838
+ "div",
4840
4839
  {
4841
- initial: { opacity: 0, y: 20 },
4842
- whileInView: { opacity: 1, y: 0 },
4843
- viewport: { once: true },
4844
- transition: { duration: 0.5, ease: "easeOut" },
4845
4840
  className: (0, import_clsx2.clsx)(
4846
- "w-full overflow-hidden mb-8 flex flex-col py-2 -my-2",
4841
+ "theme-fade-in w-full overflow-hidden mb-8 flex flex-col py-2 -my-2",
4847
4842
  className
4848
4843
  ),
4849
4844
  style: containerStyle,
@@ -4864,12 +4859,24 @@ var ThemePresetButtons = ({
4864
4859
  }
4865
4860
  );
4866
4861
  };
4862
+
4863
+ // src/hooks/useTypedTheme.ts
4864
+ function useTypedTheme(customPresets) {
4865
+ void customPresets;
4866
+ const theme = useTheme();
4867
+ const setThemePresetById = (presetId) => theme.setThemePresetById(presetId);
4868
+ return {
4869
+ ...theme,
4870
+ setThemePresetById
4871
+ };
4872
+ }
4867
4873
  // Annotate the CommonJS export names for ESM import in node:
4868
4874
  0 && (module.exports = {
4869
4875
  ThemePresetButtons,
4870
4876
  ThemeProvider,
4871
4877
  ThemeScript,
4872
4878
  ThemeToggle,
4879
+ builtInPresetIds,
4873
4880
  formatColor,
4874
4881
  getPresetById,
4875
4882
  getPresetEntries,
@@ -4880,6 +4887,7 @@ var ThemePresetButtons = ({
4880
4887
  searchPresets,
4881
4888
  tweakcnPresets,
4882
4889
  useTheme,
4890
+ useTypedTheme,
4883
4891
  validateCustomPresets,
4884
4892
  validateTweakCNPreset,
4885
4893
  withAlpha