@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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/providers/UnifiedThemeProvider.tsx
4
- import { createContext, useContext, useEffect, useState, useCallback, useMemo } from "react";
4
+ import { createContext, useContext, useEffect, useState, useCallback, useMemo as useMemo2 } from "react";
5
5
 
6
6
  // src/types/presets.ts
7
7
  var CSS_PROPERTY_CATEGORIES = {
@@ -83,6 +83,34 @@ var ALL_CSS_PROPERTIES = [
83
83
  ...CSS_PROPERTY_CATEGORIES.spacing
84
84
  ];
85
85
 
86
+ // src/data/built-in-preset-ids.ts
87
+ var builtInPresetIds = [
88
+ "amber-minimal",
89
+ "amethyst-haze",
90
+ "bold-tech",
91
+ "clean-slate",
92
+ "cosmic-night",
93
+ "doom-64",
94
+ "elegant-luxury",
95
+ "kodama-grove",
96
+ "midnight-bloom",
97
+ "mocha-mousse",
98
+ "modern-minimal",
99
+ "neo-brutalism",
100
+ "northern-lights",
101
+ "ocean-breeze",
102
+ "pastel-dreams",
103
+ "quantum-rose",
104
+ "retro-arcade",
105
+ "soft-pop",
106
+ "solar-dusk",
107
+ "starry-night",
108
+ "sunset-horizon",
109
+ "t3-chat",
110
+ "vintage-paper",
111
+ "violet-bloom"
112
+ ];
113
+
86
114
  // src/data/tweakcn-presets.ts
87
115
  var DEFAULT_ACCENT_COLORS = {
88
116
  DARK: {
@@ -3596,7 +3624,7 @@ var tweakcnPresets = {
3596
3624
  }
3597
3625
  };
3598
3626
  function getPresetIds() {
3599
- return Object.keys(tweakcnPresets);
3627
+ return [...builtInPresetIds];
3600
3628
  }
3601
3629
  function getPresetById(id) {
3602
3630
  return tweakcnPresets[id] || null;
@@ -3606,13 +3634,15 @@ function getPresetLabels() {
3606
3634
  }
3607
3635
  function searchPresets(query) {
3608
3636
  const lowerQuery = query.toLowerCase();
3609
- return Object.entries(tweakcnPresets).filter(([id, preset]) => preset.label.toLowerCase().includes(lowerQuery) || id.toLowerCase().includes(lowerQuery)).map(([id, preset]) => ({ id, preset }));
3637
+ return builtInPresetIds.map((id) => ({ id, preset: tweakcnPresets[id] })).filter(
3638
+ ({ id, preset }) => preset.label.toLowerCase().includes(lowerQuery) || id.toLowerCase().includes(lowerQuery)
3639
+ );
3610
3640
  }
3611
3641
  function getPresetsCount() {
3612
- return Object.keys(tweakcnPresets).length;
3642
+ return builtInPresetIds.length;
3613
3643
  }
3614
3644
  function getPresetEntries() {
3615
- return Object.entries(tweakcnPresets);
3645
+ return builtInPresetIds.map((id) => [id, tweakcnPresets[id]]);
3616
3646
  }
3617
3647
 
3618
3648
  // src/utils/preset-validation.ts
@@ -3761,8 +3791,153 @@ function logValidationResult(result, context = "Custom presets") {
3761
3791
  }
3762
3792
  }
3763
3793
 
3764
- // src/providers/UnifiedThemeProvider.tsx
3794
+ // src/components/UnifiedThemeScript.tsx
3795
+ import { useMemo } from "react";
3765
3796
  import { jsx } from "react/jsx-runtime";
3797
+ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3798
+ const defaultPresetData = useMemo(() => {
3799
+ if (!defaultPreset) return null;
3800
+ const preset = getPresetById(defaultPreset);
3801
+ return preset ? {
3802
+ presetId: defaultPreset,
3803
+ presetName: preset.label,
3804
+ colors: preset.styles
3805
+ } : null;
3806
+ }, [defaultPreset]);
3807
+ const scriptContent = useMemo(
3808
+ () => `
3809
+ // Unified Theme Engine: Restore preset colors before hydration
3810
+ (function() {
3811
+ try {
3812
+ const presetStorageKey = "${presetStorageKey}";
3813
+ const isDev = (function() {
3814
+ try {
3815
+ return location.hostname === 'localhost' || location.hostname === '127.0.0.1';
3816
+ } catch {
3817
+ return false;
3818
+ }
3819
+ })();
3820
+
3821
+ // CSS property categories (inline for script)
3822
+ const CSS_CATEGORIES = {
3823
+ colors: [
3824
+ 'background', 'foreground', 'card', 'card-foreground', 'popover', 'popover-foreground',
3825
+ 'primary', 'primary-foreground', 'secondary', 'secondary-foreground',
3826
+ 'muted', 'muted-foreground', 'accent', 'accent-foreground',
3827
+ 'destructive', 'destructive-foreground', 'border', 'input', 'ring',
3828
+ 'chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5',
3829
+ 'sidebar', 'sidebar-foreground', 'sidebar-primary', 'sidebar-primary-foreground',
3830
+ 'sidebar-accent', 'sidebar-accent-foreground', 'sidebar-border', 'sidebar-ring'
3831
+ ],
3832
+ typography: ['font-sans', 'font-serif', 'font-mono'],
3833
+ layout: ['radius'],
3834
+ shadows: ['shadow-color', 'shadow-opacity', 'shadow-blur', 'shadow-spread', 'shadow-offset-x', 'shadow-offset-y'],
3835
+ spacing: ['letter-spacing', 'spacing']
3836
+ };
3837
+
3838
+ // Function to apply all preset properties - with proper clearing and defaults
3839
+ function applyPresetProperties(colors) {
3840
+ if (!colors) return;
3841
+
3842
+ const root = document.documentElement;
3843
+
3844
+ // Default values for essential properties that might be missing
3845
+ const defaultValues = {
3846
+ 'spacing': '0.25rem',
3847
+ 'letter-spacing': 'normal'
3848
+ };
3849
+
3850
+ // Get all possible CSS properties
3851
+ const allCategories = ['colors', 'typography', 'layout', 'shadows', 'spacing'];
3852
+ const allProperties = [];
3853
+ allCategories.forEach(function(category) {
3854
+ CSS_CATEGORIES[category].forEach(function(prop) {
3855
+ allProperties.push(prop);
3856
+ });
3857
+ });
3858
+
3859
+ // First, clear all properties to prevent leftover values
3860
+ let clearedCount = 0;
3861
+ allProperties.forEach(function(prop) {
3862
+ const cssVar = '--' + prop;
3863
+ root.style.removeProperty(cssVar);
3864
+ clearedCount++;
3865
+ });
3866
+
3867
+ // Apply all properties with defaults for missing ones
3868
+ let appliedCount = 0;
3869
+ allProperties.forEach(function(prop) {
3870
+ let value = colors[prop];
3871
+
3872
+ // Apply default value if property is missing and we have a default
3873
+ if (!value && defaultValues[prop]) {
3874
+ value = defaultValues[prop];
3875
+ }
3876
+
3877
+ if (value) {
3878
+ const cssVar = '--' + prop;
3879
+ // Apply directly like TweakCN does - no conversion, no !important
3880
+ root.style.setProperty(cssVar, value);
3881
+ appliedCount++;
3882
+ }
3883
+ });
3884
+
3885
+ }
3886
+
3887
+ // Load and apply persisted preset or default preset
3888
+ const storedPreset = localStorage.getItem(presetStorageKey);
3889
+ let presetToApply = null;
3890
+
3891
+ if (storedPreset) {
3892
+ try {
3893
+ presetToApply = JSON.parse(storedPreset);
3894
+ } catch (error) {
3895
+ if (isDev) console.warn('\u{1F3A8} UnifiedThemeScript: Failed to parse stored preset:', error);
3896
+ }
3897
+ }
3898
+
3899
+ // Use default preset if no stored preset
3900
+ if (!presetToApply && ${JSON.stringify(defaultPresetData)}) {
3901
+ presetToApply = ${JSON.stringify(defaultPresetData)};
3902
+ }
3903
+
3904
+ if (presetToApply) {
3905
+ // Determine current mode (will be set by ThemeProvider)
3906
+ // Default to light if no theme class is present yet
3907
+ const isDark = document.documentElement.classList.contains('dark');
3908
+ const mode = isDark ? 'dark' : 'light';
3909
+ const colors = presetToApply.colors && presetToApply.colors[mode];
3910
+
3911
+ if (colors) {
3912
+ // Font inheritance logic: inherit missing fonts from other mode
3913
+ const fontProperties = ['font-sans', 'font-serif', 'font-mono'];
3914
+ const otherMode = mode === 'light' ? 'dark' : 'light';
3915
+ const otherModeColors = presetToApply.colors && presetToApply.colors[otherMode];
3916
+
3917
+ if (otherModeColors) {
3918
+ fontProperties.forEach(function(fontProp) {
3919
+ if (!colors[fontProp] && otherModeColors[fontProp]) {
3920
+ colors[fontProp] = otherModeColors[fontProp];
3921
+ }
3922
+ });
3923
+ }
3924
+
3925
+ applyPresetProperties(colors);
3926
+ }
3927
+ }
3928
+
3929
+ } catch (error) {
3930
+ if (isDev) console.error('\u{1F3A8} UnifiedThemeScript: Initialization failed:', error);
3931
+ }
3932
+ })();
3933
+ `,
3934
+ [presetStorageKey, defaultPresetData]
3935
+ );
3936
+ return /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: scriptContent }, suppressHydrationWarning: true });
3937
+ }
3938
+
3939
+ // src/providers/UnifiedThemeProvider.tsx
3940
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
3766
3941
  var UnifiedThemeContext = createContext(void 0);
