@shellui/core 0.0.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/README.md +17 -0
- package/dist/ContentView-CZG-ro_B.js +146 -0
- package/dist/ContentView-CZG-ro_B.js.map +1 -0
- package/dist/CookiePreferencesView-MhO9FO-4.js +213 -0
- package/dist/CookiePreferencesView-MhO9FO-4.js.map +1 -0
- package/dist/DefaultLayout-Dbb3uJED.js +394 -0
- package/dist/DefaultLayout-Dbb3uJED.js.map +1 -0
- package/dist/FullscreenLayout-1SgPHWw-.js +30 -0
- package/dist/FullscreenLayout-1SgPHWw-.js.map +1 -0
- package/dist/HomeView-DYU-O_Il.js +21 -0
- package/dist/HomeView-DYU-O_Il.js.map +1 -0
- package/dist/NotFoundView-CeYjJNg0.js +52 -0
- package/dist/NotFoundView-CeYjJNg0.js.map +1 -0
- package/dist/OverlayShell-pzbqQW25.js +642 -0
- package/dist/OverlayShell-pzbqQW25.js.map +1 -0
- package/dist/SettingsView-Bndrta44.js +2207 -0
- package/dist/SettingsView-Bndrta44.js.map +1 -0
- package/dist/ViewRoute-ChSPabOy.js +32 -0
- package/dist/ViewRoute-ChSPabOy.js.map +1 -0
- package/dist/WindowsLayout-CXGNPKoY.js +633 -0
- package/dist/WindowsLayout-CXGNPKoY.js.map +1 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/components/ContentView.d.ts +10 -0
- package/dist/components/ContentView.d.ts.map +1 -0
- package/dist/components/HomeView.d.ts +2 -0
- package/dist/components/HomeView.d.ts.map +1 -0
- package/dist/components/LoadingOverlay.d.ts +2 -0
- package/dist/components/LoadingOverlay.d.ts.map +1 -0
- package/dist/components/NotFoundView.d.ts +2 -0
- package/dist/components/NotFoundView.d.ts.map +1 -0
- package/dist/components/RouteErrorBoundary.d.ts +2 -0
- package/dist/components/RouteErrorBoundary.d.ts.map +1 -0
- package/dist/components/ViewRoute.d.ts +7 -0
- package/dist/components/ViewRoute.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +32 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +20 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/button-group.d.ts +7 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button.d.ts +12 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/dialog.d.ts +24 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/drawer.d.ts +38 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/select.d.ts +5 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/sidebar.d.ts +46 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sonner.d.ts +6 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/switch.d.ts +8 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/constants/urls.d.ts +6 -0
- package/dist/constants/urls.d.ts.map +1 -0
- package/dist/constants/urls.js +8 -0
- package/dist/constants/urls.js.map +1 -0
- package/dist/features/alertDialog/DialogContext.d.ts +12 -0
- package/dist/features/alertDialog/DialogContext.d.ts.map +1 -0
- package/dist/features/config/ConfigProvider.d.ts +15 -0
- package/dist/features/config/ConfigProvider.d.ts.map +1 -0
- package/dist/features/config/types.d.ts +177 -0
- package/dist/features/config/types.d.ts.map +1 -0
- package/dist/features/config/useConfig.d.ts +8 -0
- package/dist/features/config/useConfig.d.ts.map +1 -0
- package/dist/features/cookieConsent/CookieConsentModal.d.ts +6 -0
- package/dist/features/cookieConsent/CookieConsentModal.d.ts.map +1 -0
- package/dist/features/cookieConsent/CookiePreferencesView.d.ts +2 -0
- package/dist/features/cookieConsent/CookiePreferencesView.d.ts.map +1 -0
- package/dist/features/cookieConsent/cookieConsent.d.ts +22 -0
- package/dist/features/cookieConsent/cookieConsent.d.ts.map +1 -0
- package/dist/features/cookieConsent/useCookieConsent.d.ts +15 -0
- package/dist/features/cookieConsent/useCookieConsent.d.ts.map +1 -0
- package/dist/features/drawer/DrawerContext.d.ts +24 -0
- package/dist/features/drawer/DrawerContext.d.ts.map +1 -0
- package/dist/features/layouts/AppLayout.d.ts +12 -0
- package/dist/features/layouts/AppLayout.d.ts.map +1 -0
- package/dist/features/layouts/DefaultLayout.d.ts +10 -0
- package/dist/features/layouts/DefaultLayout.d.ts.map +1 -0
- package/dist/features/layouts/FullscreenLayout.d.ts +9 -0
- package/dist/features/layouts/FullscreenLayout.d.ts.map +1 -0
- package/dist/features/layouts/LayoutProviders.d.ts +9 -0
- package/dist/features/layouts/LayoutProviders.d.ts.map +1 -0
- package/dist/features/layouts/OverlayShell.d.ts +10 -0
- package/dist/features/layouts/OverlayShell.d.ts.map +1 -0
- package/dist/features/layouts/WindowsLayout.d.ts +24 -0
- package/dist/features/layouts/WindowsLayout.d.ts.map +1 -0
- package/dist/features/layouts/utils.d.ts +16 -0
- package/dist/features/layouts/utils.d.ts.map +1 -0
- package/dist/features/modal/ModalContext.d.ts +20 -0
- package/dist/features/modal/ModalContext.d.ts.map +1 -0
- package/dist/features/sentry/initSentry.d.ts +14 -0
- package/dist/features/sentry/initSentry.d.ts.map +1 -0
- package/dist/features/settings/SettingsContext.d.ts +10 -0
- package/dist/features/settings/SettingsContext.d.ts.map +1 -0
- package/dist/features/settings/SettingsIcons.d.ts +22 -0
- package/dist/features/settings/SettingsIcons.d.ts.map +1 -0
- package/dist/features/settings/SettingsProvider.d.ts +5 -0
- package/dist/features/settings/SettingsProvider.d.ts.map +1 -0
- package/dist/features/settings/SettingsRoutes.d.ts +7 -0
- package/dist/features/settings/SettingsRoutes.d.ts.map +1 -0
- package/dist/features/settings/SettingsView.d.ts +2 -0
- package/dist/features/settings/SettingsView.d.ts.map +1 -0
- package/dist/features/settings/components/Advanced.d.ts +2 -0
- package/dist/features/settings/components/Advanced.d.ts.map +1 -0
- package/dist/features/settings/components/Appearance.d.ts +2 -0
- package/dist/features/settings/components/Appearance.d.ts.map +1 -0
- package/dist/features/settings/components/DataPrivacy.d.ts +2 -0
- package/dist/features/settings/components/DataPrivacy.d.ts.map +1 -0
- package/dist/features/settings/components/Develop.d.ts +2 -0
- package/dist/features/settings/components/Develop.d.ts.map +1 -0
- package/dist/features/settings/components/LanguageAndRegion.d.ts +2 -0
- package/dist/features/settings/components/LanguageAndRegion.d.ts.map +1 -0
- package/dist/features/settings/components/ServiceWorker.d.ts +2 -0
- package/dist/features/settings/components/ServiceWorker.d.ts.map +1 -0
- package/dist/features/settings/components/UpdateApp.d.ts +2 -0
- package/dist/features/settings/components/UpdateApp.d.ts.map +1 -0
- package/dist/features/settings/components/develop/DialogTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/DialogTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/DrawerTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/DrawerTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/ModalTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/ModalTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/ToastTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/ToastTestButtons.d.ts.map +1 -0
- package/dist/features/settings/hooks/useSettings.d.ts +2 -0
- package/dist/features/settings/hooks/useSettings.d.ts.map +1 -0
- package/dist/features/sonner/SonnerContext.d.ts +29 -0
- package/dist/features/sonner/SonnerContext.d.ts.map +1 -0
- package/dist/features/theme/ThemeProvider.d.ts +11 -0
- package/dist/features/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/features/theme/themes.d.ts +114 -0
- package/dist/features/theme/themes.d.ts.map +1 -0
- package/dist/features/theme/useTheme.d.ts +10 -0
- package/dist/features/theme/useTheme.d.ts.map +1 -0
- package/dist/i18n/I18nProvider.d.ts +9 -0
- package/dist/i18n/I18nProvider.d.ts.map +1 -0
- package/dist/i18n/config.d.ts +23 -0
- package/dist/i18n/config.d.ts.map +1 -0
- package/dist/i18n/translations/en/common.json.d.ts +19 -0
- package/dist/i18n/translations/en/cookieConsent.json.d.ts +53 -0
- package/dist/i18n/translations/en/settings.json.d.ts +358 -0
- package/dist/i18n/translations/fr/common.json.d.ts +19 -0
- package/dist/i18n/translations/fr/cookieConsent.json.d.ts +53 -0
- package/dist/i18n/translations/fr/settings.json.d.ts +358 -0
- package/dist/index-lmRk5L6z.js +2160 -0
- package/dist/index-lmRk5L6z.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/z-index.d.ts +29 -0
- package/dist/lib/z-index.d.ts.map +1 -0
- package/dist/router/router.d.ts +3 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/routes.d.ts +4 -0
- package/dist/router/routes.d.ts.map +1 -0
- package/dist/sidebar-ClIeZ2zb.js +303 -0
- package/dist/sidebar-ClIeZ2zb.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/switch-8SzUJz7Q.js +44 -0
- package/dist/switch-8SzUJz7Q.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +93 -0
- package/postcss.config.js +6 -0
- package/src/app.tsx +119 -0
- package/src/components/ContentView.tsx +258 -0
- package/src/components/HomeView.tsx +19 -0
- package/src/components/LoadingOverlay.tsx +12 -0
- package/src/components/NotFoundView.tsx +84 -0
- package/src/components/RouteErrorBoundary.tsx +95 -0
- package/src/components/ViewRoute.tsx +47 -0
- package/src/components/ui/alert-dialog.tsx +181 -0
- package/src/components/ui/breadcrumb.tsx +155 -0
- package/src/components/ui/button-group.tsx +52 -0
- package/src/components/ui/button.tsx +51 -0
- package/src/components/ui/dialog.tsx +160 -0
- package/src/components/ui/drawer.tsx +200 -0
- package/src/components/ui/select.tsx +24 -0
- package/src/components/ui/sidebar.tsx +406 -0
- package/src/components/ui/sonner.tsx +36 -0
- package/src/components/ui/switch.tsx +45 -0
- package/src/constants/urls.ts +4 -0
- package/src/features/alertDialog/DialogContext.tsx +468 -0
- package/src/features/config/ConfigProvider.ts +96 -0
- package/src/features/config/types.ts +195 -0
- package/src/features/config/useConfig.ts +15 -0
- package/src/features/cookieConsent/CookieConsentModal.tsx +122 -0
- package/src/features/cookieConsent/CookiePreferencesView.tsx +328 -0
- package/src/features/cookieConsent/cookieConsent.ts +84 -0
- package/src/features/cookieConsent/useCookieConsent.ts +39 -0
- package/src/features/drawer/DrawerContext.tsx +116 -0
- package/src/features/layouts/AppLayout.tsx +63 -0
- package/src/features/layouts/DefaultLayout.tsx +625 -0
- package/src/features/layouts/FullscreenLayout.tsx +55 -0
- package/src/features/layouts/LayoutProviders.tsx +20 -0
- package/src/features/layouts/OverlayShell.tsx +171 -0
- package/src/features/layouts/WindowsLayout.tsx +860 -0
- package/src/features/layouts/utils.ts +99 -0
- package/src/features/modal/ModalContext.tsx +112 -0
- package/src/features/sentry/initSentry.ts +72 -0
- package/src/features/settings/SettingsContext.tsx +19 -0
- package/src/features/settings/SettingsIcons.tsx +452 -0
- package/src/features/settings/SettingsProvider.tsx +341 -0
- package/src/features/settings/SettingsRoutes.tsx +66 -0
- package/src/features/settings/SettingsView.tsx +327 -0
- package/src/features/settings/components/Advanced.tsx +128 -0
- package/src/features/settings/components/Appearance.tsx +306 -0
- package/src/features/settings/components/DataPrivacy.tsx +142 -0
- package/src/features/settings/components/Develop.tsx +174 -0
- package/src/features/settings/components/LanguageAndRegion.tsx +329 -0
- package/src/features/settings/components/ServiceWorker.tsx +363 -0
- package/src/features/settings/components/UpdateApp.tsx +206 -0
- package/src/features/settings/components/develop/DialogTestButtons.tsx +137 -0
- package/src/features/settings/components/develop/DrawerTestButtons.tsx +67 -0
- package/src/features/settings/components/develop/ModalTestButtons.tsx +30 -0
- package/src/features/settings/components/develop/ToastTestButtons.tsx +179 -0
- package/src/features/settings/hooks/useSettings.tsx +10 -0
- package/src/features/sonner/SonnerContext.tsx +286 -0
- package/src/features/theme/ThemeProvider.tsx +16 -0
- package/src/features/theme/themes.ts +561 -0
- package/src/features/theme/useTheme.tsx +71 -0
- package/src/i18n/I18nProvider.tsx +32 -0
- package/src/i18n/config.ts +107 -0
- package/src/i18n/translations/en/common.json +16 -0
- package/src/i18n/translations/en/cookieConsent.json +50 -0
- package/src/i18n/translations/en/settings.json +355 -0
- package/src/i18n/translations/fr/common.json +16 -0
- package/src/i18n/translations/fr/cookieConsent.json +50 -0
- package/src/i18n/translations/fr/settings.json +355 -0
- package/src/index.css +412 -0
- package/src/index.html +100 -0
- package/src/index.ts +31 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/z-index.ts +29 -0
- package/src/main.tsx +26 -0
- package/src/router/router.tsx +8 -0
- package/src/router/routes.tsx +115 -0
- package/src/service-worker/register.ts +1199 -0
- package/src/service-worker/sw-dev.ts +87 -0
- package/src/service-worker/sw.ts +105 -0
- package/tailwind.config.js +60 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/**
|
|
3
|
+
* Theme color definitions following shadcn/ui CSS variable structure
|
|
4
|
+
* Each theme has light and dark mode variants
|
|
5
|
+
* Colors are stored in hex format (e.g., "#4CAF50" or "4CAF50")
|
|
6
|
+
* and converted to HSL when applied to CSS variables
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Convert hex color (format: "#RRGGBB" or "RRGGBB") to HSL string (format: "H S% L%")
|
|
11
|
+
*/
|
|
12
|
+
function hexToHsl(hexString: string): string {
|
|
13
|
+
// Handle non-color values like radius
|
|
14
|
+
if (!hexString || typeof hexString !== 'string') {
|
|
15
|
+
return hexString;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check if it's a hex color
|
|
19
|
+
if (!hexString.match(/^#?[0-9A-Fa-f]{6}$/)) {
|
|
20
|
+
// If it's not a hex color, return as-is (might be radius or other value)
|
|
21
|
+
return hexString;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Remove # if present
|
|
25
|
+
const hex = hexString.replace('#', '');
|
|
26
|
+
|
|
27
|
+
// Parse RGB values
|
|
28
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
29
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
30
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
31
|
+
|
|
32
|
+
// Validate parsed values
|
|
33
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
|
34
|
+
console.warn(`[Theme] Invalid hex color: ${hexString}`);
|
|
35
|
+
return hexString;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Normalize RGB values to 0-1
|
|
39
|
+
const rNorm = r / 255;
|
|
40
|
+
const gNorm = g / 255;
|
|
41
|
+
const bNorm = b / 255;
|
|
42
|
+
|
|
43
|
+
const max = Math.max(rNorm, gNorm, bNorm);
|
|
44
|
+
const min = Math.min(rNorm, gNorm, bNorm);
|
|
45
|
+
let h = 0;
|
|
46
|
+
let s = 0;
|
|
47
|
+
const l = (max + min) / 2;
|
|
48
|
+
|
|
49
|
+
if (max !== min) {
|
|
50
|
+
const d = max - min;
|
|
51
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
52
|
+
|
|
53
|
+
switch (max) {
|
|
54
|
+
case rNorm:
|
|
55
|
+
h = ((gNorm - bNorm) / d + (gNorm < bNorm ? 6 : 0)) / 6;
|
|
56
|
+
break;
|
|
57
|
+
case gNorm:
|
|
58
|
+
h = ((bNorm - rNorm) / d + 2) / 6;
|
|
59
|
+
break;
|
|
60
|
+
case bNorm:
|
|
61
|
+
h = ((rNorm - gNorm) / d + 4) / 6;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Convert to degrees and percentages
|
|
67
|
+
const hDeg = Math.round(h * 360 * 10) / 10; // Keep one decimal for precision
|
|
68
|
+
const sPercent = Math.round(s * 100 * 10) / 10;
|
|
69
|
+
const lPercent = Math.round(l * 100 * 10) / 10;
|
|
70
|
+
|
|
71
|
+
const result = `${hDeg} ${sPercent}% ${lPercent}%`;
|
|
72
|
+
|
|
73
|
+
// Validate result
|
|
74
|
+
if (!result.match(/^\d+(\.\d+)?\s+\d+(\.\d+)?%\s+\d+(\.\d+)?%$/)) {
|
|
75
|
+
console.warn(`[Theme] Invalid HSL conversion result for ${hexString}: ${result}`);
|
|
76
|
+
return hexString;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ThemeColors {
|
|
83
|
+
light: {
|
|
84
|
+
background: string;
|
|
85
|
+
foreground: string;
|
|
86
|
+
card: string;
|
|
87
|
+
cardForeground: string;
|
|
88
|
+
popover: string;
|
|
89
|
+
popoverForeground: string;
|
|
90
|
+
primary: string;
|
|
91
|
+
primaryForeground: string;
|
|
92
|
+
secondary: string;
|
|
93
|
+
secondaryForeground: string;
|
|
94
|
+
muted: string;
|
|
95
|
+
mutedForeground: string;
|
|
96
|
+
accent: string;
|
|
97
|
+
accentForeground: string;
|
|
98
|
+
destructive: string;
|
|
99
|
+
destructiveForeground: string;
|
|
100
|
+
border: string;
|
|
101
|
+
input: string;
|
|
102
|
+
ring: string;
|
|
103
|
+
radius: string;
|
|
104
|
+
sidebarBackground: string;
|
|
105
|
+
sidebarForeground: string;
|
|
106
|
+
sidebarPrimary: string;
|
|
107
|
+
sidebarPrimaryForeground: string;
|
|
108
|
+
sidebarAccent: string;
|
|
109
|
+
sidebarAccentForeground: string;
|
|
110
|
+
sidebarBorder: string;
|
|
111
|
+
sidebarRing: string;
|
|
112
|
+
};
|
|
113
|
+
dark: {
|
|
114
|
+
background: string;
|
|
115
|
+
foreground: string;
|
|
116
|
+
card: string;
|
|
117
|
+
cardForeground: string;
|
|
118
|
+
popover: string;
|
|
119
|
+
popoverForeground: string;
|
|
120
|
+
primary: string;
|
|
121
|
+
primaryForeground: string;
|
|
122
|
+
secondary: string;
|
|
123
|
+
secondaryForeground: string;
|
|
124
|
+
muted: string;
|
|
125
|
+
mutedForeground: string;
|
|
126
|
+
accent: string;
|
|
127
|
+
accentForeground: string;
|
|
128
|
+
destructive: string;
|
|
129
|
+
destructiveForeground: string;
|
|
130
|
+
border: string;
|
|
131
|
+
input: string;
|
|
132
|
+
ring: string;
|
|
133
|
+
radius: string;
|
|
134
|
+
sidebarBackground: string;
|
|
135
|
+
sidebarForeground: string;
|
|
136
|
+
sidebarPrimary: string;
|
|
137
|
+
sidebarPrimaryForeground: string;
|
|
138
|
+
sidebarAccent: string;
|
|
139
|
+
sidebarAccentForeground: string;
|
|
140
|
+
sidebarBorder: string;
|
|
141
|
+
sidebarRing: string;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface ThemeDefinition {
|
|
146
|
+
name: string;
|
|
147
|
+
displayName: string;
|
|
148
|
+
colors: ThemeColors;
|
|
149
|
+
fontFamily?: string; // Optional custom font family (backward compatible)
|
|
150
|
+
headingFontFamily?: string; // Optional font family for headings (h1-h6)
|
|
151
|
+
bodyFontFamily?: string; // Optional font family for body text
|
|
152
|
+
fontFiles?: string[]; // Optional array of font file URLs or paths to load (e.g., Google Fonts links or local paths)
|
|
153
|
+
letterSpacing?: string; // Optional custom letter spacing (e.g., "0.02em")
|
|
154
|
+
textShadow?: string; // Optional custom text shadow (e.g., "1px 1px 2px rgba(0, 0, 0, 0.1)")
|
|
155
|
+
lineHeight?: string; // Optional custom line height (e.g., "1.6")
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Default theme - Green (current ShellUI theme)
|
|
160
|
+
* Colors are in hex format (e.g., "#FFFFFF" or "FFFFFF" for white)
|
|
161
|
+
*/
|
|
162
|
+
export const defaultTheme: ThemeDefinition = {
|
|
163
|
+
name: 'default',
|
|
164
|
+
displayName: 'Default',
|
|
165
|
+
colors: {
|
|
166
|
+
light: {
|
|
167
|
+
background: '#FFFFFF', // White
|
|
168
|
+
foreground: '#020617', // Very dark blue-gray
|
|
169
|
+
card: '#FFFFFF', // White
|
|
170
|
+
cardForeground: '#020617', // Very dark blue-gray
|
|
171
|
+
popover: '#FFFFFF', // White
|
|
172
|
+
popoverForeground: '#020617', // Very dark blue-gray
|
|
173
|
+
primary: '#22C55E', // Green
|
|
174
|
+
primaryForeground: '#FFFFFF', // White
|
|
175
|
+
secondary: '#F1F5F9', // Light gray-blue
|
|
176
|
+
secondaryForeground: '#0F172A', // Dark blue-gray
|
|
177
|
+
muted: '#F1F5F9', // Light gray-blue
|
|
178
|
+
mutedForeground: '#64748B', // Medium gray-blue
|
|
179
|
+
accent: '#F1F5F9', // Light gray-blue
|
|
180
|
+
accentForeground: '#0F172A', // Dark blue-gray
|
|
181
|
+
destructive: '#EF4444', // Red
|
|
182
|
+
destructiveForeground: '#F8FAFC', // Off-white
|
|
183
|
+
border: '#E2E8F0', // Light gray
|
|
184
|
+
input: '#E2E8F0', // Light gray
|
|
185
|
+
ring: '#020617', // Very dark blue-gray
|
|
186
|
+
radius: '0.5rem',
|
|
187
|
+
sidebarBackground: '#FAFAFA', // Off-white
|
|
188
|
+
sidebarForeground: '#334155', // Dark gray-blue
|
|
189
|
+
sidebarPrimary: '#0F172A', // Very dark blue-gray
|
|
190
|
+
sidebarPrimaryForeground: '#FAFAFA', // Off-white
|
|
191
|
+
sidebarAccent: '#F4F4F5', // Light gray
|
|
192
|
+
sidebarAccentForeground: '#0F172A', // Very dark blue-gray
|
|
193
|
+
sidebarBorder: '#E2E8F0', // Light gray
|
|
194
|
+
sidebarRing: '#3B82F6', // Blue
|
|
195
|
+
},
|
|
196
|
+
dark: {
|
|
197
|
+
background: '#020617', // Very dark blue-gray
|
|
198
|
+
foreground: '#F8FAFC', // Off-white
|
|
199
|
+
card: '#020617', // Very dark blue-gray
|
|
200
|
+
cardForeground: '#F8FAFC', // Off-white
|
|
201
|
+
popover: '#020617', // Very dark blue-gray
|
|
202
|
+
popoverForeground: '#F8FAFC', // Off-white
|
|
203
|
+
primary: '#4ADE80', // Bright green
|
|
204
|
+
primaryForeground: '#FFFFFF', // White
|
|
205
|
+
secondary: '#1E293B', // Dark blue-gray
|
|
206
|
+
secondaryForeground: '#F8FAFC', // Off-white
|
|
207
|
+
muted: '#1E293B', // Dark blue-gray
|
|
208
|
+
mutedForeground: '#94A3B8', // Medium gray-blue
|
|
209
|
+
accent: '#1E293B', // Dark blue-gray
|
|
210
|
+
accentForeground: '#F8FAFC', // Off-white
|
|
211
|
+
destructive: '#991B1B', // Dark red
|
|
212
|
+
destructiveForeground: '#F8FAFC', // Off-white
|
|
213
|
+
border: '#1E293B', // Dark blue-gray
|
|
214
|
+
input: '#1E293B', // Dark blue-gray
|
|
215
|
+
ring: '#CBD5E1', // Light gray-blue
|
|
216
|
+
radius: '0.5rem',
|
|
217
|
+
sidebarBackground: '#0F172A', // Very dark blue-gray
|
|
218
|
+
sidebarForeground: '#F1F5F9', // Light gray-blue
|
|
219
|
+
sidebarPrimary: '#E0E7FF', // Very light blue
|
|
220
|
+
sidebarPrimaryForeground: '#0F172A', // Very dark blue-gray
|
|
221
|
+
sidebarAccent: '#18181B', // Very dark gray
|
|
222
|
+
sidebarAccentForeground: '#F1F5F9', // Light gray-blue
|
|
223
|
+
sidebarBorder: '#18181B', // Very dark gray
|
|
224
|
+
sidebarRing: '#3B82F6', // Blue
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Blue theme
|
|
231
|
+
* Colors are in hex format (e.g., "#FFFFFF" or "FFFFFF" for white)
|
|
232
|
+
*/
|
|
233
|
+
export const blueTheme: ThemeDefinition = {
|
|
234
|
+
name: 'blue',
|
|
235
|
+
displayName: 'Blue',
|
|
236
|
+
colors: {
|
|
237
|
+
light: {
|
|
238
|
+
background: '#FFFFFF', // White
|
|
239
|
+
foreground: '#020617', // Very dark blue-gray
|
|
240
|
+
card: '#FFFFFF', // White
|
|
241
|
+
cardForeground: '#020617', // Very dark blue-gray
|
|
242
|
+
popover: '#FFFFFF', // White
|
|
243
|
+
popoverForeground: '#020617', // Very dark blue-gray
|
|
244
|
+
primary: '#3B82F6', // Blue
|
|
245
|
+
primaryForeground: '#FFFFFF', // White
|
|
246
|
+
secondary: '#F1F5F9', // Light gray-blue
|
|
247
|
+
secondaryForeground: '#0F172A', // Dark blue-gray
|
|
248
|
+
muted: '#F1F5F9', // Light gray-blue
|
|
249
|
+
mutedForeground: '#64748B', // Medium gray-blue
|
|
250
|
+
accent: '#F1F5F9', // Light gray-blue
|
|
251
|
+
accentForeground: '#0F172A', // Dark blue-gray
|
|
252
|
+
destructive: '#EF4444', // Red
|
|
253
|
+
destructiveForeground: '#F8FAFC', // Off-white
|
|
254
|
+
border: '#E2E8F0', // Light gray
|
|
255
|
+
input: '#E2E8F0', // Light gray
|
|
256
|
+
ring: '#3B82F6', // Blue
|
|
257
|
+
radius: '0.5rem',
|
|
258
|
+
sidebarBackground: '#FAFAFA', // Off-white
|
|
259
|
+
sidebarForeground: '#334155', // Dark gray-blue
|
|
260
|
+
sidebarPrimary: '#3B82F6', // Blue
|
|
261
|
+
sidebarPrimaryForeground: '#FFFFFF', // White
|
|
262
|
+
sidebarAccent: '#F4F4F5', // Light gray
|
|
263
|
+
sidebarAccentForeground: '#0F172A', // Very dark blue-gray
|
|
264
|
+
sidebarBorder: '#E2E8F0', // Light gray
|
|
265
|
+
sidebarRing: '#3B82F6', // Blue
|
|
266
|
+
},
|
|
267
|
+
dark: {
|
|
268
|
+
background: '#020617', // Very dark blue-gray
|
|
269
|
+
foreground: '#F8FAFC', // Off-white
|
|
270
|
+
card: '#020617', // Very dark blue-gray
|
|
271
|
+
cardForeground: '#F8FAFC', // Off-white
|
|
272
|
+
popover: '#020617', // Very dark blue-gray
|
|
273
|
+
popoverForeground: '#F8FAFC', // Off-white
|
|
274
|
+
primary: '#3B82F6', // Blue
|
|
275
|
+
primaryForeground: '#FFFFFF', // White
|
|
276
|
+
secondary: '#1E293B', // Dark blue-gray
|
|
277
|
+
secondaryForeground: '#F8FAFC', // Off-white
|
|
278
|
+
muted: '#1E293B', // Dark blue-gray
|
|
279
|
+
mutedForeground: '#94A3B8', // Medium gray-blue
|
|
280
|
+
accent: '#1E293B', // Dark blue-gray
|
|
281
|
+
accentForeground: '#F8FAFC', // Off-white
|
|
282
|
+
destructive: '#991B1B', // Dark red
|
|
283
|
+
destructiveForeground: '#F8FAFC', // Off-white
|
|
284
|
+
border: '#1E293B', // Dark blue-gray
|
|
285
|
+
input: '#1E293B', // Dark blue-gray
|
|
286
|
+
ring: '#3B82F6', // Blue
|
|
287
|
+
radius: '0.5rem',
|
|
288
|
+
sidebarBackground: '#0F172A', // Very dark blue-gray
|
|
289
|
+
sidebarForeground: '#F1F5F9', // Light gray-blue
|
|
290
|
+
sidebarPrimary: '#3B82F6', // Blue
|
|
291
|
+
sidebarPrimaryForeground: '#0F172A', // Dark blue-gray
|
|
292
|
+
sidebarAccent: '#18181B', // Very dark gray
|
|
293
|
+
sidebarAccentForeground: '#F1F5F9', // Light gray-blue
|
|
294
|
+
sidebarBorder: '#18181B', // Very dark gray
|
|
295
|
+
sidebarRing: '#3B82F6', // Blue
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Warm Yellow theme - Custom yellowish theme with warm tones
|
|
302
|
+
* Colors are in hex format (e.g., "#FFFFFF" or "FFFFFF" for white)
|
|
303
|
+
*/
|
|
304
|
+
export const warmYellowTheme: ThemeDefinition = {
|
|
305
|
+
name: 'warm-yellow',
|
|
306
|
+
displayName: 'Warm Yellow',
|
|
307
|
+
fontFamily: '"Comic Sans MS", "Comic Sans", "Chalkboard SE", "Comic Neue", cursive, sans-serif',
|
|
308
|
+
letterSpacing: '0.02em',
|
|
309
|
+
textShadow: '1px 1px 2px rgba(0, 0, 0, 0.1)',
|
|
310
|
+
lineHeight: '1.6',
|
|
311
|
+
colors: {
|
|
312
|
+
light: {
|
|
313
|
+
background: '#FFF8E7', // Warm cream/yellowish
|
|
314
|
+
foreground: '#3E2723', // Warm dark brown
|
|
315
|
+
card: '#FFFEF5', // Slightly off-white cream
|
|
316
|
+
cardForeground: '#3E2723', // Warm dark brown
|
|
317
|
+
popover: '#FFFEF5', // Slightly off-white cream
|
|
318
|
+
popoverForeground: '#3E2723', // Warm dark brown
|
|
319
|
+
primary: '#F59E0B', // Warm golden amber (complements yellow)
|
|
320
|
+
primaryForeground: '#FFFFFF', // White
|
|
321
|
+
secondary: '#F5E6D3', // Warm beige
|
|
322
|
+
secondaryForeground: '#3E2723', // Warm dark brown
|
|
323
|
+
muted: '#F5E6D3', // Warm beige
|
|
324
|
+
mutedForeground: '#6D4C41', // Medium warm brown
|
|
325
|
+
accent: '#FFE082', // Light warm yellow
|
|
326
|
+
accentForeground: '#3E2723', // Warm dark brown
|
|
327
|
+
destructive: '#E57373', // Soft red (works with warm tones)
|
|
328
|
+
destructiveForeground: '#FFFFFF', // White
|
|
329
|
+
border: '#E8D5B7', // Warm tan border
|
|
330
|
+
input: '#E8D5B7', // Warm tan input border
|
|
331
|
+
ring: '#F59E0B', // Warm golden amber ring
|
|
332
|
+
radius: '0.5rem',
|
|
333
|
+
sidebarBackground: '#FFF5E1', // Slightly warmer cream (subtle contrast with main background)
|
|
334
|
+
sidebarForeground: '#5D4037', // Medium warm brown
|
|
335
|
+
sidebarPrimary: '#F59E0B', // Warm golden amber
|
|
336
|
+
sidebarPrimaryForeground: '#FFFFFF', // White
|
|
337
|
+
sidebarAccent: '#F5E6D3', // Warm beige
|
|
338
|
+
sidebarAccentForeground: '#3E2723', // Warm dark brown
|
|
339
|
+
sidebarBorder: '#E8D5B7', // Warm tan
|
|
340
|
+
sidebarRing: '#F59E0B', // Warm golden amber
|
|
341
|
+
},
|
|
342
|
+
dark: {
|
|
343
|
+
background: '#2E2419', // Dark warm brown
|
|
344
|
+
foreground: '#FFF8E7', // Warm cream (inverted)
|
|
345
|
+
card: '#3E2723', // Dark warm brown
|
|
346
|
+
cardForeground: '#FFF8E7', // Warm cream
|
|
347
|
+
popover: '#3E2723', // Dark warm brown
|
|
348
|
+
popoverForeground: '#FFF8E7', // Warm cream
|
|
349
|
+
primary: '#FBBF24', // Bright golden amber (lighter for dark mode)
|
|
350
|
+
primaryForeground: '#FFFFFF', // White for better contrast
|
|
351
|
+
secondary: '#4E342E', // Medium dark warm brown
|
|
352
|
+
secondaryForeground: '#FFF8E7', // Warm cream
|
|
353
|
+
muted: '#4E342E', // Medium dark warm brown
|
|
354
|
+
mutedForeground: '#D7CCC8', // Light warm gray
|
|
355
|
+
accent: '#FFB74D', // Warm orange accent
|
|
356
|
+
accentForeground: '#2E2419', // Dark warm brown for better contrast
|
|
357
|
+
destructive: '#EF5350', // Softer red for dark mode
|
|
358
|
+
destructiveForeground: '#FFF8E7', // Warm cream
|
|
359
|
+
border: '#5D4037', // Medium warm brown border
|
|
360
|
+
input: '#5D4037', // Medium warm brown input border
|
|
361
|
+
ring: '#FBBF24', // Bright golden amber ring
|
|
362
|
+
radius: '0.5rem',
|
|
363
|
+
sidebarBackground: '#35281F', // Slightly warmer dark brown (subtle contrast with main background)
|
|
364
|
+
sidebarForeground: '#D7CCC8', // Light warm gray
|
|
365
|
+
sidebarPrimary: '#FBBF24', // Bright golden amber
|
|
366
|
+
sidebarPrimaryForeground: '#2E2419', // Dark warm brown for better contrast
|
|
367
|
+
sidebarAccent: '#4E342E', // Medium dark warm brown
|
|
368
|
+
sidebarAccentForeground: '#FFF8E7', // Warm cream
|
|
369
|
+
sidebarBorder: '#5D4037', // Medium warm brown
|
|
370
|
+
sidebarRing: '#FBBF24', // Bright golden amber
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Registry of all available themes
|
|
377
|
+
*/
|
|
378
|
+
const themeRegistry = new Map<string, ThemeDefinition>([
|
|
379
|
+
['default', defaultTheme],
|
|
380
|
+
['blue', blueTheme],
|
|
381
|
+
['warm-yellow', warmYellowTheme],
|
|
382
|
+
]);
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Register a custom theme
|
|
386
|
+
*/
|
|
387
|
+
export function registerTheme(theme: ThemeDefinition): void {
|
|
388
|
+
themeRegistry.set(theme.name, theme);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get a theme by name
|
|
393
|
+
*/
|
|
394
|
+
export function getTheme(name: string): ThemeDefinition | undefined {
|
|
395
|
+
return themeRegistry.get(name);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get all available themes
|
|
400
|
+
*/
|
|
401
|
+
export function getAllThemes(): ThemeDefinition[] {
|
|
402
|
+
return Array.from(themeRegistry.values());
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Apply theme colors to the document
|
|
407
|
+
* Converts hex format colors to HSL format for CSS variables
|
|
408
|
+
* Sets variables directly on :root to ensure they override CSS defaults
|
|
409
|
+
*/
|
|
410
|
+
export function applyTheme(theme: ThemeDefinition, isDark: boolean): void {
|
|
411
|
+
if (typeof document === 'undefined') {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const root = document.documentElement;
|
|
416
|
+
const colors = isDark ? theme.colors.dark : theme.colors.light;
|
|
417
|
+
|
|
418
|
+
// Convert hex to HSL for all colors
|
|
419
|
+
const primaryHsl = hexToHsl(colors.primary);
|
|
420
|
+
|
|
421
|
+
// Apply CSS variables directly on :root element
|
|
422
|
+
// Inline styles have the highest specificity and will override CSS defaults
|
|
423
|
+
// Format: HSL values without hsl() wrapper (e.g., "142 71% 45%")
|
|
424
|
+
root.style.setProperty('--background', hexToHsl(colors.background));
|
|
425
|
+
root.style.setProperty('--foreground', hexToHsl(colors.foreground));
|
|
426
|
+
root.style.setProperty('--card', hexToHsl(colors.card));
|
|
427
|
+
root.style.setProperty('--card-foreground', hexToHsl(colors.cardForeground));
|
|
428
|
+
root.style.setProperty('--popover', hexToHsl(colors.popover));
|
|
429
|
+
root.style.setProperty('--popover-foreground', hexToHsl(colors.popoverForeground));
|
|
430
|
+
root.style.setProperty('--primary', primaryHsl);
|
|
431
|
+
root.style.setProperty('--primary-foreground', hexToHsl(colors.primaryForeground));
|
|
432
|
+
root.style.setProperty('--secondary', hexToHsl(colors.secondary));
|
|
433
|
+
root.style.setProperty('--secondary-foreground', hexToHsl(colors.secondaryForeground));
|
|
434
|
+
root.style.setProperty('--muted', hexToHsl(colors.muted));
|
|
435
|
+
root.style.setProperty('--muted-foreground', hexToHsl(colors.mutedForeground));
|
|
436
|
+
root.style.setProperty('--accent', hexToHsl(colors.accent));
|
|
437
|
+
root.style.setProperty('--accent-foreground', hexToHsl(colors.accentForeground));
|
|
438
|
+
root.style.setProperty('--destructive', hexToHsl(colors.destructive));
|
|
439
|
+
root.style.setProperty('--destructive-foreground', hexToHsl(colors.destructiveForeground));
|
|
440
|
+
root.style.setProperty('--border', hexToHsl(colors.border));
|
|
441
|
+
root.style.setProperty('--input', hexToHsl(colors.input));
|
|
442
|
+
root.style.setProperty('--ring', hexToHsl(colors.ring));
|
|
443
|
+
root.style.setProperty('--radius', colors.radius); // radius is not a color
|
|
444
|
+
root.style.setProperty('--sidebar-background', hexToHsl(colors.sidebarBackground));
|
|
445
|
+
root.style.setProperty('--sidebar-foreground', hexToHsl(colors.sidebarForeground));
|
|
446
|
+
root.style.setProperty('--sidebar-primary', hexToHsl(colors.sidebarPrimary));
|
|
447
|
+
root.style.setProperty('--sidebar-primary-foreground', hexToHsl(colors.sidebarPrimaryForeground));
|
|
448
|
+
root.style.setProperty('--sidebar-accent', hexToHsl(colors.sidebarAccent));
|
|
449
|
+
root.style.setProperty('--sidebar-accent-foreground', hexToHsl(colors.sidebarAccentForeground));
|
|
450
|
+
root.style.setProperty('--sidebar-border', hexToHsl(colors.sidebarBorder));
|
|
451
|
+
root.style.setProperty('--sidebar-ring', hexToHsl(colors.sidebarRing));
|
|
452
|
+
|
|
453
|
+
// Load custom font files if provided
|
|
454
|
+
// Always clean up existing theme fonts first (for theme switching)
|
|
455
|
+
const head = document.head || document.getElementsByTagName('head')[0];
|
|
456
|
+
const existingFontLinks = head.querySelectorAll('link[data-theme-font], style[data-theme-font]');
|
|
457
|
+
existingFontLinks.forEach((link) => link.remove());
|
|
458
|
+
|
|
459
|
+
if (theme.fontFiles && theme.fontFiles.length > 0) {
|
|
460
|
+
theme.fontFiles.forEach((fontFile, index) => {
|
|
461
|
+
// Check if it's a Google Fonts link or a regular stylesheet
|
|
462
|
+
if (fontFile.includes('fonts.googleapis.com') || fontFile.endsWith('.css')) {
|
|
463
|
+
const link = document.createElement('link');
|
|
464
|
+
link.rel = 'stylesheet';
|
|
465
|
+
link.href = fontFile;
|
|
466
|
+
link.setAttribute('data-theme-font', theme.name);
|
|
467
|
+
head.appendChild(link);
|
|
468
|
+
} else {
|
|
469
|
+
// Assume it's a font file URL - create @font-face rule
|
|
470
|
+
const style = document.createElement('style');
|
|
471
|
+
style.setAttribute('data-theme-font', theme.name);
|
|
472
|
+
// Extract font name from URL or use a generic name
|
|
473
|
+
const fontName = `ThemeFont-${theme.name}-${index}`;
|
|
474
|
+
style.textContent = `
|
|
475
|
+
@font-face {
|
|
476
|
+
font-family: '${fontName}';
|
|
477
|
+
src: url('${fontFile}') format('woff2');
|
|
478
|
+
}
|
|
479
|
+
`;
|
|
480
|
+
head.appendChild(style);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Apply custom font families
|
|
486
|
+
// Priority: headingFontFamily/bodyFontFamily > fontFamily (backward compatible)
|
|
487
|
+
const bodyFont = theme.bodyFontFamily || theme.fontFamily;
|
|
488
|
+
const headingFont = theme.headingFontFamily || theme.fontFamily || bodyFont;
|
|
489
|
+
|
|
490
|
+
if (bodyFont) {
|
|
491
|
+
root.style.setProperty('--body-font-family', bodyFont);
|
|
492
|
+
root.style.setProperty('--font-family', bodyFont); // Backward compatibility
|
|
493
|
+
document.body.style.fontFamily = bodyFont;
|
|
494
|
+
} else {
|
|
495
|
+
root.style.removeProperty('--body-font-family');
|
|
496
|
+
root.style.removeProperty('--font-family');
|
|
497
|
+
document.body.style.fontFamily = '';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (headingFont) {
|
|
501
|
+
root.style.setProperty('--heading-font-family', headingFont);
|
|
502
|
+
// Apply to headings via CSS variable (will be used in CSS)
|
|
503
|
+
} else {
|
|
504
|
+
root.style.removeProperty('--heading-font-family');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Apply optional font styling properties generically
|
|
508
|
+
if (theme.letterSpacing) {
|
|
509
|
+
root.style.setProperty('--letter-spacing', theme.letterSpacing);
|
|
510
|
+
root.style.letterSpacing = theme.letterSpacing;
|
|
511
|
+
} else {
|
|
512
|
+
root.style.removeProperty('--letter-spacing');
|
|
513
|
+
root.style.letterSpacing = '';
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (theme.textShadow) {
|
|
517
|
+
root.style.setProperty('--text-shadow', theme.textShadow);
|
|
518
|
+
// Apply slightly lighter shadow to body for better readability
|
|
519
|
+
const bodyShadow = theme.textShadow.replace(/rgba\(([^)]+)\)/, (match, rgba) => {
|
|
520
|
+
// Reduce opacity by ~20% for body text
|
|
521
|
+
const values = rgba.split(',').map((v: string) => v.trim());
|
|
522
|
+
if (values.length === 4) {
|
|
523
|
+
const opacity = parseFloat(values[3]);
|
|
524
|
+
return `rgba(${values[0]}, ${values[1]}, ${values[2]}, ${Math.max(0, opacity * 0.8)})`;
|
|
525
|
+
}
|
|
526
|
+
return match;
|
|
527
|
+
});
|
|
528
|
+
document.body.style.textShadow = bodyShadow;
|
|
529
|
+
} else {
|
|
530
|
+
root.style.removeProperty('--text-shadow');
|
|
531
|
+
document.body.style.textShadow = '';
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (theme.lineHeight) {
|
|
535
|
+
root.style.setProperty('--line-height', theme.lineHeight);
|
|
536
|
+
document.body.style.lineHeight = theme.lineHeight;
|
|
537
|
+
} else {
|
|
538
|
+
root.style.removeProperty('--line-height');
|
|
539
|
+
document.body.style.lineHeight = '';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Verify primary color is set (for debugging)
|
|
543
|
+
const actualPrimary = root.style.getPropertyValue('--primary');
|
|
544
|
+
|
|
545
|
+
// Validate HSL format (should be "H S% L%" like "142 71% 45%")
|
|
546
|
+
const hslFormat = /^\d+(\.\d+)?\s+\d+(\.\d+)?%\s+\d+(\.\d+)?%$/;
|
|
547
|
+
|
|
548
|
+
if (!actualPrimary || actualPrimary.trim() === '') {
|
|
549
|
+
console.error(
|
|
550
|
+
`[Theme] Failed to set --primary CSS variable. Expected HSL from ${colors.primary}, got: "${actualPrimary}"`,
|
|
551
|
+
);
|
|
552
|
+
} else if (!hslFormat.test(actualPrimary.trim())) {
|
|
553
|
+
console.error(
|
|
554
|
+
`[Theme] Invalid HSL format for --primary: "${actualPrimary}". Expected format: "H S% L%"`,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Force a reflow to ensure Tailwind picks up the changes
|
|
559
|
+
// This is sometimes needed for Tailwind v4 to recognize CSS variable changes
|
|
560
|
+
void root.offsetHeight;
|
|
561
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { useLayoutEffect } from 'react';
|
|
3
|
+
import { useSettings } from '../settings/hooks/useSettings';
|
|
4
|
+
import { useConfig } from '../config/useConfig';
|
|
5
|
+
import { getTheme, registerTheme, applyTheme, type ThemeDefinition } from './themes';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Apply theme to document element
|
|
9
|
+
*/
|
|
10
|
+
function applyThemeToDocument(isDark: boolean) {
|
|
11
|
+
const root = document.documentElement;
|
|
12
|
+
if (isDark) {
|
|
13
|
+
root.classList.add('dark');
|
|
14
|
+
} else {
|
|
15
|
+
root.classList.remove('dark');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook to apply theme based on settings
|
|
21
|
+
* Applies 'dark' class to document.documentElement based on:
|
|
22
|
+
* - 'light': removes dark class
|
|
23
|
+
* - 'dark': adds dark class
|
|
24
|
+
* - 'system': follows prefers-color-scheme media query
|
|
25
|
+
* Also applies theme colors based on themeName setting
|
|
26
|
+
*/
|
|
27
|
+
export function useTheme() {
|
|
28
|
+
const { settings } = useSettings();
|
|
29
|
+
const { config } = useConfig();
|
|
30
|
+
const theme = settings.appearance?.theme || 'system';
|
|
31
|
+
const themeName = settings.appearance?.themeName || 'default';
|
|
32
|
+
|
|
33
|
+
// Apply theme immediately on mount (synchronously) to prevent empty colors
|
|
34
|
+
// This ensures CSS variables are set before first render
|
|
35
|
+
useLayoutEffect(() => {
|
|
36
|
+
// Get the effective theme name (from settings or config)
|
|
37
|
+
// Use themeName from settings first, then config defaultTheme, then 'default'
|
|
38
|
+
const effectiveThemeName = themeName || config?.defaultTheme || 'default';
|
|
39
|
+
|
|
40
|
+
if (config?.themes) {
|
|
41
|
+
config.themes.forEach((themeDef: ThemeDefinition) => {
|
|
42
|
+
registerTheme(themeDef);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const themeDefinition = getTheme(effectiveThemeName) || getTheme('default');
|
|
47
|
+
|
|
48
|
+
if (themeDefinition) {
|
|
49
|
+
const determineIsDark = () => {
|
|
50
|
+
if (theme === 'system') {
|
|
51
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
52
|
+
return mediaQuery.matches;
|
|
53
|
+
}
|
|
54
|
+
return theme === 'dark';
|
|
55
|
+
};
|
|
56
|
+
const isDark = determineIsDark();
|
|
57
|
+
applyThemeToDocument(isDark);
|
|
58
|
+
applyTheme(themeDefinition, isDark);
|
|
59
|
+
} else {
|
|
60
|
+
console.error('[Theme] No theme definition found, using fallback');
|
|
61
|
+
// Fallback: at least set primary color from default theme
|
|
62
|
+
const defaultTheme = getTheme('default');
|
|
63
|
+
if (defaultTheme) {
|
|
64
|
+
const isDark =
|
|
65
|
+
theme === 'dark' ||
|
|
66
|
+
(theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
67
|
+
applyTheme(defaultTheme, isDark);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}, [theme, themeName, config]); // Run when theme, themeName, or config changes
|
|
71
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, type ReactNode } from 'react';
|
|
2
|
+
import { useSettings } from '../features/settings/hooks/useSettings';
|
|
3
|
+
import i18n, { initializeI18n } from './config';
|
|
4
|
+
import type { ShellUIConfig } from '../features/config/types';
|
|
5
|
+
import { useConfig } from '../features/config/useConfig';
|
|
6
|
+
|
|
7
|
+
interface I18nProviderProps {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
config?: ShellUIConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function I18nProvider({ children }: I18nProviderProps) {
|
|
13
|
+
const { settings } = useSettings();
|
|
14
|
+
const { config } = useConfig();
|
|
15
|
+
const currentLanguage = settings.language?.code || 'en';
|
|
16
|
+
|
|
17
|
+
// Initialize i18n with enabled languages from config
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (config?.language) {
|
|
20
|
+
initializeI18n(config.language);
|
|
21
|
+
}
|
|
22
|
+
}, [config?.language]);
|
|
23
|
+
|
|
24
|
+
// Sync i18n language with settings changes
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (i18n.language !== currentLanguage) {
|
|
27
|
+
i18n.changeLanguage(currentLanguage);
|
|
28
|
+
}
|
|
29
|
+
}, [currentLanguage]);
|
|
30
|
+
|
|
31
|
+
return <>{children}</>;
|
|
32
|
+
}
|