@fakhrirafiki/theme-engine 0.4.3 → 0.4.4

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
@@ -3870,7 +3870,15 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3870
3870
  'destructive', 'destructive-foreground', 'border', 'input', 'ring',
3871
3871
  'chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5',
3872
3872
  'sidebar', 'sidebar-foreground', 'sidebar-primary', 'sidebar-primary-foreground',
3873
- 'sidebar-accent', 'sidebar-accent-foreground', 'sidebar-border', 'sidebar-ring'
3873
+ 'sidebar-accent', 'sidebar-accent-foreground', 'sidebar-border', 'sidebar-ring',
3874
+ // Semantic accent colors for status and feedback
3875
+ 'accent-info', 'accent-info-foreground',
3876
+ 'accent-success', 'accent-success-foreground',
3877
+ 'accent-warning', 'accent-warning-foreground',
3878
+ 'accent-danger', 'accent-danger-foreground',
3879
+ 'accent-brand', 'accent-brand-foreground',
3880
+ 'accent-feature', 'accent-feature-foreground',
3881
+ 'accent-highlight', 'accent-highlight-foreground'
3874
3882
  ],
3875
3883
  typography: ['font-sans', 'font-serif', 'font-mono'],
3876
3884
  layout: ['radius'],
@@ -3878,6 +3886,106 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3878
3886
  spacing: ['letter-spacing', 'spacing']
3879
3887
  };
3880
3888
 
