@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.
Files changed (247) hide show
  1. package/README.md +17 -0
  2. package/dist/ContentView-CZG-ro_B.js +146 -0
  3. package/dist/ContentView-CZG-ro_B.js.map +1 -0
  4. package/dist/CookiePreferencesView-MhO9FO-4.js +213 -0
  5. package/dist/CookiePreferencesView-MhO9FO-4.js.map +1 -0
  6. package/dist/DefaultLayout-Dbb3uJED.js +394 -0
  7. package/dist/DefaultLayout-Dbb3uJED.js.map +1 -0
  8. package/dist/FullscreenLayout-1SgPHWw-.js +30 -0
  9. package/dist/FullscreenLayout-1SgPHWw-.js.map +1 -0
  10. package/dist/HomeView-DYU-O_Il.js +21 -0
  11. package/dist/HomeView-DYU-O_Il.js.map +1 -0
  12. package/dist/NotFoundView-CeYjJNg0.js +52 -0
  13. package/dist/NotFoundView-CeYjJNg0.js.map +1 -0
  14. package/dist/OverlayShell-pzbqQW25.js +642 -0
  15. package/dist/OverlayShell-pzbqQW25.js.map +1 -0
  16. package/dist/SettingsView-Bndrta44.js +2207 -0
  17. package/dist/SettingsView-Bndrta44.js.map +1 -0
  18. package/dist/ViewRoute-ChSPabOy.js +32 -0
  19. package/dist/ViewRoute-ChSPabOy.js.map +1 -0
  20. package/dist/WindowsLayout-CXGNPKoY.js +633 -0
  21. package/dist/WindowsLayout-CXGNPKoY.js.map +1 -0
  22. package/dist/app.d.ts +3 -0
  23. package/dist/app.d.ts.map +1 -0
  24. package/dist/components/ContentView.d.ts +10 -0
  25. package/dist/components/ContentView.d.ts.map +1 -0
  26. package/dist/components/HomeView.d.ts +2 -0
  27. package/dist/components/HomeView.d.ts.map +1 -0
  28. package/dist/components/LoadingOverlay.d.ts +2 -0
  29. package/dist/components/LoadingOverlay.d.ts.map +1 -0
  30. package/dist/components/NotFoundView.d.ts +2 -0
  31. package/dist/components/NotFoundView.d.ts.map +1 -0
  32. package/dist/components/RouteErrorBoundary.d.ts +2 -0
  33. package/dist/components/RouteErrorBoundary.d.ts.map +1 -0
  34. package/dist/components/ViewRoute.d.ts +7 -0
  35. package/dist/components/ViewRoute.d.ts.map +1 -0
  36. package/dist/components/ui/alert-dialog.d.ts +32 -0
  37. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  38. package/dist/components/ui/breadcrumb.d.ts +20 -0
  39. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  40. package/dist/components/ui/button-group.d.ts +7 -0
  41. package/dist/components/ui/button-group.d.ts.map +1 -0
  42. package/dist/components/ui/button.d.ts +12 -0
  43. package/dist/components/ui/button.d.ts.map +1 -0
  44. package/dist/components/ui/dialog.d.ts +24 -0
  45. package/dist/components/ui/dialog.d.ts.map +1 -0
  46. package/dist/components/ui/drawer.d.ts +38 -0
  47. package/dist/components/ui/drawer.d.ts.map +1 -0
  48. package/dist/components/ui/select.d.ts +5 -0
  49. package/dist/components/ui/select.d.ts.map +1 -0
  50. package/dist/components/ui/sidebar.d.ts +46 -0
  51. package/dist/components/ui/sidebar.d.ts.map +1 -0
  52. package/dist/components/ui/sonner.d.ts +6 -0
  53. package/dist/components/ui/sonner.d.ts.map +1 -0
  54. package/dist/components/ui/switch.d.ts +8 -0
  55. package/dist/components/ui/switch.d.ts.map +1 -0
  56. package/dist/constants/urls.d.ts +6 -0
  57. package/dist/constants/urls.d.ts.map +1 -0
  58. package/dist/constants/urls.js +8 -0
  59. package/dist/constants/urls.js.map +1 -0
  60. package/dist/features/alertDialog/DialogContext.d.ts +12 -0
  61. package/dist/features/alertDialog/DialogContext.d.ts.map +1 -0
  62. package/dist/features/config/ConfigProvider.d.ts +15 -0
  63. package/dist/features/config/ConfigProvider.d.ts.map +1 -0
  64. package/dist/features/config/types.d.ts +177 -0
  65. package/dist/features/config/types.d.ts.map +1 -0
  66. package/dist/features/config/useConfig.d.ts +8 -0
  67. package/dist/features/config/useConfig.d.ts.map +1 -0
  68. package/dist/features/cookieConsent/CookieConsentModal.d.ts +6 -0
  69. package/dist/features/cookieConsent/CookieConsentModal.d.ts.map +1 -0
  70. package/dist/features/cookieConsent/CookiePreferencesView.d.ts +2 -0
  71. package/dist/features/cookieConsent/CookiePreferencesView.d.ts.map +1 -0
  72. package/dist/features/cookieConsent/cookieConsent.d.ts +22 -0
  73. package/dist/features/cookieConsent/cookieConsent.d.ts.map +1 -0
  74. package/dist/features/cookieConsent/useCookieConsent.d.ts +15 -0
  75. package/dist/features/cookieConsent/useCookieConsent.d.ts.map +1 -0
  76. package/dist/features/drawer/DrawerContext.d.ts +24 -0
  77. package/dist/features/drawer/DrawerContext.d.ts.map +1 -0
  78. package/dist/features/layouts/AppLayout.d.ts +12 -0
  79. package/dist/features/layouts/AppLayout.d.ts.map +1 -0
  80. package/dist/features/layouts/DefaultLayout.d.ts +10 -0
  81. package/dist/features/layouts/DefaultLayout.d.ts.map +1 -0
  82. package/dist/features/layouts/FullscreenLayout.d.ts +9 -0
  83. package/dist/features/layouts/FullscreenLayout.d.ts.map +1 -0
  84. package/dist/features/layouts/LayoutProviders.d.ts +9 -0
  85. package/dist/features/layouts/LayoutProviders.d.ts.map +1 -0
  86. package/dist/features/layouts/OverlayShell.d.ts +10 -0
  87. package/dist/features/layouts/OverlayShell.d.ts.map +1 -0
  88. package/dist/features/layouts/WindowsLayout.d.ts +24 -0
  89. package/dist/features/layouts/WindowsLayout.d.ts.map +1 -0
  90. package/dist/features/layouts/utils.d.ts +16 -0
  91. package/dist/features/layouts/utils.d.ts.map +1 -0
  92. package/dist/features/modal/ModalContext.d.ts +20 -0
  93. package/dist/features/modal/ModalContext.d.ts.map +1 -0
  94. package/dist/features/sentry/initSentry.d.ts +14 -0
  95. package/dist/features/sentry/initSentry.d.ts.map +1 -0
  96. package/dist/features/settings/SettingsContext.d.ts +10 -0
  97. package/dist/features/settings/SettingsContext.d.ts.map +1 -0
  98. package/dist/features/settings/SettingsIcons.d.ts +22 -0
  99. package/dist/features/settings/SettingsIcons.d.ts.map +1 -0
  100. package/dist/features/settings/SettingsProvider.d.ts +5 -0
  101. package/dist/features/settings/SettingsProvider.d.ts.map +1 -0
  102. package/dist/features/settings/SettingsRoutes.d.ts +7 -0
  103. package/dist/features/settings/SettingsRoutes.d.ts.map +1 -0
  104. package/dist/features/settings/SettingsView.d.ts +2 -0
  105. package/dist/features/settings/SettingsView.d.ts.map +1 -0
  106. package/dist/features/settings/components/Advanced.d.ts +2 -0
  107. package/dist/features/settings/components/Advanced.d.ts.map +1 -0
  108. package/dist/features/settings/components/Appearance.d.ts +2 -0
  109. package/dist/features/settings/components/Appearance.d.ts.map +1 -0
  110. package/dist/features/settings/components/DataPrivacy.d.ts +2 -0
  111. package/dist/features/settings/components/DataPrivacy.d.ts.map +1 -0
  112. package/dist/features/settings/components/Develop.d.ts +2 -0
  113. package/dist/features/settings/components/Develop.d.ts.map +1 -0
  114. package/dist/features/settings/components/LanguageAndRegion.d.ts +2 -0
  115. package/dist/features/settings/components/LanguageAndRegion.d.ts.map +1 -0
  116. package/dist/features/settings/components/ServiceWorker.d.ts +2 -0
  117. package/dist/features/settings/components/ServiceWorker.d.ts.map +1 -0
  118. package/dist/features/settings/components/UpdateApp.d.ts +2 -0
  119. package/dist/features/settings/components/UpdateApp.d.ts.map +1 -0
  120. package/dist/features/settings/components/develop/DialogTestButtons.d.ts +2 -0
  121. package/dist/features/settings/components/develop/DialogTestButtons.d.ts.map +1 -0
  122. package/dist/features/settings/components/develop/DrawerTestButtons.d.ts +2 -0
  123. package/dist/features/settings/components/develop/DrawerTestButtons.d.ts.map +1 -0
  124. package/dist/features/settings/components/develop/ModalTestButtons.d.ts +2 -0
  125. package/dist/features/settings/components/develop/ModalTestButtons.d.ts.map +1 -0
  126. package/dist/features/settings/components/develop/ToastTestButtons.d.ts +2 -0
  127. package/dist/features/settings/components/develop/ToastTestButtons.d.ts.map +1 -0
  128. package/dist/features/settings/hooks/useSettings.d.ts +2 -0
  129. package/dist/features/settings/hooks/useSettings.d.ts.map +1 -0
  130. package/dist/features/sonner/SonnerContext.d.ts +29 -0
  131. package/dist/features/sonner/SonnerContext.d.ts.map +1 -0
  132. package/dist/features/theme/ThemeProvider.d.ts +11 -0
  133. package/dist/features/theme/ThemeProvider.d.ts.map +1 -0
  134. package/dist/features/theme/themes.d.ts +114 -0
  135. package/dist/features/theme/themes.d.ts.map +1 -0
  136. package/dist/features/theme/useTheme.d.ts +10 -0
  137. package/dist/features/theme/useTheme.d.ts.map +1 -0
  138. package/dist/i18n/I18nProvider.d.ts +9 -0
  139. package/dist/i18n/I18nProvider.d.ts.map +1 -0
  140. package/dist/i18n/config.d.ts +23 -0
  141. package/dist/i18n/config.d.ts.map +1 -0
  142. package/dist/i18n/translations/en/common.json.d.ts +19 -0
  143. package/dist/i18n/translations/en/cookieConsent.json.d.ts +53 -0
  144. package/dist/i18n/translations/en/settings.json.d.ts +358 -0
  145. package/dist/i18n/translations/fr/common.json.d.ts +19 -0
  146. package/dist/i18n/translations/fr/cookieConsent.json.d.ts +53 -0
  147. package/dist/i18n/translations/fr/settings.json.d.ts +358 -0
  148. package/dist/index-lmRk5L6z.js +2160 -0
  149. package/dist/index-lmRk5L6z.js.map +1 -0
  150. package/dist/index.d.ts +7 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +12 -0
  153. package/dist/index.js.map +1 -0
  154. package/dist/lib/utils.d.ts +3 -0
  155. package/dist/lib/utils.d.ts.map +1 -0
  156. package/dist/lib/z-index.d.ts +29 -0
  157. package/dist/lib/z-index.d.ts.map +1 -0
  158. package/dist/router/router.d.ts +3 -0
  159. package/dist/router/router.d.ts.map +1 -0
  160. package/dist/router/routes.d.ts +4 -0
  161. package/dist/router/routes.d.ts.map +1 -0
  162. package/dist/sidebar-ClIeZ2zb.js +303 -0
  163. package/dist/sidebar-ClIeZ2zb.js.map +1 -0
  164. package/dist/style.css +1 -0
  165. package/dist/switch-8SzUJz7Q.js +44 -0
  166. package/dist/switch-8SzUJz7Q.js.map +1 -0
  167. package/dist/types.js +2 -0
  168. package/dist/types.js.map +1 -0
  169. package/package.json +93 -0
  170. package/postcss.config.js +6 -0
  171. package/src/app.tsx +119 -0
  172. package/src/components/ContentView.tsx +258 -0
  173. package/src/components/HomeView.tsx +19 -0
  174. package/src/components/LoadingOverlay.tsx +12 -0
  175. package/src/components/NotFoundView.tsx +84 -0
  176. package/src/components/RouteErrorBoundary.tsx +95 -0
  177. package/src/components/ViewRoute.tsx +47 -0
  178. package/src/components/ui/alert-dialog.tsx +181 -0
  179. package/src/components/ui/breadcrumb.tsx +155 -0
  180. package/src/components/ui/button-group.tsx +52 -0
  181. package/src/components/ui/button.tsx +51 -0
  182. package/src/components/ui/dialog.tsx +160 -0
  183. package/src/components/ui/drawer.tsx +200 -0
  184. package/src/components/ui/select.tsx +24 -0
  185. package/src/components/ui/sidebar.tsx +406 -0
  186. package/src/components/ui/sonner.tsx +36 -0
  187. package/src/components/ui/switch.tsx +45 -0
  188. package/src/constants/urls.ts +4 -0
  189. package/src/features/alertDialog/DialogContext.tsx +468 -0
  190. package/src/features/config/ConfigProvider.ts +96 -0
  191. package/src/features/config/types.ts +195 -0
  192. package/src/features/config/useConfig.ts +15 -0
  193. package/src/features/cookieConsent/CookieConsentModal.tsx +122 -0
  194. package/src/features/cookieConsent/CookiePreferencesView.tsx +328 -0
  195. package/src/features/cookieConsent/cookieConsent.ts +84 -0
  196. package/src/features/cookieConsent/useCookieConsent.ts +39 -0
  197. package/src/features/drawer/DrawerContext.tsx +116 -0
  198. package/src/features/layouts/AppLayout.tsx +63 -0
  199. package/src/features/layouts/DefaultLayout.tsx +625 -0
  200. package/src/features/layouts/FullscreenLayout.tsx +55 -0
  201. package/src/features/layouts/LayoutProviders.tsx +20 -0
  202. package/src/features/layouts/OverlayShell.tsx +171 -0
  203. package/src/features/layouts/WindowsLayout.tsx +860 -0
  204. package/src/features/layouts/utils.ts +99 -0
  205. package/src/features/modal/ModalContext.tsx +112 -0
  206. package/src/features/sentry/initSentry.ts +72 -0
  207. package/src/features/settings/SettingsContext.tsx +19 -0
  208. package/src/features/settings/SettingsIcons.tsx +452 -0
  209. package/src/features/settings/SettingsProvider.tsx +341 -0
  210. package/src/features/settings/SettingsRoutes.tsx +66 -0
  211. package/src/features/settings/SettingsView.tsx +327 -0
  212. package/src/features/settings/components/Advanced.tsx +128 -0
  213. package/src/features/settings/components/Appearance.tsx +306 -0
  214. package/src/features/settings/components/DataPrivacy.tsx +142 -0
  215. package/src/features/settings/components/Develop.tsx +174 -0
  216. package/src/features/settings/components/LanguageAndRegion.tsx +329 -0
  217. package/src/features/settings/components/ServiceWorker.tsx +363 -0
  218. package/src/features/settings/components/UpdateApp.tsx +206 -0
  219. package/src/features/settings/components/develop/DialogTestButtons.tsx +137 -0
  220. package/src/features/settings/components/develop/DrawerTestButtons.tsx +67 -0
  221. package/src/features/settings/components/develop/ModalTestButtons.tsx +30 -0
  222. package/src/features/settings/components/develop/ToastTestButtons.tsx +179 -0
  223. package/src/features/settings/hooks/useSettings.tsx +10 -0
  224. package/src/features/sonner/SonnerContext.tsx +286 -0
  225. package/src/features/theme/ThemeProvider.tsx +16 -0
  226. package/src/features/theme/themes.ts +561 -0
  227. package/src/features/theme/useTheme.tsx +71 -0
  228. package/src/i18n/I18nProvider.tsx +32 -0
  229. package/src/i18n/config.ts +107 -0
  230. package/src/i18n/translations/en/common.json +16 -0
  231. package/src/i18n/translations/en/cookieConsent.json +50 -0
  232. package/src/i18n/translations/en/settings.json +355 -0
  233. package/src/i18n/translations/fr/common.json +16 -0
  234. package/src/i18n/translations/fr/cookieConsent.json +50 -0
  235. package/src/i18n/translations/fr/settings.json +355 -0
  236. package/src/index.css +412 -0
  237. package/src/index.html +100 -0
  238. package/src/index.ts +31 -0
  239. package/src/lib/utils.ts +6 -0
  240. package/src/lib/z-index.ts +29 -0
  241. package/src/main.tsx +26 -0
  242. package/src/router/router.tsx +8 -0
  243. package/src/router/routes.tsx +115 -0
  244. package/src/service-worker/register.ts +1199 -0
  245. package/src/service-worker/sw-dev.ts +87 -0
  246. package/src/service-worker/sw.ts +105 -0
  247. 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
+ }