@fakhrirafiki/theme-engine 0.4.5 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +281 -115
- package/dist/index.d.mts +65 -26
- package/dist/index.d.ts +65 -26
- package/dist/index.js +132 -65
- package/dist/index.mjs +131 -65
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3940,6 +3940,29 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
|
|
|
3940
3940
|
if (rgb) return toTriplet(rgbToHsl(rgb.r, rgb.g, rgb.b));
|
|
3941
3941
|
}
|
|
3942
3942
|
|
|
3943
|
+
// Any other CSS color (e.g. oklch(...), named colors) -> resolve via computed styles.
|
|
3944
|
+
try {
|
|
3945
|
+
const probe = document.createElement('span');
|
|
3946
|
+
probe.style.color = trimmed;
|
|
3947
|
+
probe.style.position = 'absolute';
|
|
3948
|
+
probe.style.left = '-9999px';
|
|
3949
|
+
probe.style.top = '-9999px';
|
|
3950
|
+
probe.style.visibility = 'hidden';
|
|
3951
|
+
document.documentElement.appendChild(probe);
|
|
3952
|
+
const computed = getComputedStyle(probe).color; // rgb(...) or rgba(...)
|
|
3953
|
+
probe.remove();
|
|
3954
|
+
|
|
3955
|
+
const match = computed && computed.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/i);
|
|
3956
|
+
if (match) {
|
|
3957
|
+
const r = clamp(parseFloat(match[1]) || 0, 0, 255);
|
|
3958
|
+
const g = clamp(parseFloat(match[2]) || 0, 0, 255);
|
|
3959
|
+
const b = clamp(parseFloat(match[3]) || 0, 0, 255);
|
|
3960
|
+
return toTriplet(rgbToHsl(r, g, b));
|
|
3961
|
+
}
|
|
3962
|
+
} catch (e) {
|
|
3963
|
+
// ignore and fall through
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3943
3966
|
return value;
|
|
3944
3967
|
}
|
|
3945
3968
|
|
|
@@ -4212,23 +4235,40 @@ function normalizeColorValueToHslTriplet(value) {
|
|
|
4212
4235
|
if (parsedHsl) return formatHSL(parsedHsl, false);
|
|
4213
4236
|
const parsedRgb = parseHex(trimmed);
|
|
4214
4237
|
if (parsedRgb) return formatHSL(rgbToHsl(parsedRgb), false);
|
|
4215
|
-
|
|
4238
|
+
if (typeof document !== "undefined") {
|
|
4239
|
+
try {
|
|
4240
|
+
if (trimmed.startsWith("var(")) return trimmed;
|
|
4241
|
+
const probe = document.createElement("span");
|
|
4242
|
+
probe.style.color = trimmed;
|
|
4243
|
+
probe.style.position = "absolute";
|
|
4244
|
+
probe.style.left = "-9999px";
|
|
4245
|
+
probe.style.top = "-9999px";
|
|
4246
|
+
probe.style.visibility = "hidden";
|
|
4247
|
+
document.documentElement.appendChild(probe);
|
|
4248
|
+
const computed = getComputedStyle(probe).color;
|
|
4249
|
+
probe.remove();
|
|
4250
|
+
const match = computed.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
|
|
4251
|
+
if (match) {
|
|
4252
|
+
const r = Number(match[1]);
|
|
4253
|
+
const g = Number(match[2]);
|
|
4254
|
+
const b = Number(match[3]);
|
|
4255
|
+
if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {
|
|
4256
|
+
return formatHSL(rgbToHsl({ r, g, b }), false);
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
} catch {
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
return trimmed;
|
|
4216
4263
|
}
|
|
4217
4264
|
function ThemeProvider({
|
|
4218
4265
|
children,
|
|
4219
4266
|
defaultMode = "system",
|
|
4220
4267
|
defaultPreset,
|
|
4221
|
-
enableTransitions = true,
|
|
4222
4268
|
modeStorageKey = THEME_STORAGE_KEY,
|
|
4223
4269
|
presetStorageKey = "theme-preset",
|
|
4224
|
-
|
|
4225
|
-
customPresets,
|
|
4226
|
-
includeBuiltInPresets = true,
|
|
4227
|
-
disableScript = false,
|
|
4228
|
-
deferRenderUntilReady
|
|
4270
|
+
customPresets
|
|
4229
4271
|
}) {
|
|
4230
|
-
const shouldInjectScript = !disableScript;
|
|
4231
|
-
const shouldDeferRenderUntilReady = deferRenderUntilReady ?? disableScript;
|
|
4232
4272
|
const normalizedCustomPresets = useMemo2(() => customPresets ?? {}, [customPresets]);
|
|
4233
4273
|
const [mode, setMode] = useState(() => {
|
|
4234
4274
|
const stored = getStoredMode(modeStorageKey);
|
|
@@ -4242,9 +4282,7 @@ function ThemeProvider({
|
|
|
4242
4282
|
});
|
|
4243
4283
|
const availablePresets = useMemo2(() => {
|
|
4244
4284
|
const merged = {};
|
|
4245
|
-
|
|
4246
|
-
Object.assign(merged, tweakcnPresets);
|
|
4247
|
-
}
|
|
4285
|
+
Object.assign(merged, tweakcnPresets);
|
|
4248
4286
|
if (Object.keys(normalizedCustomPresets).length > 0) {
|
|
4249
4287
|
const validationResult = validateCustomPresets(normalizedCustomPresets);
|
|
4250
4288
|
const isDevelopment = typeof window !== "undefined" && window.location?.hostname === "localhost";
|
|
@@ -4260,8 +4298,8 @@ function ThemeProvider({
|
|
|
4260
4298
|
}
|
|
4261
4299
|
}
|
|
4262
4300
|
return merged;
|
|
4263
|
-
}, [
|
|
4264
|
-
const builtInPresets =
|
|
4301
|
+
}, [normalizedCustomPresets]);
|
|
4302
|
+
const builtInPresets = tweakcnPresets;
|
|
4265
4303
|
const getAvailablePresetById = useCallback(
|
|
4266
4304
|
(id) => {
|
|
4267
4305
|
return availablePresets[id] || null;
|
|
@@ -4269,12 +4307,8 @@ function ThemeProvider({
|
|
|
4269
4307
|
[availablePresets]
|
|
4270
4308
|
);
|
|
4271
4309
|
const [currentPreset, setCurrentPreset] = useState(null);
|
|
4272
|
-
const [isReady, setIsReady] = useState(() => !shouldDeferRenderUntilReady);
|
|
4273
4310
|
useEffect(() => {
|
|
4274
|
-
if (
|
|
4275
|
-
setIsReady(true);
|
|
4276
|
-
return;
|
|
4277
|
-
}
|
|
4311
|
+
if (typeof window === "undefined") return;
|
|
4278
4312
|
try {
|
|
4279
4313
|
const stored = localStorage.getItem(presetStorageKey);
|
|
4280
4314
|
const isDevelopment = typeof window !== "undefined" && window.location?.hostname === "localhost";
|
|
@@ -4327,10 +4361,8 @@ function ThemeProvider({
|
|
|
4327
4361
|
if (isDevelopment) {
|
|
4328
4362
|
console.warn("\u{1F3A8} UnifiedTheme: Failed to load preset from storage:", error);
|
|
4329
4363
|
}
|
|
4330
|
-
} finally {
|
|
4331
|
-
setIsReady(true);
|
|
4332
4364
|
}
|
|
4333
|
-
}, [presetStorageKey,
|
|
4365
|
+
}, [presetStorageKey, defaultPreset, getAvailablePresetById]);
|
|
4334
4366
|
useEffect(() => {
|
|
4335
4367
|
if (mode === "system") {
|
|
4336
4368
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
@@ -4409,7 +4441,7 @@ function ThemeProvider({
|
|
|
4409
4441
|
(coordinates) => {
|
|
4410
4442
|
const newMode = resolvedMode === "light" ? "dark" : "light";
|
|
4411
4443
|
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
4412
|
-
if (
|
|
4444
|
+
if (!prefersReducedMotion && typeof document !== "undefined" && "startViewTransition" in document) {
|
|
4413
4445
|
const root = document.documentElement;
|
|
4414
4446
|
if (coordinates) {
|
|
4415
4447
|
root.style.setProperty("--x", `${coordinates.x}px`);
|
|
@@ -4422,7 +4454,7 @@ function ThemeProvider({
|
|
|
4422
4454
|
handleModeChange(newMode);
|
|
4423
4455
|
}
|
|
4424
4456
|
},
|
|
4425
|
-
[resolvedMode,
|
|
4457
|
+
[resolvedMode, handleModeChange]
|
|
4426
4458
|
);
|
|
4427
4459
|
useEffect(() => {
|
|
4428
4460
|
if (!currentPreset || typeof window === "undefined") return;
|
|
@@ -4437,7 +4469,7 @@ function ThemeProvider({
|
|
|
4437
4469
|
appliedAt: Date.now()
|
|
4438
4470
|
};
|
|
4439
4471
|
setCurrentPreset(presetData);
|
|
4440
|
-
if (
|
|
4472
|
+
if (typeof window !== "undefined") {
|
|
4441
4473
|
try {
|
|
4442
4474
|
localStorage.setItem(presetStorageKey, JSON.stringify(presetData));
|
|
4443
4475
|
} catch (error) {
|
|
@@ -4448,7 +4480,7 @@ function ThemeProvider({
|
|
|
4448
4480
|
applyPresetColors(preset.colors, resolvedMode);
|
|
4449
4481
|
}
|
|
4450
4482
|
},
|
|
4451
|
-
[presetStorageKey,
|
|
4483
|
+
[presetStorageKey, applyPresetColors, resolvedMode]
|
|
4452
4484
|
);
|
|
4453
4485
|
const setThemePresetById = useCallback(
|
|
4454
4486
|
(presetId) => {
|
|
@@ -4472,53 +4504,50 @@ function ThemeProvider({
|
|
|
4472
4504
|
[getAvailablePresetById, applyPreset]
|
|
4473
4505
|
);
|
|
4474
4506
|
const clearPreset = useCallback(() => {
|
|
4475
|
-
if (
|
|
4507
|
+
if (typeof window !== "undefined") {
|
|
4476
4508
|
try {
|
|
4477
4509
|
localStorage.removeItem(presetStorageKey);
|
|
4478
4510
|
} catch (error) {
|
|
4479
4511
|
console.error("\u{1F3A8} UnifiedTheme: Failed to clear preset:", error);
|
|
4480
4512
|
}
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
console.warn("\u{1F3A8} UnifiedTheme: Default preset not found:", defaultPreset);
|
|
4497
|
-
setCurrentPreset(null);
|
|
4498
|
-
}
|
|
4513
|
+
}
|
|
4514
|
+
if (defaultPreset) {
|
|
4515
|
+
const preset = getAvailablePresetById(defaultPreset);
|
|
4516
|
+
if (preset) {
|
|
4517
|
+
const presetData = {
|
|
4518
|
+
presetId: defaultPreset,
|
|
4519
|
+
presetName: preset.label,
|
|
4520
|
+
colors: {
|
|
4521
|
+
light: preset.styles.light,
|
|
4522
|
+
dark: preset.styles.dark
|
|
4523
|
+
},
|
|
4524
|
+
appliedAt: Date.now()
|
|
4525
|
+
};
|
|
4526
|
+
setCurrentPreset(presetData);
|
|
4527
|
+
applyPresetColors(presetData.colors, resolvedMode);
|
|
4499
4528
|
} else {
|
|
4529
|
+
console.warn("\u{1F3A8} UnifiedTheme: Default preset not found:", defaultPreset);
|
|
4500
4530
|
setCurrentPreset(null);
|
|
4501
|
-
const root = document.documentElement;
|
|
4502
|
-
const allProperties = [
|
|
4503
|
-
...CSS_PROPERTY_CATEGORIES.colors,
|
|
4504
|
-
...CSS_PROPERTY_CATEGORIES.typography,
|
|
4505
|
-
...CSS_PROPERTY_CATEGORIES.layout,
|
|
4506
|
-
...CSS_PROPERTY_CATEGORIES.shadows,
|
|
4507
|
-
...CSS_PROPERTY_CATEGORIES.spacing
|
|
4508
|
-
];
|
|
4509
|
-
let clearedCount = 0;
|
|
4510
|
-
allProperties.forEach((prop) => {
|
|
4511
|
-
const cssVar = `--${prop}`;
|
|
4512
|
-
root.style.removeProperty(cssVar);
|
|
4513
|
-
clearedCount++;
|
|
4514
|
-
});
|
|
4515
4531
|
}
|
|
4532
|
+
} else {
|
|
4533
|
+
setCurrentPreset(null);
|
|
4534
|
+
const root = document.documentElement;
|
|
4535
|
+
const allProperties = [
|
|
4536
|
+
...CSS_PROPERTY_CATEGORIES.colors,
|
|
4537
|
+
...CSS_PROPERTY_CATEGORIES.typography,
|
|
4538
|
+
...CSS_PROPERTY_CATEGORIES.layout,
|
|
4539
|
+
...CSS_PROPERTY_CATEGORIES.shadows,
|
|
4540
|
+
...CSS_PROPERTY_CATEGORIES.spacing
|
|
4541
|
+
];
|
|
4542
|
+
let clearedCount = 0;
|
|
4543
|
+
allProperties.forEach((prop) => {
|
|
4544
|
+
const cssVar = `--${prop}`;
|
|
4545
|
+
root.style.removeProperty(cssVar);
|
|
4546
|
+
clearedCount++;
|
|
4547
|
+
});
|
|
4516
4548
|
}
|
|
4517
|
-
}, [presetStorageKey,
|
|
4518
|
-
const scriptElement =
|
|
4519
|
-
if (!isReady && shouldDeferRenderUntilReady) {
|
|
4520
|
-
return scriptElement;
|
|
4521
|
-
}
|
|
4549
|
+
}, [presetStorageKey, defaultPreset, getAvailablePresetById, applyPresetColors, resolvedMode]);
|
|
4550
|
+
const scriptElement = /* @__PURE__ */ jsx2(ThemeScript, { presetStorageKey, defaultPreset });
|
|
4522
4551
|
const isUsingDefaultPreset = !!defaultPreset && currentPreset?.presetId === defaultPreset;
|
|
4523
4552
|
const contextValue = {
|
|
4524
4553
|
mode,
|
|
@@ -4774,7 +4803,7 @@ var AnimatedRow = ({
|
|
|
4774
4803
|
}) => {
|
|
4775
4804
|
if (presets.length === 0) return null;
|
|
4776
4805
|
const duplicatedPresets = Array(animation.duplicationFactor).fill(presets).flat();
|
|
4777
|
-
const totalWidth = presets.reduce((sum, preset) => sum + (Number(preset.metadata?.buttonWidth) || layout.buttonWidth), 0) + presets.length * layout.buttonGap;
|
|
4806
|
+
const totalWidth = presets.reduce((sum, preset) => sum + (Number(preset.metadata?.buttonWidth) || layout.buttonWidth), 0) + Math.max(0, presets.length - 1) * layout.buttonGap;
|
|
4778
4807
|
const effectiveScrollSpeed = Math.max(0.1, animation.scrollSpeed || 1);
|
|
4779
4808
|
const animationDuration = presets.length * animation.duration / effectiveScrollSpeed;
|
|
4780
4809
|
return /* @__PURE__ */ jsx4(
|
|
@@ -4971,6 +5000,42 @@ function useTypedTheme(customPresets) {
|
|
|
4971
5000
|
setThemePresetById
|
|
4972
5001
|
};
|
|
4973
5002
|
}
|
|
5003
|
+
|
|
5004
|
+
// src/hooks/useThemeEngine.ts
|
|
5005
|
+
function useThemeEngine() {
|
|
5006
|
+
const theme = useTheme();
|
|
5007
|
+
const darkMode = theme.resolvedMode === "dark";
|
|
5008
|
+
const setDarkMode = (mode) => theme.setMode(mode);
|
|
5009
|
+
const toggleDarkMode = (coordinates) => theme.toggleMode(coordinates);
|
|
5010
|
+
const applyThemeById = (themeId) => theme.setThemePresetById(themeId);
|
|
5011
|
+
const applyPresetById = applyThemeById;
|
|
5012
|
+
const clearTheme = () => theme.clearPreset();
|
|
5013
|
+
const clearPreset = clearTheme;
|
|
5014
|
+
const currentTheme = theme.currentPreset;
|
|
5015
|
+
const currentPreset = theme.currentPreset;
|
|
5016
|
+
return {
|
|
5017
|
+
// Mode
|
|
5018
|
+
darkMode,
|
|
5019
|
+
mode: theme.mode,
|
|
5020
|
+
resolvedMode: theme.resolvedMode,
|
|
5021
|
+
setDarkMode,
|
|
5022
|
+
setMode: theme.setMode,
|
|
5023
|
+
toggleDarkMode,
|
|
5024
|
+
toggleMode: theme.toggleMode,
|
|
5025
|
+
// Presets (theme naming)
|
|
5026
|
+
applyThemeById,
|
|
5027
|
+
applyPresetById,
|
|
5028
|
+
clearTheme,
|
|
5029
|
+
clearPreset,
|
|
5030
|
+
currentTheme,
|
|
5031
|
+
currentPreset,
|
|
5032
|
+
// Advanced / diagnostics
|
|
5033
|
+
isUsingDefaultPreset: theme.isUsingDefaultPreset,
|
|
5034
|
+
availablePresets: theme.availablePresets,
|
|
5035
|
+
builtInPresets: theme.builtInPresets,
|
|
5036
|
+
customPresets: theme.customPresets
|
|
5037
|
+
};
|
|
5038
|
+
}
|
|
4974
5039
|
export {
|
|
4975
5040
|
ThemePresetButtons,
|
|
4976
5041
|
ThemeProvider,
|
|
@@ -4987,6 +5052,7 @@ export {
|
|
|
4987
5052
|
searchPresets,
|
|
4988
5053
|
tweakcnPresets,
|
|
4989
5054
|
useTheme,
|
|
5055
|
+
useThemeEngine,
|
|
4990
5056
|
useTypedTheme,
|
|
4991
5057
|
validateCustomPresets,
|
|
4992
5058
|
validateTweakCNPreset,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fakhrirafiki/theme-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
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",
|