3889
+ // Normalize hex/rgb/hsl() colors into "H S% L%" for hsl(var(--token)) usage.
3890
+ function normalizeColorValueToHslTriplet(value) {
3891
+ if (!value) return value;
3892
+ const trimmed = String(value).trim();
3893
+ if (!trimmed) return value;
3894
+ if (trimmed.startsWith('var(')) return trimmed;
3895
+
3896
+ // Already a triplet: "210 40% 98%"
3897
+ if (/^\\d+(?:\\.\\d+)?\\s+\\d+(?:\\.\\d+)?%\\s+\\d+(?:\\.\\d+)?%$/.test(trimmed)) {
3898
+ return trimmed;
3899
+ }
3900
+
3901
+ function clamp(n, min, max) {
3902
+ return Math.min(max, Math.max(min, n));
3903
+ }
3904
+
3905
+ function rgbToHsl(r, g, b) {
3906
+ r /= 255;
3907
+ g /= 255;
3908
+ b /= 255;
3909
+ const max = Math.max(r, g, b);
3910
+ const min = Math.min(r, g, b);
3911
+ const diff = max - min;
3912
+ let h = 0;
3913
+ let s = 0;
3914
+ const l = (max + min) / 2;
3915
+
3916
+ if (diff !== 0) {
3917
+ s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
3918
+ switch (max) {
3919
+ case r:
3920
+ h = (g - b) / diff + (g < b ? 6 : 0);
3921
+ break;
3922
+ case g:
3923
+ h = (b - r) / diff + 2;
3924
+ break;
3925
+ case b:
3926
+ h = (r - g) / diff + 4;
3927
+ break;
3928
+ }
3929
+ h /= 6;
3930
+ }
3931
+
3932
+ return {
3933
+ h: Math.round(h * 360),
3934
+ s: Math.round(s * 100),
3935
+ l: Math.round(l * 100),
3936
+ };
3937
+ }
3938
+
3939
+ function hexToRgb(hex) {
3940
+ let clean = hex.replace('#', '').trim();
3941
+ if (clean.length === 3) clean = clean.split('').map(function(c) { return c + c; }).join('');
3942
+ if (clean.length === 8) clean = clean.substring(0, 6); // ignore alpha
3943
+ if (clean.length !== 6) return null;
3944
+
3945
+ const r = parseInt(clean.substring(0, 2), 16);
3946
+ const g = parseInt(clean.substring(2, 4), 16);
3947
+ const b = parseInt(clean.substring(4, 6), 16);
3948
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
3949
+ return { r: r, g: g, b: b };
3950
+ }
3951
+
3952
+ function toTriplet(hsl) {
3953
+ return String(clamp(hsl.h, 0, 360)) + ' ' + String(clamp(hsl.s, 0, 100)) + '% ' + String(clamp(hsl.l, 0, 100)) + '%';
3954
+ }
3955
+
3956
+ // hsl(...) or raw "H S% L%"
3957
+ if (/^hsl\\(/i.test(trimmed)) {
3958
+ const cleaned = trimmed.replace(/hsl\\(|\\)/gi, '').replace(/[,%]/g, ' ').trim();
3959
+ const parts = cleaned.split(/\\s+/).filter(Boolean);
3960
+ if (parts.length === 3) {
3961
+ const h = parseFloat(parts[0]) || 0;
3962
+ const s = parseFloat(parts[1]) || 0;
3963
+ const l = parseFloat(parts[2]) || 0;
3964
+ return toTriplet({ h: h, s: s, l: l });
3965
+ }
3966
+ }
3967
+
3968
+ // rgb(...)
3969
+ if (/^rgb\\(/i.test(trimmed)) {
3970
+ const cleaned = trimmed.replace(/rgb\\(|\\)/gi, '').trim();
3971
+ const parts = cleaned.split(',').map(function(p) { return p.trim(); });
3972
+ if (parts.length === 3) {
3973
+ const r = clamp(parseFloat(parts[0]) || 0, 0, 255);
3974
+ const g = clamp(parseFloat(parts[1]) || 0, 0, 255);
3975
+ const b = clamp(parseFloat(parts[2]) || 0, 0, 255);
3976
+ return toTriplet(rgbToHsl(r, g, b));
3977
+ }
3978
+ }
3979
+
3980
+ // hex
3981
+ if (trimmed[0] === '#') {
3982
+ const rgb = hexToRgb(trimmed);
3983
+ if (rgb) return toTriplet(rgbToHsl(rgb.r, rgb.g, rgb.b));
3984
+ }
3985
+
3986
+ return value;
3987
+ }
3988
+
3881
3989
  // Function to apply all preset properties - with proper clearing and defaults
3882
3990
  function applyPresetProperties(colors) {
3883
3991
  if (!colors) return;
@@ -3909,6 +4017,10 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3909
4017
 
3910
4018
  // Apply all properties with defaults for missing ones
3911
4019
  let appliedCount = 0;
4020
+ const colorProps = {};
4021
+ CSS_CATEGORIES.colors.forEach(function(prop) { colorProps[prop] = true; });
4022
+ colorProps['shadow-color'] = true;
4023
+
3912
4024
  allProperties.forEach(function(prop) {
3913
4025
  let value = colors[prop];
3914
4026
 
@@ -3918,6 +4030,10 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3918
4030
  }
3919
4031
 
3920
4032
  if (value) {
4033
+ if (colorProps[prop]) {
4034
+ value = normalizeColorValueToHslTriplet(value);
4035
+ }
4036
+
3921
4037
  const cssVar = '--' + prop;
3922
4038
  // Apply directly like TweakCN does - no conversion, no !important
3923
4039
  root.style.setProperty(cssVar, value);
@@ -3979,6 +4095,138 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3979
4095
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { dangerouslySetInnerHTML: { __html: scriptContent }, suppressHydrationWarning: true });
3980
4096
  }
3981
4097
 
4098
+ // src/utils/colors.ts
4099
+ function parseHSL(hslString) {
4100
+ try {
4101
+ const cleaned = hslString.replace(/hsl\(|\)/g, "").replace(/[,%]/g, " ").trim();
4102
+ const parts = cleaned.split(/\s+/).filter(Boolean);
4103
+ if (parts.length !== 3) return null;
4104
+ const h = parseFloat(parts[0]) || 0;
4105
+ const s = parseFloat(parts[1]) || 0;
4106
+ const l = parseFloat(parts[2]) || 0;
4107
+ return {
4108
+ h: Math.max(0, Math.min(360, h)),
4109
+ s: Math.max(0, Math.min(100, s)),
4110
+ l: Math.max(0, Math.min(100, l))
4111
+ };
4112
+ } catch {
4113
+ return null;
4114
+ }
4115
+ }
4116
+ function parseHex(hexString) {
4117
+ try {
4118
+ let hex = hexString.replace("#", "");
4119
+ if (hex.length === 3) {
4120
+ hex = hex.split("").map((char) => char + char).join("");
4121
+ }
4122
+ if (hex.length !== 6) return null;
4123
+ const r = parseInt(hex.substring(0, 2), 16);
4124
+ const s = parseInt(hex.substring(2, 4), 16);
4125
+ const l = parseInt(hex.substring(4, 6), 16);
4126
+ if (isNaN(r) || isNaN(s) || isNaN(l)) return null;
4127
+ return { r, g: s, b: l };
4128
+ } catch {
4129
+ return null;
4130
+ }
4131
+ }
4132
+ function hslToRgb(hsl) {
4133
+ const h = hsl.h / 360;
4134
+ const s = hsl.s / 100;
4135
+ const l = hsl.l / 100;
4136
+ if (s === 0) {
4137
+ const gray = Math.round(l * 255);
4138
+ return { r: gray, g: gray, b: gray };
4139
+ }
4140
+ const hue2rgb = (p2, q2, t) => {
4141
+ if (t < 0) t += 1;
4142
+ if (t > 1) t -= 1;
4143
+ if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
4144
+ if (t < 1 / 2) return q2;
4145
+ if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
4146
+ return p2;
4147
+ };
4148
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
4149
+ const p = 2 * l - q;
4150
+ const r = Math.round(hue2rgb(p, q, h + 1 / 3) * 255);
4151
+ const g = Math.round(hue2rgb(p, q, h) * 255);
4152
+ const b = Math.round(hue2rgb(p, q, h - 1 / 3) * 255);
4153
+ return { r, g, b };
4154
+ }
4155
+ function rgbToHsl(rgb) {
4156
+ const r = rgb.r / 255;
4157
+ const g = rgb.g / 255;
4158
+ const b = rgb.b / 255;
4159
+ const max = Math.max(r, g, b);
4160
+ const min = Math.min(r, g, b);
4161
+ const diff = max - min;
4162
+ let h = 0;
4163
+ let s = 0;
4164
+ const l = (max + min) / 2;
4165
+ if (diff !== 0) {
4166
+ s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
4167
+ switch (max) {
4168
+ case r:
4169
+ h = (g - b) / diff + (g < b ? 6 : 0);
4170
+ break;
4171
+ case g:
4172
+ h = (b - r) / diff + 2;
4173
+ break;
4174
+ case b:
4175
+ h = (r - g) / diff + 4;
4176
+ break;
4177
+ }
4178
+ h /= 6;
4179
+ }
4180
+ return {
4181
+ h: Math.round(h * 360),
4182
+ s: Math.round(s * 100),
4183
+ l: Math.round(l * 100)
4184
+ };
4185
+ }
4186
+ function formatHSL(hsl, includeHslWrapper = true) {
4187
+ const values = `${hsl.h} ${hsl.s}% ${hsl.l}%`;
4188
+ return includeHslWrapper ? `hsl(${values})` : values;
4189
+ }
4190
+ function formatRGB(rgb) {
4191
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
4192
+ }
4193
+ function formatHex(rgb) {
4194
+ const toHex = (n) => {
4195
+ const hex = Math.round(Math.max(0, Math.min(255, n))).toString(16);
4196
+ return hex.length === 1 ? "0" + hex : hex;
4197
+ };
4198
+ return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
4199
+ }
4200
+ function formatColor(colorInput, outputFormat = "hsl", includeFunctionWrapper = true) {
4201
+ let hsl = parseHSL(colorInput);
4202
+ if (!hsl) {
4203
+ const rgb = parseHex(colorInput);
4204
+ if (rgb) {
4205
+ hsl = rgbToHsl(rgb);
4206
+ }
4207
+ }
4208
+ if (!hsl) {
4209
+ return colorInput;
4210
+ }
4211
+ switch (outputFormat) {
4212
+ case "hsl":
4213
+ return formatHSL(hsl, includeFunctionWrapper);
4214
+ case "rgb":
4215
+ return formatRGB(hslToRgb(hsl));
4216
+ case "hex":
4217
+ return formatHex(hslToRgb(hsl));
4218
+ default:
4219
+ return colorInput;
4220
+ }
4221
+ }
4222
+ function withAlpha(colorInput, alpha) {
4223
+ const hsl = parseHSL(colorInput);
4224
+ if (!hsl) return colorInput;
4225
+ const rgb = hslToRgb(hsl);
4226
+ const clampedAlpha = Math.max(0, Math.min(1, alpha));
4227
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${clampedAlpha})`;
4228
+ }
4229
+
3982
4230
  // src/providers/UnifiedThemeProvider.tsx
3983
4231
  var import_jsx_runtime2 = require("react/jsx-runtime");
3984
4232
  var UnifiedThemeContext = (0, import_react2.createContext)(void 0);
@@ -4001,6 +4249,14 @@ function setStoredMode(mode, storageKey) {
4001
4249
  } catch {
4002
4250
  }
4003
4251
  }
4252
+ function normalizeColorValueToHslTriplet(value) {
4253
+ const trimmed = value.trim();
4254
+ const parsedHsl = parseHSL(trimmed);
4255
+ if (parsedHsl) return formatHSL(parsedHsl, false);
4256
+ const parsedRgb = parseHex(trimmed);
4257
+ if (parsedRgb) return formatHSL(rgbToHsl(parsedRgb), false);
4258
+ return value;
4259
+ }
4004
4260
  function ThemeProvider({
4005
4261
  children,
4006
4262
  defaultMode = "system",
@@ -4176,6 +4432,9 @@ function ThemeProvider({
4176
4432
  value = defaultValues[prop];
4177
4433
  }
4178
4434
  if (value) {
4435
+ if (CSS_PROPERTY_CATEGORIES.colors.includes(prop) || prop === "shadow-color") {
4436
+ value = normalizeColorValueToHslTriplet(String(value));
4437
+ }
4179
4438
  const cssVar = `--${prop}`;
4180
4439
  root.style.setProperty(cssVar, value);
4181
4440
  appliedCount++;
@@ -4461,140 +4720,6 @@ ThemeToggle.displayName = "ThemeToggle";
4461
4720
  // src/components/ThemePresetButtons.tsx
4462
4721
  var import_react4 = require("react");
4463
4722
  var import_clsx2 = require("clsx");
4464
-
4465
- // src/utils/colors.ts
4466
- function parseHSL(hslString) {
4467
- try {
4468
- const cleaned = hslString.replace(/hsl\(|\)/g, "").replace(/[,%]/g, " ").trim();
4469
- const parts = cleaned.split(/\s+/).filter(Boolean);
4470
- if (parts.length !== 3) return null;
4471
- const h = parseFloat(parts[0]) || 0;
4472
- const s = parseFloat(parts[1]) || 0;
4473
- const l = parseFloat(parts[2]) || 0;
4474
- return {
4475
- h: Math.max(0, Math.min(360, h)),
4476
- s: Math.max(0, Math.min(100, s)),
4477
- l: Math.max(0, Math.min(100, l))
4478
- };
4479
- } catch {
4480
- return null;
4481
- }
4482
- }
4483
- function parseHex(hexString) {
4484
- try {
4485
- let hex = hexString.replace("#", "");
4486
- if (hex.length === 3) {
4487
- hex = hex.split("").map((char) => char + char).join("");
4488
- }
4489
- if (hex.length !== 6) return null;
4490
- const r = parseInt(hex.substring(0, 2), 16);
4491
- const s = parseInt(hex.substring(2, 4), 16);
4492
- const l = parseInt(hex.substring(4, 6), 16);
4493
- if (isNaN(r) || isNaN(s) || isNaN(l)) return null;
4494
- return { r, g: s, b: l };
4495
- } catch {
4496
- return null;
4497
- }
4498
- }
4499
- function hslToRgb(hsl) {
4500
- const h = hsl.h / 360;
4501
- const s = hsl.s / 100;
4502
- const l = hsl.l / 100;
4503
- if (s === 0) {
4504
- const gray = Math.round(l * 255);
4505
- return { r: gray, g: gray, b: gray };
4506
- }
4507
- const hue2rgb = (p2, q2, t) => {
4508
- if (t < 0) t += 1;
4509
- if (t > 1) t -= 1;
4510
- if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
4511
- if (t < 1 / 2) return q2;
4512
- if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
4513
- return p2;
4514
- };
4515
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
4516
- const p = 2 * l - q;
4517
- const r = Math.round(hue2rgb(p, q, h + 1 / 3) * 255);
4518
- const g = Math.round(hue2rgb(p, q, h) * 255);
4519
- const b = Math.round(hue2rgb(p, q, h - 1 / 3) * 255);
4520
- return { r, g, b };
4521
- }
4522
- function rgbToHsl(rgb) {
4523
- const r = rgb.r / 255;
4524
- const g = rgb.g / 255;
4525
- const b = rgb.b / 255;
4526
- const max = Math.max(r, g, b);
4527
- const min = Math.min(r, g, b);
4528
- const diff = max - min;
4529
- let h = 0;
4530
- let s = 0;
4531
- const l = (max + min) / 2;
4532
- if (diff !== 0) {
4533
- s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
4534
- switch (max) {
4535
- case r:
4536
- h = (g - b) / diff + (g < b ? 6 : 0);
4537
- break;
4538
- case g:
4539
- h = (b - r) / diff + 2;
4540
- break;
4541
- case b:
4542
- h = (r - g) / diff + 4;
4543
- break;
4544
- }
4545
- h /= 6;
4546
- }
4547
- return {
4548
- h: Math.round(h * 360),
4549
- s: Math.round(s * 100),
4550
- l: Math.round(l * 100)
4551
- };
4552
- }
4553
- function formatHSL(hsl, includeHslWrapper = true) {
4554
- const values = `${hsl.h} ${hsl.s}% ${hsl.l}%`;
4555
- return includeHslWrapper ? `hsl(${values})` : values;
4556
- }
4557
- function formatRGB(rgb) {
4558
- return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
4559
- }
4560
- function formatHex(rgb) {
4561
- const toHex = (n) => {
4562
- const hex = Math.round(Math.max(0, Math.min(255, n))).toString(16);
4563
- return hex.length === 1 ? "0" + hex : hex;
4564
- };
4565
- return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
4566
- }
4567
- function formatColor(colorInput, outputFormat = "hsl", includeFunctionWrapper = true) {
4568
- let hsl = parseHSL(colorInput);
4569
- if (!hsl) {
4570
- const rgb = parseHex(colorInput);
4571
- if (rgb) {
4572
- hsl = rgbToHsl(rgb);
4573
- }
4574
- }
4575
- if (!hsl) {
4576
- return colorInput;
4577
- }
4578
- switch (outputFormat) {
4579
- case "hsl":
4580
- return formatHSL(hsl, includeFunctionWrapper);
4581
- case "rgb":
4582
- return formatRGB(hslToRgb(hsl));
4583
- case "hex":
4584
- return formatHex(hslToRgb(hsl));
4585
- default:
4586
- return colorInput;
4587
- }
4588
- }
4589
- function withAlpha(colorInput, alpha) {
4590
- const hsl = parseHSL(colorInput);
4591
- if (!hsl) return colorInput;
4592
- const rgb = hslToRgb(hsl);
4593
- const clampedAlpha = Math.max(0, Math.min(1, alpha));
4594
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${clampedAlpha})`;
4595
- }
4596
-
4597
- // src/components/ThemePresetButtons.tsx
4598
4723
  var import_jsx_runtime4 = require("react/jsx-runtime");
4599
4724
  var DEFAULT_ANIMATION = {
4600
4725
  enabled: true,
@@ -4617,6 +4742,17 @@ var DEFAULT_LAYOUT = {
4617
4742
  colorBoxCount: 3,
4618
4743
  enableMask: true
4619
4744
  };
4745
+ function getPresetButtonWidthPx(label, layout) {
4746
+ const approxCharWidthPx = 7.25;
4747
+ const maxWidthPx = 360;
4748
+ const dotSizePx = 12;
4749
+ const dotGapPx = 4;
4750
+ const dotsWidthPx = layout.showColorBoxes ? layout.colorBoxCount * dotSizePx + Math.max(0, layout.colorBoxCount - 1) * dotGapPx : 0;
4751
+ const contentPaddingPx = 28;
4752
+ const estimatedTextWidthPx = Math.ceil(label.length * approxCharWidthPx);
4753
+ const estimatedWidthPx = contentPaddingPx + dotsWidthPx + estimatedTextWidthPx;
4754
+ return Math.min(maxWidthPx, Math.max(layout.buttonWidth, estimatedWidthPx));
4755
+ }
4620
4756
  var ColorBox = ({ color, className }) => {
4621
4757
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4622
4758
  "div",
@@ -4637,6 +4773,7 @@ var PresetButton = ({
4637
4773
  }) => {
4638
4774
  const colors = preset.colors[mode];
4639
4775
  const label = preset.name.replace(/-/g, " ");
4776
+ const buttonWidth = Math.max(layout.buttonWidth, Number(preset.metadata?.buttonWidth ?? 0) || layout.buttonWidth);
4640
4777
  if (renderPreset) {
4641
4778
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4642
4779
  "div",
@@ -4648,7 +4785,7 @@ var PresetButton = ({
4648
4785
  }
4649
4786
  );
4650
4787
  }
4651
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-shrink-0", style: { minWidth: layout.buttonWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4788
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-shrink-0", style: { width: buttonWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4652
4789
  "button",
4653
4790
  {
4654
4791
  type: "button",
@@ -4680,7 +4817,7 @@ var AnimatedRow = ({
4680
4817
  }) => {
4681
4818
  if (presets.length === 0) return null;
4682
4819
  const duplicatedPresets = Array(animation.duplicationFactor).fill(presets).flat();
4683
- const totalWidth = presets.length * (layout.buttonWidth + layout.buttonGap);
4820
+ const totalWidth = presets.reduce((sum, preset) => sum + (Number(preset.metadata?.buttonWidth) || layout.buttonWidth), 0) + presets.length * layout.buttonGap;
4684
4821
  const effectiveScrollSpeed = Math.max(0.1, animation.scrollSpeed || 1);
4685
4822
  const animationDuration = presets.length * animation.duration / effectiveScrollSpeed;
4686
4823
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -4760,6 +4897,7 @@ var ThemePresetButtons = ({
4760
4897
  dark: preset.styles.dark
4761
4898
  },
4762
4899
  metadata: {
4900
+ buttonWidth: getPresetButtonWidthPx(preset.label, layout),
4763
4901
  category: preset.label.toLowerCase().includes("minimal") ? "minimal" : preset.label.toLowerCase().includes("violet") || preset.label.toLowerCase().includes("purple") ? "vibrant" : "modern",
4764
4902
  tags: [preset.label.toLowerCase().replace(/\s+/g, "-")],
4765
4903
  createdAt: preset.createdAt,
@@ -4778,6 +4916,7 @@ var ThemePresetButtons = ({
4778
4916
  dark: preset.styles.dark
4779
4917
  },
4780
4918
  metadata: {
4919
+ buttonWidth: getPresetButtonWidthPx(preset.label, layout),
4781
4920
  category: preset.label.toLowerCase().includes("minimal") ? "minimal" : preset.label.toLowerCase().includes("violet") || preset.label.toLowerCase().includes("purple") ? "vibrant" : "modern",
4782
4921
  tags: [preset.label.toLowerCase().replace(/\s+/g, "-")],
4783
4922
  createdAt: preset.createdAt,
@@ -4802,7 +4941,7 @@ var ThemePresetButtons = ({
4802
4941
  } finally {
4803
4942
  setLoading(false);
4804
4943
  }
4805
- }, [availablePresets, builtInPresets, customPresets, categories, maxPresets, showBuiltIn, showCustom]);
4944
+ }, [availablePresets, builtInPresets, customPresets, categories, maxPresets, showBuiltIn, showCustom, layout]);
4806
4945
  (0, import_react4.useEffect)(() => {
4807
4946
  loadPresets();
4808
4947
  }, [loadPresets]);
package/dist/index.mjs CHANGED
@@ -3827,7 +3827,15 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3827
3827
  'destructive', 'destructive-foreground', 'border', 'input', 'ring',
3828
3828
  'chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5',
3829
3829
  'sidebar', 'sidebar-foreground', 'sidebar-primary', 'sidebar-primary-foreground',
3830
- 'sidebar-accent', 'sidebar-accent-foreground', 'sidebar-border', 'sidebar-ring'
3830
+ 'sidebar-accent', 'sidebar-accent-foreground', 'sidebar-border', 'sidebar-ring',
3831
+ // Semantic accent colors for status and feedback
3832
+ 'accent-info', 'accent-info-foreground',
3833
+ 'accent-success', 'accent-success-foreground',
3834
+ 'accent-warning', 'accent-warning-foreground',
3835
+ 'accent-danger', 'accent-danger-foreground',
3836
+ 'accent-brand', 'accent-brand-foreground',
3837
+ 'accent-feature', 'accent-feature-foreground',
3838
+ 'accent-highlight', 'accent-highlight-foreground'
3831
3839
  ],
3832
3840
  typography: ['font-sans', 'font-serif', 'font-mono'],
3833
3841
  layout: ['radius'],
@@ -3835,6 +3843,106 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3835
3843
  spacing: ['letter-spacing', 'spacing']
3836
3844
  };
3837
3845
 
3846
+ // Normalize hex/rgb/hsl() colors into "H S% L%" for hsl(var(--token)) usage.
3847
+ function normalizeColorValueToHslTriplet(value) {
3848
+ if (!value) return value;
3849
+ const trimmed = String(value).trim();
3850
+ if (!trimmed) return value;
3851
+ if (trimmed.startsWith('var(')) return trimmed;
3852
+
3853
+ // Already a triplet: "210 40% 98%"
3854
+ if (/^\\d+(?:\\.\\d+)?\\s+\\d+(?:\\.\\d+)?%\\s+\\d+(?:\\.\\d+)?%$/.test(trimmed)) {
3855
+ return trimmed;
3856
+ }
3857
+
3858
+ function clamp(n, min, max) {
3859
+ return Math.min(max, Math.max(min, n));
3860
+ }
3861
+
3862
+ function rgbToHsl(r, g, b) {
3863
+ r /= 255;
3864
+ g /= 255;
3865
+ b /= 255;
3866
+ const max = Math.max(r, g, b);
3867
+ const min = Math.min(r, g, b);
3868
+ const diff = max - min;
3869
+ let h = 0;
3870
+ let s = 0;
3871
+ const l = (max + min) / 2;
3872
+
3873
+ if (diff !== 0) {
3874
+ s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
3875
+ switch (max) {
3876
+ case r:
3877
+ h = (g - b) / diff + (g < b ? 6 : 0);
3878
+ break;
3879
+ case g:
3880
+ h = (b - r) / diff + 2;
3881
+ break;
3882
+ case b:
3883
+ h = (r - g) / diff + 4;
3884
+ break;
3885
+ }
3886
+ h /= 6;
3887
+ }
3888
+
3889
+ return {
3890
+ h: Math.round(h * 360),
3891
+ s: Math.round(s * 100),
3892
+ l: Math.round(l * 100),
3893
+ };
3894
+ }
3895
+
3896
+ function hexToRgb(hex) {
3897
+ let clean = hex.replace('#', '').trim();
3898
+ if (clean.length === 3) clean = clean.split('').map(function(c) { return c + c; }).join('');
3899
+ if (clean.length === 8) clean = clean.substring(0, 6); // ignore alpha
3900
+ if (clean.length !== 6) return null;
3901
+
3902
+ const r = parseInt(clean.substring(0, 2), 16);
3903
+ const g = parseInt(clean.substring(2, 4), 16);
3904
+ const b = parseInt(clean.substring(4, 6), 16);
3905
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
3906
+ return { r: r, g: g, b: b };
3907
+ }
3908
+
3909
+ function toTriplet(hsl) {
3910
+ return String(clamp(hsl.h, 0, 360)) + ' ' + String(clamp(hsl.s, 0, 100)) + '% ' + String(clamp(hsl.l, 0, 100)) + '%';
3911
+ }
3912
+
3913
+ // hsl(...) or raw "H S% L%"
3914
+ if (/^hsl\\(/i.test(trimmed)) {
3915
+ const cleaned = trimmed.replace(/hsl\\(|\\)/gi, '').replace(/[,%]/g, ' ').trim();
3916
+ const parts = cleaned.split(/\\s+/).filter(Boolean);
3917
+ if (parts.length === 3) {
3918
+ const h = parseFloat(parts[0]) || 0;
3919
+ const s = parseFloat(parts[1]) || 0;
3920
+ const l = parseFloat(parts[2]) || 0;
3921
+ return toTriplet({ h: h, s: s, l: l });
3922
+ }
3923
+ }
3924
+
3925
+ // rgb(...)
3926
+ if (/^rgb\\(/i.test(trimmed)) {
3927
+ const cleaned = trimmed.replace(/rgb\\(|\\)/gi, '').trim();
3928
+ const parts = cleaned.split(',').map(function(p) { return p.trim(); });
3929
+ if (parts.length === 3) {
3930
+ const r = clamp(parseFloat(parts[0]) || 0, 0, 255);
3931
+ const g = clamp(parseFloat(parts[1]) || 0, 0, 255);
3932
+ const b = clamp(parseFloat(parts[2]) || 0, 0, 255);
3933
+ return toTriplet(rgbToHsl(r, g, b));
3934
+ }
3935
+ }
3936
+
3937
+ // hex
3938
+ if (trimmed[0] === '#') {
3939
+ const rgb = hexToRgb(trimmed);
3940
+ if (rgb) return toTriplet(rgbToHsl(rgb.r, rgb.g, rgb.b));
3941
+ }
3942
+
3943
+ return value;
3944
+ }
3945
+
3838
3946
  // Function to apply all preset properties - with proper clearing and defaults
3839
3947
  function applyPresetProperties(colors) {
3840
3948
  if (!colors) return;
@@ -3866,6 +3974,10 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3866
3974
 
3867
3975
  // Apply all properties with defaults for missing ones
3868
3976
  let appliedCount = 0;
3977
+ const colorProps = {};
3978
+ CSS_CATEGORIES.colors.forEach(function(prop) { colorProps[prop] = true; });
3979
+ colorProps['shadow-color'] = true;
3980
+
3869
3981
  allProperties.forEach(function(prop) {
3870
3982
  let value = colors[prop];
3871
3983
 
@@ -3875,6 +3987,10 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3875
3987
  }
3876
3988
 
3877
3989
  if (value) {
3990
+ if (colorProps[prop]) {
3991
+ value = normalizeColorValueToHslTriplet(value);
3992
+ }
3993
+
3878
3994
  const cssVar = '--' + prop;
3879
3995
  // Apply directly like TweakCN does - no conversion, no !important
3880
3996
  root.style.setProperty(cssVar, value);
@@ -3936,6 +4052,138 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3936
4052
  return /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: scriptContent }, suppressHydrationWarning: true });
3937
4053
  }
3938
4054
 
4055
+ // src/utils/colors.ts
4056
+ function parseHSL(hslString) {
4057
+ try {
4058
+ const cleaned = hslString.replace(/hsl\(|\)/g, "").replace(/[,%]/g, " ").trim();
4059
+ const parts = cleaned.split(/\s+/).filter(Boolean);
4060
+ if (parts.length !== 3) return null;
4061
+ const h = parseFloat(parts[0]) || 0;
4062
+ const s = parseFloat(parts[1]) || 0;
4063
+ const l = parseFloat(parts[2]) || 0;
4064
+ return {
4065
+ h: Math.max(0, Math.min(360, h)),
4066
+ s: Math.max(0, Math.min(100, s)),
4067
+ l: Math.max(0, Math.min(100, l))
4068
+ };
4069
+ } catch {
4070
+ return null;
4071
+ }
4072
+ }
4073
+ function parseHex(hexString) {
4074
+ try {
4075
+ let hex = hexString.replace("#", "");
4076
+ if (hex.length === 3) {
4077
+ hex = hex.split("").map((char) => char + char).join("");
4078
+ }
4079
+ if (hex.length !== 6) return null;
4080
+ const r = parseInt(hex.substring(0, 2), 16);
4081
+ const s = parseInt(hex.substring(2, 4), 16);
4082
+ const l = parseInt(hex.substring(4, 6), 16);
4083
+ if (isNaN(r) || isNaN(s) || isNaN(l)) return null;
4084
+ return { r, g: s, b: l };
4085
+ } catch {
4086
+ return null;
4087
+ }
4088
+ }
4089
+ function hslToRgb(hsl) {
4090
+ const h = hsl.h / 360;
4091
+ const s = hsl.s / 100;
4092
+ const l = hsl.l / 100;
4093
+ if (s === 0) {
4094
+ const gray = Math.round(l * 255);
4095
+ return { r: gray, g: gray, b: gray };
4096
+ }
4097
+ const hue2rgb = (p2, q2, t) => {
4098
+ if (t < 0) t += 1;
4099
+ if (t > 1) t -= 1;
4100
+ if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
4101
+ if (t < 1 / 2) return q2;
4102
+ if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
4103
+ return p2;
4104
+ };
4105
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
4106
+ const p = 2 * l - q;
4107
+ const r = Math.round(hue2rgb(p, q, h + 1 / 3) * 255);
4108
+ const g = Math.round(hue2rgb(p, q, h) * 255);
4109
+ const b = Math.round(hue2rgb(p, q, h - 1 / 3) * 255);
4110
+ return { r, g, b };
4111
+ }
4112
+ function rgbToHsl(rgb) {
4113
+ const r = rgb.r / 255;
4114
+ const g = rgb.g / 255;
4115
+ const b = rgb.b / 255;
4116
+ const max = Math.max(r, g, b);
4117
+ const min = Math.min(r, g, b);
4118
+ const diff = max - min;
4119
+ let h = 0;
4120
+ let s = 0;
4121
+ const l = (max + min) / 2;
4122
+ if (diff !== 0) {
4123
+ s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
4124
+ switch (max) {
4125
+ case r:
4126
+ h = (g - b) / diff + (g < b ? 6 : 0);
4127
+ break;
4128
+ case g:
4129
+ h = (b - r) / diff + 2;
4130
+ break;
4131
+ case b:
4132
+ h = (r - g) / diff + 4;
4133
+ break;
4134
+ }
4135
+ h /= 6;
4136
+ }
4137
+ return {
4138
+ h: Math.round(h * 360),
4139
+ s: Math.round(s * 100),
4140
+ l: Math.round(l * 100)
4141
+ };
4142
+ }
4143
+ function formatHSL(hsl, includeHslWrapper = true) {
4144
+ const values = `${hsl.h} ${hsl.s}% ${hsl.l}%`;
4145
+ return includeHslWrapper ? `hsl(${values})` : values;
4146
+ }
4147
+ function formatRGB(rgb) {
4148
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
4149
+ }
4150
+ function formatHex(rgb) {
4151
+ const toHex = (n) => {
4152
+ const hex = Math.round(Math.max(0, Math.min(255, n))).toString(16);
4153
+ return hex.length === 1 ? "0" + hex : hex;
4154
+ };
4155
+ return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
4156
+ }
4157
+ function formatColor(colorInput, outputFormat = "hsl", includeFunctionWrapper = true) {
4158
+ let hsl = parseHSL(colorInput);
4159
+ if (!hsl) {
4160
+ const rgb = parseHex(colorInput);
4161
+ if (rgb) {
4162
+ hsl = rgbToHsl(rgb);
4163
+ }
4164
+ }
4165
+ if (!hsl) {
4166
+ return colorInput;
4167
+ }
4168
+ switch (outputFormat) {
4169
+ case "hsl":
4170
+ return formatHSL(hsl, includeFunctionWrapper);
4171
+ case "rgb":
4172
+ return formatRGB(hslToRgb(hsl));
4173
+ case "hex":
4174
+ return formatHex(hslToRgb(hsl));
4175
+ default:
4176
+ return colorInput;
4177
+ }
4178
+ }
4179
+ function withAlpha(colorInput, alpha) {
4180
+ const hsl = parseHSL(colorInput);
4181
+ if (!hsl) return colorInput;
4182
+ const rgb = hslToRgb(hsl);
4183
+ const clampedAlpha = Math.max(0, Math.min(1, alpha));
4184
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${clampedAlpha})`;
4185
+ }
4186
+
3939
4187
  // src/providers/UnifiedThemeProvider.tsx
3940
4188
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
3941
4189
  var UnifiedThemeContext = createContext(void 0);
@@ -3958,6 +4206,14 @@ function setStoredMode(mode, storageKey) {
3958
4206
  } catch {
3959
4207
  }
3960
4208
  }
4209
+ function normalizeColorValueToHslTriplet(value) {
4210
+ const trimmed = value.trim();
4211
+ const parsedHsl = parseHSL(trimmed);
4212
+ if (parsedHsl) return formatHSL(parsedHsl, false);
4213
+ const parsedRgb = parseHex(trimmed);
4214
+ if (parsedRgb) return formatHSL(rgbToHsl(parsedRgb), false);
4215
+ return value;
4216
+ }
3961
4217
  function ThemeProvider({
3962
4218
  children,
3963
4219
  defaultMode = "system",
@@ -4133,6 +4389,9 @@ function ThemeProvider({
4133
4389
  value = defaultValues[prop];
4134
4390
  }
4135
4391
  if (value) {
4392
+ if (CSS_PROPERTY_CATEGORIES.colors.includes(prop) || prop === "shadow-color") {
4393
+ value = normalizeColorValueToHslTriplet(String(value));
4394
+ }
4136
4395
  const cssVar = `--${prop}`;
4137
4396
  root.style.setProperty(cssVar, value);
4138
4397
  appliedCount++;
@@ -4418,140 +4677,6 @@ ThemeToggle.displayName = "ThemeToggle";
4418
4677
  // src/components/ThemePresetButtons.tsx
4419
4678
  import { useEffect as useEffect2, useState as useState2, useCallback as useCallback2, useMemo as useMemo3 } from "react";
4420
4679
  import { clsx as clsx2 } from "clsx";
4421
-
4422
- // src/utils/colors.ts
4423
- function parseHSL(hslString) {
4424
- try {
4425
- const cleaned = hslString.replace(/hsl\(|\)/g, "").replace(/[,%]/g, " ").trim();
4426
- const parts = cleaned.split(/\s+/).filter(Boolean);
4427
- if (parts.length !== 3) return null;
4428
- const h = parseFloat(parts[0]) || 0;
4429
- const s = parseFloat(parts[1]) || 0;
4430
- const l = parseFloat(parts[2]) || 0;
4431
- return {
4432
- h: Math.max(0, Math.min(360, h)),
4433
- s: Math.max(0, Math.min(100, s)),
4434
- l: Math.max(0, Math.min(100, l))
4435
- };
4436
- } catch {
4437
- return null;
4438
- }
4439
- }
4440
- function parseHex(hexString) {
4441
- try {
4442
- let hex = hexString.replace("#", "");
4443
- if (hex.length === 3) {
4444
- hex = hex.split("").map((char) => char + char).join("");
4445
- }
4446
- if (hex.length !== 6) return null;
4447
- const r = parseInt(hex.substring(0, 2), 16);
4448
- const s = parseInt(hex.substring(2, 4), 16);
4449
- const l = parseInt(hex.substring(4, 6), 16);
4450
- if (isNaN(r) || isNaN(s) || isNaN(l)) return null;
4451
- return { r, g: s, b: l };
4452
- } catch {
4453
- return null;
4454
- }
4455
- }
4456
- function hslToRgb(hsl) {
4457
- const h = hsl.h / 360;
4458
- const s = hsl.s / 100;
4459
- const l = hsl.l / 100;
4460
- if (s === 0) {
4461
- const gray = Math.round(l * 255);
4462
- return { r: gray, g: gray, b: gray };
4463
- }
4464
- const hue2rgb = (p2, q2, t) => {
4465
- if (t < 0) t += 1;
4466
- if (t > 1) t -= 1;
4467
- if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
4468
- if (t < 1 / 2) return q2;
4469
- if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
4470
- return p2;
4471
- };
4472
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
4473
- const p = 2 * l - q;
4474
- const r = Math.round(hue2rgb(p, q, h + 1 / 3) * 255);
4475
- const g = Math.round(hue2rgb(p, q, h) * 255);
4476
- const b = Math.round(hue2rgb(p, q, h - 1 / 3) * 255);
4477
- return { r, g, b };
4478
- }
4479
- function rgbToHsl(rgb) {
4480
- const r = rgb.r / 255;
4481
- const g = rgb.g / 255;
4482
- const b = rgb.b / 255;
4483
- const max = Math.max(r, g, b);
4484
- const min = Math.min(r, g, b);
4485
- const diff = max - min;
4486
- let h = 0;
4487
- let s = 0;
4488
- const l = (max + min) / 2;
4489
- if (diff !== 0) {
4490
- s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
4491
- switch (max) {
4492
- case r:
4493
- h = (g - b) / diff + (g < b ? 6 : 0);
4494
- break;
4495
- case g:
4496
- h = (b - r) / diff + 2;
4497
- break;
4498
- case b:
4499
- h = (r - g) / diff + 4;
4500
- break;
4501
- }
4502
- h /= 6;
4503
- }
4504
- return {
4505
- h: Math.round(h * 360),
4506
- s: Math.round(s * 100),
4507
- l: Math.round(l * 100)
4508
- };
4509
- }
4510
- function formatHSL(hsl, includeHslWrapper = true) {
4511
- const values = `${hsl.h} ${hsl.s}% ${hsl.l}%`;
4512
- return includeHslWrapper ? `hsl(${values})` : values;
4513
- }
4514
- function formatRGB(rgb) {
4515
- return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
4516
- }
4517
- function formatHex(rgb) {
4518
- const toHex = (n) => {
4519
- const hex = Math.round(Math.max(0, Math.min(255, n))).toString(16);
4520
- return hex.length === 1 ? "0" + hex : hex;
4521
- };
4522
- return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
4523
- }
4524
- function formatColor(colorInput, outputFormat = "hsl", includeFunctionWrapper = true) {
4525
- let hsl = parseHSL(colorInput);
4526
- if (!hsl) {
4527
- const rgb = parseHex(colorInput);
4528
- if (rgb) {
4529
- hsl = rgbToHsl(rgb);
4530
- }
4531
- }
4532
- if (!hsl) {
4533
- return colorInput;
4534
- }
4535
- switch (outputFormat) {
4536
- case "hsl":
4537
- return formatHSL(hsl, includeFunctionWrapper);
4538
- case "rgb":
4539
- return formatRGB(hslToRgb(hsl));
4540
- case "hex":
4541
- return formatHex(hslToRgb(hsl));
4542
- default:
4543
- return colorInput;
4544
- }
4545
- }
4546
- function withAlpha(colorInput, alpha) {
4547
- const hsl = parseHSL(colorInput);
4548
- if (!hsl) return colorInput;
4549
- const rgb = hslToRgb(hsl);
4550
- const clampedAlpha = Math.max(0, Math.min(1, alpha));
4551
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${clampedAlpha})`;
4552
- }
4553
-
4554
- // src/components/ThemePresetButtons.tsx
4555
4680
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
4556
4681
  var DEFAULT_ANIMATION = {
4557
4682
  enabled: true,
@@ -4574,6 +4699,17 @@ var DEFAULT_LAYOUT = {
4574
4699
  colorBoxCount: 3,
4575
4700
  enableMask: true
4576
4701
  };
4702
+ function getPresetButtonWidthPx(label, layout) {
4703
+ const approxCharWidthPx = 7.25;
4704
+ const maxWidthPx = 360;
4705
+ const dotSizePx = 12;
4706
+ const dotGapPx = 4;
4707
+ const dotsWidthPx = layout.showColorBoxes ? layout.colorBoxCount * dotSizePx + Math.max(0, layout.colorBoxCount - 1) * dotGapPx : 0;
4708
+ const contentPaddingPx = 28;
4709
+ const estimatedTextWidthPx = Math.ceil(label.length * approxCharWidthPx);
4710
+ const estimatedWidthPx = contentPaddingPx + dotsWidthPx + estimatedTextWidthPx;
4711
+ return Math.min(maxWidthPx, Math.max(layout.buttonWidth, estimatedWidthPx));
4712
+ }
4577
4713
  var ColorBox = ({ color, className }) => {
4578
4714
  return /* @__PURE__ */ jsx4(
4579
4715
  "div",
@@ -4594,6 +4730,7 @@ var PresetButton = ({
4594
4730
  }) => {
4595
4731
  const colors = preset.colors[mode];
4596
4732
  const label = preset.name.replace(/-/g, " ");
4733
+ const buttonWidth = Math.max(layout.buttonWidth, Number(preset.metadata?.buttonWidth ?? 0) || layout.buttonWidth);
4597
4734
  if (renderPreset) {
4598
4735
  return /* @__PURE__ */ jsx4(
4599
4736
  "div",
@@ -4605,7 +4742,7 @@ var PresetButton = ({
4605
4742
  }
4606
4743
  );
4607
4744
  }
4608
- return /* @__PURE__ */ jsx4("div", { className: "flex-shrink-0", style: { minWidth: layout.buttonWidth }, children: /* @__PURE__ */ jsxs3(
4745
+ return /* @__PURE__ */ jsx4("div", { className: "flex-shrink-0", style: { width: buttonWidth }, children: /* @__PURE__ */ jsxs3(
4609
4746
  "button",
4610
4747
  {
4611
4748
  type: "button",
@@ -4637,7 +4774,7 @@ var AnimatedRow = ({
4637
4774
  }) => {
4638
4775
  if (presets.length === 0) return null;
4639
4776
  const duplicatedPresets = Array(animation.duplicationFactor).fill(presets).flat();
4640
- const totalWidth = presets.length * (layout.buttonWidth + layout.buttonGap);
4777
+ const totalWidth = presets.reduce((sum, preset) => sum + (Number(preset.metadata?.buttonWidth) || layout.buttonWidth), 0) + presets.length * layout.buttonGap;
4641
4778
  const effectiveScrollSpeed = Math.max(0.1, animation.scrollSpeed || 1);
4642
4779
  const animationDuration = presets.length * animation.duration / effectiveScrollSpeed;
4643
4780
  return /* @__PURE__ */ jsx4(
@@ -4717,6 +4854,7 @@ var ThemePresetButtons = ({
4717
4854
  dark: preset.styles.dark
4718
4855
  },
4719
4856
  metadata: {
4857
+ buttonWidth: getPresetButtonWidthPx(preset.label, layout),
4720
4858
  category: preset.label.toLowerCase().includes("minimal") ? "minimal" : preset.label.toLowerCase().includes("violet") || preset.label.toLowerCase().includes("purple") ? "vibrant" : "modern",
4721
4859
  tags: [preset.label.toLowerCase().replace(/\s+/g, "-")],
4722
4860
  createdAt: preset.createdAt,
@@ -4735,6 +4873,7 @@ var ThemePresetButtons = ({
4735
4873
  dark: preset.styles.dark
4736
4874
  },
4737
4875
  metadata: {
4876
+ buttonWidth: getPresetButtonWidthPx(preset.label, layout),
4738
4877
  category: preset.label.toLowerCase().includes("minimal") ? "minimal" : preset.label.toLowerCase().includes("violet") || preset.label.toLowerCase().includes("purple") ? "vibrant" : "modern",
4739
4878
  tags: [preset.label.toLowerCase().replace(/\s+/g, "-")],
4740
4879
  createdAt: preset.createdAt,
@@ -4759,7 +4898,7 @@ var ThemePresetButtons = ({
4759
4898
  } finally {
4760
4899
  setLoading(false);
4761
4900
  }
4762
- }, [availablePresets, builtInPresets, customPresets, categories, maxPresets, showBuiltIn, showCustom]);
4901
+ }, [availablePresets, builtInPresets, customPresets, categories, maxPresets, showBuiltIn, showCustom, layout]);
4763
4902
  useEffect2(() => {
4764
4903
  loadPresets();
4765
4904
  }, [loadPresets]);
@@ -95,10 +95,10 @@
95
95
  }
96
96
 
97
97
  .theme-preset-button__label {
98
- display: -webkit-box;
99
- -webkit-box-orient: vertical;
100
- -webkit-line-clamp: 2;
98
+ display: block;
99
+ white-space: nowrap;
101
100
  overflow: hidden;
101
+ text-overflow: ellipsis;
102
102
 
103
103
  line-height: 1.1;
104
104
  font-size: 0.875rem;
@@ -7,58 +7,58 @@
7
7
 
8
8
  @theme inline {
9
9
  /* Color mappings - Bridge between CSS variables and Tailwind classes */
10
- --color-background: var(--background);
11
- --color-foreground: var(--foreground);
12
- --color-card: var(--card);
13
- --color-card-foreground: var(--card-foreground);
14
- --color-popover: var(--popover);
15
- --color-popover-foreground: var(--popover-foreground);
16
- --color-primary: var(--primary);
17
- --color-primary-foreground: var(--primary-foreground);
18
- --color-secondary: var(--secondary);
19
- --color-secondary-foreground: var(--secondary-foreground);
20
- --color-muted: var(--muted);
21
- --color-muted-foreground: var(--muted-foreground);
22
- --color-accent: var(--accent);
23
- --color-accent-foreground: var(--accent-foreground);
24
- --color-destructive: var(--destructive);
25
- --color-destructive-foreground: var(--destructive-foreground);
26
- --color-border: var(--border);
27
- --color-input: var(--input);
28
- --color-ring: var(--ring);
10
+ --color-background: hsl(var(--background));
11
+ --color-foreground: hsl(var(--foreground));
12
+ --color-card: hsl(var(--card));
13
+ --color-card-foreground: hsl(var(--card-foreground));
14
+ --color-popover: hsl(var(--popover));
15
+ --color-popover-foreground: hsl(var(--popover-foreground));
16
+ --color-primary: hsl(var(--primary));
17
+ --color-primary-foreground: hsl(var(--primary-foreground));
18
+ --color-secondary: hsl(var(--secondary));
19
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
20
+ --color-muted: hsl(var(--muted));
21
+ --color-muted-foreground: hsl(var(--muted-foreground));
22
+ --color-accent: hsl(var(--accent));
23
+ --color-accent-foreground: hsl(var(--accent-foreground));
24
+ --color-destructive: hsl(var(--destructive));
25
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
26
+ --color-border: hsl(var(--border));
27
+ --color-input: hsl(var(--input));
28
+ --color-ring: hsl(var(--ring));
29
29
 
30
30
  /* Chart colors for data visualization */
31
- --color-chart-1: var(--chart-1);
32
- --color-chart-2: var(--chart-2);
33
- --color-chart-3: var(--chart-3);
34
- --color-chart-4: var(--chart-4);
35
- --color-chart-5: var(--chart-5);
31
+ --color-chart-1: hsl(var(--chart-1));
32
+ --color-chart-2: hsl(var(--chart-2));
33
+ --color-chart-3: hsl(var(--chart-3));
34
+ --color-chart-4: hsl(var(--chart-4));
35
+ --color-chart-5: hsl(var(--chart-5));
36
36
 
37
37
  /* Sidebar colors for dashboard layouts */
38
- --color-sidebar: var(--sidebar);
39
- --color-sidebar-foreground: var(--sidebar-foreground);
40
- --color-sidebar-primary: var(--sidebar-primary);
41
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
42
- --color-sidebar-accent: var(--sidebar-accent);
43
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
44
- --color-sidebar-border: var(--sidebar-border);
45
- --color-sidebar-ring: var(--sidebar-ring);
38
+ --color-sidebar: hsl(var(--sidebar));
39
+ --color-sidebar-foreground: hsl(var(--sidebar-foreground));
40
+ --color-sidebar-primary: hsl(var(--sidebar-primary));
41
+ --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
42
+ --color-sidebar-accent: hsl(var(--sidebar-accent));
43
+ --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
44
+ --color-sidebar-border: hsl(var(--sidebar-border));
45
+ --color-sidebar-ring: hsl(var(--sidebar-ring));
46
46
 
47
47
  /* Semantic accent colors for status and feedback */
48
- --color-accent-info: var(--accent-info);
49
- --color-accent-info-foreground: var(--accent-info-foreground);
50
- --color-accent-success: var(--accent-success);
51
- --color-accent-success-foreground: var(--accent-success-foreground);
52
- --color-accent-warning: var(--accent-warning);
53
- --color-accent-warning-foreground: var(--accent-warning-foreground);
54
- --color-accent-danger: var(--accent-danger);
55
- --color-accent-danger-foreground: var(--accent-danger-foreground);
56
- --color-accent-brand: var(--accent-brand);
57
- --color-accent-brand-foreground: var(--accent-brand-foreground);
58
- --color-accent-feature: var(--accent-feature);
59
- --color-accent-feature-foreground: var(--accent-feature-foreground);
60
- --color-accent-highlight: var(--accent-highlight);
61
- --color-accent-highlight-foreground: var(--accent-highlight-foreground);
48
+ --color-accent-info: hsl(var(--accent-info));
49
+ --color-accent-info-foreground: hsl(var(--accent-info-foreground));
50
+ --color-accent-success: hsl(var(--accent-success));
51
+ --color-accent-success-foreground: hsl(var(--accent-success-foreground));
52
+ --color-accent-warning: hsl(var(--accent-warning));
53
+ --color-accent-warning-foreground: hsl(var(--accent-warning-foreground));
54
+ --color-accent-danger: hsl(var(--accent-danger));
55
+ --color-accent-danger-foreground: hsl(var(--accent-danger-foreground));
56
+ --color-accent-brand: hsl(var(--accent-brand));
57
+ --color-accent-brand-foreground: hsl(var(--accent-brand-foreground));
58
+ --color-accent-feature: hsl(var(--accent-feature));
59
+ --color-accent-feature-foreground: hsl(var(--accent-feature-foreground));
60
+ --color-accent-highlight: hsl(var(--accent-highlight));
61
+ --color-accent-highlight-foreground: hsl(var(--accent-highlight-foreground));
62
62
 
63
63
  /* Dynamic border radius system */
64
64
  --radius-sm: calc(var(--radius) - 4px);
@@ -83,7 +83,7 @@
83
83
  --size-spacing: var(--spacing);
84
84
 
85
85
  /* Shadow system integration */
86
- --color-shadow: var(--shadow-color);
86
+ --color-shadow: hsl(var(--shadow-color));
87
87
  --shadow-opacity: var(--shadow-opacity);
88
88
  --shadow-blur: var(--shadow-blur);
89
89
  --shadow-spread: var(--shadow-spread);
@@ -118,4 +118,4 @@
118
118
  -webkit-font-smoothing: antialiased;
119
119
  -moz-osx-font-smoothing: grayscale;
120
120
  }
121
- }
121
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fakhrirafiki/theme-engine",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
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",