3767
3942
  var THEME_STORAGE_KEY = "theme-engine-theme";
3768
3943
  function getSystemTheme() {
@@ -3791,9 +3966,14 @@ function ThemeProvider({
3791
3966
  modeStorageKey = THEME_STORAGE_KEY,
3792
3967
  presetStorageKey = "theme-preset",
3793
3968
  enablePresets = true,
3794
- customPresets = {},
3795
- includeBuiltInPresets = true
3969
+ customPresets,
3970
+ includeBuiltInPresets = true,
3971
+ disableScript = false,
3972
+ deferRenderUntilReady
3796
3973
  }) {
3974
+ const shouldInjectScript = !disableScript;
3975
+ const shouldDeferRenderUntilReady = deferRenderUntilReady ?? disableScript;
3976
+ const normalizedCustomPresets = useMemo2(() => customPresets ?? {}, [customPresets]);
3797
3977
  const [mode, setMode] = useState(() => {
3798
3978
  const stored = getStoredMode(modeStorageKey);
3799
3979
  return stored || defaultMode;
@@ -3804,19 +3984,19 @@ function ThemeProvider({
3804
3984
  }
3805
3985
  return mode;
3806
3986
  });
3807
- const availablePresets = useMemo(() => {
3987
+ const availablePresets = useMemo2(() => {
3808
3988
  const merged = {};
3809
3989
  if (includeBuiltInPresets) {
3810
3990
  Object.assign(merged, tweakcnPresets);
3811
3991
  }
3812
- if (customPresets && Object.keys(customPresets).length > 0) {
3813
- const validationResult = validateCustomPresets(customPresets);
3992
+ if (Object.keys(normalizedCustomPresets).length > 0) {
3993
+ const validationResult = validateCustomPresets(normalizedCustomPresets);
3814
3994
  const isDevelopment = typeof window !== "undefined" && window.location?.hostname === "localhost";
3815
3995
  if (isDevelopment) {
3816
3996
  logValidationResult(validationResult, "Custom presets");
3817
3997
  }
3818
3998
  if (validationResult.isValid || validationResult.errors.length === 0) {
3819
- Object.assign(merged, customPresets);
3999
+ Object.assign(merged, normalizedCustomPresets);
3820
4000
  } else {
3821
4001
  if (isDevelopment) {
3822
4002
  console.error("\u{1F3A8} ThemeProvider: Skipping invalid custom presets");
@@ -3824,9 +4004,8 @@ function ThemeProvider({
3824
4004
  }
3825
4005
  }
3826
4006
  return merged;
3827
- }, [includeBuiltInPresets, customPresets]);
3828
- const builtInPresets = useMemo(() => includeBuiltInPresets ? tweakcnPresets : {}, [includeBuiltInPresets]);
3829
- const normalizedCustomPresets = useMemo(() => customPresets || {}, [customPresets]);
4007
+ }, [includeBuiltInPresets, normalizedCustomPresets]);
4008
+ const builtInPresets = useMemo2(() => includeBuiltInPresets ? tweakcnPresets : {}, [includeBuiltInPresets]);
3830
4009
  const getAvailablePresetById = useCallback(
3831
4010
  (id) => {
3832
4011
  return availablePresets[id] || null;
@@ -3834,7 +4013,7 @@ function ThemeProvider({
3834
4013
  [availablePresets]
3835
4014
  );
3836
4015
  const [currentPreset, setCurrentPreset] = useState(null);
3837
- const [isReady, setIsReady] = useState(false);
4016
+ const [isReady, setIsReady] = useState(() => !shouldDeferRenderUntilReady);
3838
4017
  useEffect(() => {
3839
4018
  if (!enablePresets || typeof window === "undefined") {
3840
4019
  setIsReady(true);
@@ -4077,8 +4256,9 @@ function ThemeProvider({
4077
4256
  }
4078
4257
  }
4079
4258
  }, [presetStorageKey, enablePresets, defaultPreset, applyPresetColors, resolvedMode]);
4080
- if (!isReady) {
4081
- return null;
4259
+ const scriptElement = shouldInjectScript ? /* @__PURE__ */ jsx2(ThemeScript, { presetStorageKey, defaultPreset }) : null;
4260
+ if (!isReady && shouldDeferRenderUntilReady) {
4261
+ return scriptElement;
4082
4262
  }
4083
4263
  const isUsingDefaultPreset = !!defaultPreset && currentPreset?.presetId === defaultPreset;
4084
4264
  const contextValue = {
@@ -4095,7 +4275,10 @@ function ThemeProvider({
4095
4275
  builtInPresets,
4096
4276
  customPresets: normalizedCustomPresets
4097
4277
  };
4098
- return /* @__PURE__ */ jsx(UnifiedThemeContext.Provider, { value: contextValue, children });
4278
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4279
+ scriptElement,
4280
+ /* @__PURE__ */ jsx2(UnifiedThemeContext.Provider, { value: contextValue, children })
4281
+ ] });
4099
4282
  }
4100
4283
  function useTheme() {
4101
4284
  const context = useContext(UnifiedThemeContext);
@@ -4105,163 +4288,11 @@ function useTheme() {
4105
4288
  return context;
4106
4289
  }
4107
4290
 
4108
- // src/components/UnifiedThemeScript.tsx
4109
- import { useMemo as useMemo2 } from "react";
4110
- import { jsx as jsx2 } from "react/jsx-runtime";
4111
- function ThemeScript({
4112
- presetStorageKey = "theme-preset",
4113
- defaultPreset
4114
- }) {
4115
- const defaultPresetData = useMemo2(() => {
4116
- if (!defaultPreset) return null;
4117
- const preset = getPresetById(defaultPreset);
4118
- return preset ? {
4119
- presetId: defaultPreset,
4120
- presetName: preset.label,
4121
- colors: preset.styles
4122
- } : null;
4123
- }, [defaultPreset]);
4124
- const scriptContent = useMemo2(() => `
4125
- // Unified Theme Engine: Restore preset colors before hydration
4126
- (function() {
4127
- try {
4128
- const presetStorageKey = "${presetStorageKey}";
4129
-
4130
- // CSS property categories (inline for script)
4131
- const CSS_CATEGORIES = {
4132
- colors: [
4133
- 'background', 'foreground', 'card', 'card-foreground', 'popover', 'popover-foreground',
4134
- 'primary', 'primary-foreground', 'secondary', 'secondary-foreground',
4135
- 'muted', 'muted-foreground', 'accent', 'accent-foreground',
4136
- 'destructive', 'destructive-foreground', 'border', 'input', 'ring',
4137
- 'chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5',
4138
- 'sidebar', 'sidebar-foreground', 'sidebar-primary', 'sidebar-primary-foreground',
4139
- 'sidebar-accent', 'sidebar-accent-foreground', 'sidebar-border', 'sidebar-ring'
4140
- ],
4141
- typography: ['font-sans', 'font-serif', 'font-mono'],
4142
- layout: ['radius'],
4143
- shadows: ['shadow-color', 'shadow-opacity', 'shadow-blur', 'shadow-spread', 'shadow-offset-x', 'shadow-offset-y'],
4144
- spacing: ['letter-spacing', 'spacing']
4145
- };
4146
-
4147
- // Function to apply all preset properties - with proper clearing and defaults
4148
- function applyPresetProperties(colors) {
4149
- if (!colors) return;
4150
-
4151
- const root = document.documentElement;
4152
-
4153
- // Default values for essential properties that might be missing
4154
- const defaultValues = {
4155
- 'spacing': '0.25rem',
4156
- 'letter-spacing': 'normal'
4157
- };
4158
-
4159
- // Get all possible CSS properties
4160
- const allCategories = ['colors', 'typography', 'layout', 'shadows', 'spacing'];
4161
- const allProperties = [];
4162
- allCategories.forEach(function(category) {
4163
- CSS_CATEGORIES[category].forEach(function(prop) {
4164
- allProperties.push(prop);
4165
- });
4166
- });
4167
-
4168
- // First, clear all properties to prevent leftover values
4169
- let clearedCount = 0;
4170
- allProperties.forEach(function(prop) {
4171
- const cssVar = '--' + prop;
4172
- root.style.removeProperty(cssVar);
4173
- clearedCount++;
4174
- });
4175
- console.log('\u{1F3A8} UnifiedThemeScript: Cleared ' + clearedCount + ' CSS properties before applying new theme');
4176
-
4177
- // Apply all properties with defaults for missing ones
4178
- let appliedCount = 0;
4179
- allProperties.forEach(function(prop) {
4180
- let value = colors[prop];
4181
-
4182
- // Apply default value if property is missing and we have a default
4183
- if (!value && defaultValues[prop]) {
4184
- value = defaultValues[prop];
4185
- console.log('\u{1F3A8} Script using default value for ' + prop + ': ' + value);
4186
- }
4187
-
4188
- if (value) {
4189
- const cssVar = '--' + prop;
4190
- // Apply directly like TweakCN does - no conversion, no !important
4191
- root.style.setProperty(cssVar, value);
4192
- appliedCount++;
4193
- console.log('\u{1F3A8} Script applied ' + cssVar + ': ' + value);
4194
- }
4195
- });
4196
-
4197
- console.log('\u{1F3A8} UnifiedThemeScript: Applied ' + appliedCount + ' CSS properties');
4198
- }
4199
-
4200
- // Load and apply persisted preset or default preset
4201
- const storedPreset = localStorage.getItem(presetStorageKey);
4202
- let presetToApply = null;
4203
-
4204
- if (storedPreset) {
4205
- try {
4206
- presetToApply = JSON.parse(storedPreset);
4207
- console.log('\u{1F3A8} UnifiedThemeScript: Using stored preset:', presetToApply.presetName);
4208
- } catch (error) {
4209
- console.warn('\u{1F3A8} UnifiedThemeScript: Failed to parse stored preset:', error);
4210
- }
4211
- }
4212
-
4213
- // Use default preset if no stored preset
4214
- if (!presetToApply && ${JSON.stringify(defaultPresetData)}) {
4215
- presetToApply = ${JSON.stringify(defaultPresetData)};
4216
- console.log('\u{1F3A8} UnifiedThemeScript: Using default preset:', presetToApply.presetName);
4217
- }
4218
-
4219
- if (presetToApply) {
4220
- // Determine current mode (will be set by ThemeProvider)
4221
- // Default to light if no theme class is present yet
4222
- const isDark = document.documentElement.classList.contains('dark');
4223
- const mode = isDark ? 'dark' : 'light';
4224
- const colors = presetToApply.colors && presetToApply.colors[mode];
4225
-
4226
- if (colors) {
4227
- // Font inheritance logic: inherit missing fonts from other mode
4228
- const fontProperties = ['font-sans', 'font-serif', 'font-mono'];
4229
- const otherMode = mode === 'light' ? 'dark' : 'light';
4230
- const otherModeColors = presetToApply.colors && presetToApply.colors[otherMode];
4231
-
4232
- if (otherModeColors) {
4233
- fontProperties.forEach(function(fontProp) {
4234
- if (!colors[fontProp] && otherModeColors[fontProp]) {
4235
- colors[fontProp] = otherModeColors[fontProp];
4236
- console.log('\u{1F3A8} Script inherited ' + fontProp + ': "' + otherModeColors[fontProp] + '" from ' + otherMode + ' mode');
4237
- }
4238
- });
4239
- }
4240
-
4241
- applyPresetProperties(colors);
4242
- console.log('\u{1F3A8} UnifiedThemeScript: Applied preset properties for', mode, 'mode');
4243
- }
4244
- }
4245
-
4246
- } catch (error) {
4247
- console.error('\u{1F3A8} UnifiedThemeScript: Initialization failed:', error);
4248
- }
4249
- })();
4250
- `, [presetStorageKey, defaultPresetData]);
4251
- return /* @__PURE__ */ jsx2(
4252
- "script",
4253
- {
4254
- dangerouslySetInnerHTML: { __html: scriptContent },
4255
- suppressHydrationWarning: true
4256
- }
4257
- );
4258
- }
4259
-
4260
4291
  // src/components/ThemeToggle.tsx
4261
4292
  import { forwardRef } from "react";
4262
4293
  import { clsx } from "clsx";
4263
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
4264
- var SunIcon = () => /* @__PURE__ */ jsxs(
4294
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
4295
+ var SunIcon = () => /* @__PURE__ */ jsxs2(
4265
4296
  "svg",
4266
4297
  {
4267
4298
  xmlns: "http://www.w3.org/2000/svg",
@@ -4303,7 +4334,7 @@ var MoonIcon = () => /* @__PURE__ */ jsx3(
4303
4334
  children: /* @__PURE__ */ jsx3("path", { d: "m12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" })
4304
4335
  }
4305
4336
  );
4306
- var SystemIcon = () => /* @__PURE__ */ jsxs(
4337
+ var SystemIcon = () => /* @__PURE__ */ jsxs2(
4307
4338
  "svg",
4308
4339
  {
4309
4340
  xmlns: "http://www.w3.org/2000/svg",
@@ -4373,6 +4404,7 @@ var ThemeToggle = forwardRef(
4373
4404
  className: baseClasses,
4374
4405
  onClick: handleClick,
4375
4406
  "data-mode": resolvedMode,
4407
+ "data-theme": resolvedMode,
4376
4408
  "aria-label": `Switch to ${resolvedMode === "light" ? "dark" : "light"} mode`,
4377
4409
  title: `Switch to ${resolvedMode === "light" ? "dark" : "light"} mode`,
4378
4410
  ...props,
@@ -4384,8 +4416,7 @@ var ThemeToggle = forwardRef(
4384
4416
  ThemeToggle.displayName = "ThemeToggle";
4385
4417
 
4386
4418
  // src/components/ThemePresetButtons.tsx
4387
- import { useEffect as useEffect2, useState as useState2, useCallback as useCallback2, useMemo as useMemo3, useRef } from "react";
4388
- import { motion, useAnimate } from "framer-motion";
4419
+ import { useEffect as useEffect2, useState as useState2, useCallback as useCallback2, useMemo as useMemo3 } from "react";
4389
4420
  import { clsx as clsx2 } from "clsx";
4390
4421
 
4391
4422
  // src/utils/colors.ts
@@ -4521,7 +4552,7 @@ function withAlpha(colorInput, alpha) {
4521
4552
  }
4522
4553
 
4523
4554
  // src/components/ThemePresetButtons.tsx
4524
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
4555
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
4525
4556
  var DEFAULT_ANIMATION = {
4526
4557
  enabled: true,
4527
4558
  duration: 5,
@@ -4573,33 +4604,25 @@ var PresetButton = ({
4573
4604
  }
4574
4605
  );
4575
4606
  }
4576
- return /* @__PURE__ */ jsx4(
4577
- motion.div,
4607
+ return /* @__PURE__ */ jsx4("div", { className: "flex-shrink-0", style: { minWidth: layout.buttonWidth }, children: /* @__PURE__ */ jsx4(
4608
+ "div",
4578
4609
  {
4579
- className: "flex-shrink-0",
4580
- style: { minWidth: layout.buttonWidth },
4581
- whileHover: { scale: 1.02, y: -3, zIndex: 20 },
4582
- transition: { duration: 0.2, ease: "easeOut" },
4583
- children: /* @__PURE__ */ jsx4(
4584
- "div",
4585
- {
4586
- className: clsx2(
4587
- "cursor-pointer bg-card hover:bg-card/80 border border-border rounded-lg",
4588
- "flex w-full h-full items-center justify-center relative transition-all duration-200",
4589
- "hover:shadow-lg hover:border-primary/20 px-4 py-3",
4590
- isSelected ? "ring-2 ring-primary/50 shadow-md bg-primary/5" : ""
4591
- ),
4592
- onClick,
4593
- children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2.5 text-center", children: [
4594
- layout.showColorBoxes && /* @__PURE__ */ jsx4("div", { className: "flex gap-1", children: [colors.primary, colors.secondary, colors.accent].slice(0, layout.colorBoxCount).map(
4595
- (color, index) => renderColorBox ? renderColorBox(color, index) : /* @__PURE__ */ jsx4(ColorBox, { color }, index)
4596
- ) }),
4597
- /* @__PURE__ */ jsx4("span", { className: "capitalize px-1 leading-tight text-sm font-medium text-foreground", children: preset.name.replace(/-/g, " ") })
4598
- ] })
4599
- }
4600
- )
4610
+ className: clsx2(
4611
+ "theme-preset-button",
4612
+ "cursor-pointer bg-card hover:bg-card/80 border border-border rounded-lg",
4613
+ "flex w-full h-full items-center justify-center relative transition-all duration-200",
4614
+ "hover:shadow-lg hover:border-primary/20 px-4 py-3",
4615
+ isSelected ? "ring-2 ring-primary/50 shadow-md bg-primary/5" : ""
4616
+ ),
4617
+ onClick,
4618
+ children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2.5 text-center", children: [
4619
+ layout.showColorBoxes && /* @__PURE__ */ jsx4("div", { className: "flex gap-1", children: [colors.primary, colors.secondary, colors.accent].slice(0, layout.colorBoxCount).map(
4620
+ (color, index) => renderColorBox ? /* @__PURE__ */ jsx4("span", { children: renderColorBox(color, index) }, index) : /* @__PURE__ */ jsx4(ColorBox, { color }, index)
4621
+ ) }),
4622
+ /* @__PURE__ */ jsx4("span", { className: "capitalize px-1 leading-tight text-sm font-medium text-foreground", children: preset.name.replace(/-/g, " ") })
4623
+ ] })
4601
4624
  }
4602
- );
4625
+ ) });
4603
4626
  };
4604
4627
  var AnimatedRow = ({
4605
4628
  presets,
@@ -4611,36 +4634,28 @@ var AnimatedRow = ({
4611
4634
  renderPreset,
4612
4635
  renderColorBox
4613
4636
  }) => {
4614
- const [scope, animate] = useAnimate();
4615
- const controls = useRef(null);
4616
4637
  if (presets.length === 0) return null;
4617
4638
  const duplicatedPresets = Array(animation.duplicationFactor).fill(presets).flat();
4618
4639
  const totalWidth = presets.length * (layout.buttonWidth + layout.buttonGap);
4619
- const animationDuration = presets.length * animation.duration;
4620
- const target = {
4621
- x: [0, -totalWidth]
4622
- };
4623
- const options = {
4624
- duration: animationDuration,
4625
- ease: animation.easing,
4626
- repeat: Infinity
4627
- };
4628
- useEffect2(() => {
4629
- if (animation.enabled) {
4630
- controls.current = animate(scope.current, target, options);
4631
- }
4632
- }, [target, options, animate, scope, animation.enabled]);
4640
+ const effectiveScrollSpeed = Math.max(0.1, animation.scrollSpeed || 1);
4641
+ const animationDuration = presets.length * animation.duration / effectiveScrollSpeed;
4633
4642
  return /* @__PURE__ */ jsx4(
4634
- motion.div,
4643
+ "div",
4635
4644
  {
4636
- ref: scope,
4637
- className: "flex",
4638
- onHoverStart: () => animation.hoverPause && controls.current?.pause(),
4639
- onHoverEnd: () => animation.hoverPause && controls.current?.play(),
4645
+ className: clsx2(
4646
+ "theme-preset-row flex",
4647
+ animation.enabled && "theme-preset-row--animated",
4648
+ animation.hoverPause && "theme-preset-row--hover-pause"
4649
+ ),
4650
+ style: {
4651
+ ["--theme-engine-scroll-distance"]: `${totalWidth}px`,
4652
+ ["--theme-engine-scroll-duration"]: `${animationDuration}s`,
4653
+ ["--theme-engine-scroll-easing"]: animation.easing
4654
+ },
4640
4655
  children: /* @__PURE__ */ jsx4(
4641
4656
  "div",
4642
4657
  {
4643
- className: "flex flex-shrink-0",
4658
+ className: "theme-preset-track flex flex-shrink-0",
4644
4659
  style: { gap: `${layout.buttonGap}px` },
4645
4660
  children: duplicatedPresets.map((preset, index) => /* @__PURE__ */ jsx4(
4646
4661
  PresetButton,
@@ -4669,29 +4684,11 @@ var ThemePresetButtons = ({
4669
4684
  categories,
4670
4685
  maxPresets,
4671
4686
  showBuiltIn = true,
4672
- showCustom = true,
4673
- groupBy = "none",
4674
- labels = {},
4675
- showSectionHeaders = true
4687
+ showCustom = true
4676
4688
  }) => {
4677
4689
  const { currentPreset, applyPreset, resolvedMode, availablePresets, builtInPresets, customPresets } = useTheme();
4678
4690
  const selectedPresetId = currentPreset?.presetId || void 0;
4679
4691
  const onPresetSelect = applyPreset;
4680
- const defaultLabels = {
4681
- builtIn: labels.builtIn || "Built-in Themes",
4682
- custom: labels.custom || "Custom Themes"
4683
- };
4684
- const isGrouped = groupBy !== "none";
4685
- const shouldShowHeaders = showSectionHeaders && isGrouped;
4686
- if (typeof window !== "undefined" && window.location?.hostname === "localhost") {
4687
- console.log("\u{1F3A8} ThemePresetButtons: Categorization features", {
4688
- groupBy,
4689
- showBuiltIn,
4690
- showCustom,
4691
- labels: defaultLabels,
4692
- shouldShowHeaders
4693
- });
4694
- }
4695
4692
  const [presets, setPresets] = useState2([]);
4696
4693
  const [loading, setLoading] = useState2(true);
4697
4694
  const [error, setError] = useState2(null);
@@ -4786,7 +4783,7 @@ var ThemePresetButtons = ({
4786
4783
  return /* @__PURE__ */ jsx4("div", { className: clsx2("flex items-center justify-center py-8", className), children: /* @__PURE__ */ jsx4("div", { className: "animate-pulse text-sm text-muted-foreground", children: "Loading presets..." }) });
4787
4784
  }
4788
4785
  if (error) {
4789
- return /* @__PURE__ */ jsx4("div", { className: clsx2("flex items-center justify-center py-8", className), children: /* @__PURE__ */ jsxs2("div", { className: "text-sm text-destructive", children: [
4786
+ return /* @__PURE__ */ jsx4("div", { className: clsx2("flex items-center justify-center py-8", className), children: /* @__PURE__ */ jsxs3("div", { className: "text-sm text-destructive", children: [
4790
4787
  "Error: ",
4791
4788
  error
4792
4789
  ] }) });
@@ -4795,14 +4792,10 @@ var ThemePresetButtons = ({
4795
4792
  return /* @__PURE__ */ jsx4("div", { className: clsx2("flex items-center justify-center py-8", className), children: /* @__PURE__ */ jsx4("div", { className: "text-sm text-muted-foreground", children: "No presets available" }) });
4796
4793
  }
4797
4794
  return /* @__PURE__ */ jsx4(
4798
- motion.div,
4795
+ "div",
4799
4796
  {
4800
- initial: { opacity: 0, y: 20 },
4801
- whileInView: { opacity: 1, y: 0 },
4802
- viewport: { once: true },
4803
- transition: { duration: 0.5, ease: "easeOut" },
4804
4797
  className: clsx2(
4805
- "w-full overflow-hidden mb-8 flex flex-col py-2 -my-2",
4798
+ "theme-fade-in w-full overflow-hidden mb-8 flex flex-col py-2 -my-2",
4806
4799
  className
4807
4800
  ),
4808
4801
  style: containerStyle,
@@ -4823,11 +4816,23 @@ var ThemePresetButtons = ({
4823
4816
  }
4824
4817
  );
4825
4818
  };
4819
+
4820
+ // src/hooks/useTypedTheme.ts
4821
+ function useTypedTheme(customPresets) {
4822
+ void customPresets;
4823
+ const theme = useTheme();
4824
+ const setThemePresetById = (presetId) => theme.setThemePresetById(presetId);
4825
+ return {
4826
+ ...theme,
4827
+ setThemePresetById
4828
+ };
4829
+ }
4826
4830
  export {
4827
4831
  ThemePresetButtons,
4828
4832
  ThemeProvider,
4829
4833
  ThemeScript,
4830
4834
  ThemeToggle,
4835
+ builtInPresetIds,
4831
4836
  formatColor,
4832
4837
  getPresetById,
4833
4838
  getPresetEntries,
@@ -4838,6 +4843,7 @@ export {
4838
4843
  searchPresets,
4839
4844
  tweakcnPresets,
4840
4845
  useTheme,
4846
+ useTypedTheme,
4841
4847
  validateCustomPresets,
4842
4848
  validateTweakCNPreset,
4843
4849
  withAlpha