@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/README.md +142 -482
- package/dist/index.d.mts +57 -9
- package/dist/index.d.ts +57 -9
- package/dist/index.js +268 -260
- package/dist/index.mjs +256 -250
- package/dist/styles/animations.css +23 -1
- package/dist/styles/components.css +12 -3
- package/package.json +7 -7
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
|
|
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
|
|
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
|
|
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
|
|
3685
|
+
return builtInPresetIds.length;
|
|
3654
3686
|
}
|
|
3655
3687
|
function getPresetEntries() {
|
|
3656
|
-
return
|
|
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/
|
|
3837
|
+
// src/components/UnifiedThemeScript.tsx
|
|
3838
|
+
var import_react = require("react");
|
|
3806
3839
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
3807
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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 (
|
|
3854
|
-
const validationResult = validateCustomPresets(
|
|
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,
|
|
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,
|
|
3869
|
-
const builtInPresets = (0,
|
|
3870
|
-
const
|
|
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,
|
|
3878
|
-
const [isReady, setIsReady] = (0,
|
|
3879
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
4122
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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:
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
"
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
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
|
|
4661
|
-
const
|
|
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
|
-
|
|
4686
|
+
"div",
|
|
4676
4687
|
{
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
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
|
-
|
|
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
|