@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,306 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import { useSettings } from '../hooks/useSettings';
3
+ import { useConfig } from '@/features/config/useConfig';
4
+ import { Button } from '@/components/ui/button';
5
+ import { ButtonGroup } from '@/components/ui/button-group';
6
+ import { cn } from '@/lib/utils';
7
+ import { useEffect, useState } from 'react';
8
+ import { getAllThemes, registerTheme, type ThemeDefinition } from '@/features/theme/themes';
9
+
10
+ const SunIcon = () => (
11
+ <svg
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ width="16"
14
+ height="16"
15
+ viewBox="0 0 24 24"
16
+ fill="none"
17
+ stroke="currentColor"
18
+ strokeWidth="2"
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ >
22
+ <circle
23
+ cx="12"
24
+ cy="12"
25
+ r="4"
26
+ />
27
+ <path d="M12 2v2" />
28
+ <path d="M12 20v2" />
29
+ <path d="m4.93 4.93 1.41 1.41" />
30
+ <path d="m17.66 17.66 1.41 1.41" />
31
+ <path d="M2 12h2" />
32
+ <path d="M20 12h2" />
33
+ <path d="m6.34 17.66-1.41 1.41" />
34
+ <path d="m19.07 4.93-1.41 1.41" />
35
+ </svg>
36
+ );
37
+
38
+ const MoonIcon = () => (
39
+ <svg
40
+ xmlns="http://www.w3.org/2000/svg"
41
+ width="16"
42
+ height="16"
43
+ viewBox="0 0 24 24"
44
+ fill="none"
45
+ stroke="currentColor"
46
+ strokeWidth="2"
47
+ strokeLinecap="round"
48
+ strokeLinejoin="round"
49
+ >
50
+ <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
51
+ </svg>
52
+ );
53
+
54
+ const MonitorIcon = () => (
55
+ <svg
56
+ xmlns="http://www.w3.org/2000/svg"
57
+ width="16"
58
+ height="16"
59
+ viewBox="0 0 24 24"
60
+ fill="none"
61
+ stroke="currentColor"
62
+ strokeWidth="2"
63
+ strokeLinecap="round"
64
+ strokeLinejoin="round"
65
+ >
66
+ <rect
67
+ width="20"
68
+ height="14"
69
+ x="2"
70
+ y="3"
71
+ rx="2"
72
+ />
73
+ <line
74
+ x1="8"
75
+ x2="16"
76
+ y1="21"
77
+ y2="21"
78
+ />
79
+ <line
80
+ x1="12"
81
+ x2="12"
82
+ y1="17"
83
+ y2="21"
84
+ />
85
+ </svg>
86
+ );
87
+
88
+ // Theme color preview component
89
+ const ThemePreview = ({
90
+ theme,
91
+ isSelected,
92
+ isDark,
93
+ }: {
94
+ theme: ThemeDefinition;
95
+ isSelected: boolean;
96
+ isDark: boolean;
97
+ }) => {
98
+ const colors = isDark ? theme.colors.dark : theme.colors.light;
99
+
100
+ return (
101
+ <div
102
+ className={cn(
103
+ 'relative overflow-hidden rounded-lg border-2 transition-all',
104
+ isSelected ? 'border-primary shadow-lg' : 'border-border',
105
+ )}
106
+ style={{ backgroundColor: colors.background }}
107
+ >
108
+ <div className="p-3 space-y-2">
109
+ {/* Primary color */}
110
+ <div
111
+ className="h-8 rounded-md"
112
+ style={{ backgroundColor: colors.primary }}
113
+ />
114
+ {/* Secondary colors */}
115
+ <div className="flex gap-1">
116
+ <div
117
+ className="h-6 flex-1 rounded"
118
+ style={{ backgroundColor: colors.background }}
119
+ />
120
+ <div
121
+ className="h-6 flex-1 rounded"
122
+ style={{ backgroundColor: colors.secondary }}
123
+ />
124
+ <div
125
+ className="h-6 flex-1 rounded"
126
+ style={{ backgroundColor: colors.accent }}
127
+ />
128
+ </div>
129
+ {/* Accent colors */}
130
+ <div className="flex gap-1">
131
+ <div
132
+ className="h-4 flex-1 rounded"
133
+ style={{ backgroundColor: colors.muted }}
134
+ />
135
+ <div
136
+ className="h-4 flex-1 rounded"
137
+ style={{ backgroundColor: colors.border }}
138
+ />
139
+ </div>
140
+ </div>
141
+ {/* Theme name overlay */}
142
+ <div
143
+ className="absolute bottom-0 left-0 right-0 bg-background/90 backdrop-blur-sm px-2 py-1"
144
+ style={{ backgroundColor: colors.background }}
145
+ >
146
+ <p
147
+ className="text-xs font-medium text-center"
148
+ style={
149
+ theme.fontFamily
150
+ ? {
151
+ fontFamily: theme.fontFamily,
152
+ letterSpacing: theme.letterSpacing || 'normal',
153
+ textShadow: theme.textShadow || 'none',
154
+ }
155
+ : {}
156
+ }
157
+ >
158
+ {theme.displayName}
159
+ </p>
160
+ </div>
161
+ </div>
162
+ );
163
+ };
164
+
165
+ export const Appearance = () => {
166
+ const { t } = useTranslation('settings');
167
+ const { settings, updateSetting } = useSettings();
168
+ const { config } = useConfig();
169
+ const currentTheme = settings.appearance?.theme || 'system';
170
+ const currentThemeName = settings.appearance?.themeName || 'default';
171
+
172
+ const [availableThemes, setAvailableThemes] = useState<ThemeDefinition[]>([]);
173
+
174
+ // Register custom themes from config and get all themes
175
+ useEffect(() => {
176
+ if (config?.themes) {
177
+ config.themes.forEach((themeDef: ThemeDefinition) => {
178
+ registerTheme(themeDef);
179
+ });
180
+ }
181
+ setAvailableThemes(getAllThemes());
182
+ }, [config]);
183
+
184
+ // Determine if we're in dark mode for preview
185
+ const [isDarkForPreview, setIsDarkForPreview] = useState(() => {
186
+ if (typeof window === 'undefined') return false;
187
+ return (
188
+ currentTheme === 'dark' ||
189
+ (currentTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
190
+ );
191
+ });
192
+
193
+ // Update preview mode when theme changes
194
+ useEffect(() => {
195
+ if (typeof window === 'undefined') return;
196
+
197
+ const updatePreview = () => {
198
+ setIsDarkForPreview(
199
+ currentTheme === 'dark' ||
200
+ (currentTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches),
201
+ );
202
+ };
203
+
204
+ updatePreview();
205
+
206
+ if (currentTheme === 'system') {
207
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
208
+ const handleChange = () => updatePreview();
209
+
210
+ if (mediaQuery.addEventListener) {
211
+ mediaQuery.addEventListener('change', handleChange);
212
+ return () => mediaQuery.removeEventListener('change', handleChange);
213
+ } else if (mediaQuery.addListener) {
214
+ mediaQuery.addListener(handleChange);
215
+ return () => mediaQuery.removeListener(handleChange);
216
+ }
217
+ }
218
+ }, [currentTheme]);
219
+
220
+ const modeThemes = [
221
+ { value: 'light' as const, label: t('appearance.themes.light'), icon: SunIcon },
222
+ { value: 'dark' as const, label: t('appearance.themes.dark'), icon: MoonIcon },
223
+ { value: 'system' as const, label: t('appearance.themes.system'), icon: MonitorIcon },
224
+ ];
225
+
226
+ return (
227
+ <div className="space-y-6">
228
+ {/* Theme Mode Selection (Light/Dark/System) */}
229
+ <div className="space-y-2">
230
+ <div className="space-y-0.5">
231
+ <label
232
+ className="text-sm font-medium leading-none"
233
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
234
+ >
235
+ {t('appearance.mode')}
236
+ </label>
237
+ <p className="text-sm text-muted-foreground">{t('appearance.modeDescription')}</p>
238
+ </div>
239
+ <div className="mt-2">
240
+ <ButtonGroup>
241
+ {modeThemes.map((theme) => {
242
+ const Icon = theme.icon;
243
+ const isSelected = currentTheme === theme.value;
244
+ return (
245
+ <Button
246
+ key={theme.value}
247
+ variant={isSelected ? 'default' : 'outline'}
248
+ onClick={() => {
249
+ updateSetting('appearance', { theme: theme.value });
250
+ }}
251
+ className={cn(
252
+ 'h-10 px-4 transition-all flex items-center gap-2 cursor-pointer',
253
+ isSelected && ['font-semibold'],
254
+ !isSelected && ['bg-background hover:bg-accent/50', 'text-muted-foreground'],
255
+ )}
256
+ aria-label={theme.label}
257
+ title={theme.label}
258
+ >
259
+ <Icon />
260
+ <span className="text-sm font-medium">{theme.label}</span>
261
+ </Button>
262
+ );
263
+ })}
264
+ </ButtonGroup>
265
+ </div>
266
+ </div>
267
+
268
+ {/* Theme Selection (Color Scheme) */}
269
+ <div className="space-y-2">
270
+ <div className="space-y-0.5">
271
+ <label
272
+ className="text-sm font-medium leading-none"
273
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
274
+ >
275
+ {t('appearance.colorTheme')}
276
+ </label>
277
+ <p className="text-sm text-muted-foreground">{t('appearance.colorThemeDescription')}</p>
278
+ </div>
279
+ <div className="mt-2 grid grid-cols-2 md:grid-cols-3 gap-4">
280
+ {availableThemes.map((theme) => {
281
+ const isSelected = currentThemeName === theme.name;
282
+ return (
283
+ <button
284
+ key={theme.name}
285
+ onClick={() => {
286
+ updateSetting('appearance', { themeName: theme.name });
287
+ }}
288
+ className={cn(
289
+ 'text-left transition-all cursor-pointer',
290
+ isSelected && 'ring-2 ring-primary ring-offset-2 rounded-lg',
291
+ )}
292
+ aria-label={theme.displayName}
293
+ >
294
+ <ThemePreview
295
+ theme={theme}
296
+ isSelected={isSelected}
297
+ isDark={isDarkForPreview}
298
+ />
299
+ </button>
300
+ );
301
+ })}
302
+ </div>
303
+ </div>
304
+ </div>
305
+ );
306
+ };
@@ -0,0 +1,142 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import { useSettings } from '../hooks/useSettings';
3
+ import { useConfig } from '@/features/config/useConfig';
4
+ import { Button } from '@/components/ui/button';
5
+ import { shellui } from '@shellui/sdk';
6
+ import urls from '@/constants/urls';
7
+ import { cn } from '@/lib/utils';
8
+
9
+ export const DataPrivacy = () => {
10
+ const { t } = useTranslation('settings');
11
+ const { config } = useConfig();
12
+ const { settings, updateSetting } = useSettings();
13
+
14
+ const cookies = config?.cookieConsent?.cookies ?? [];
15
+ const hasCookieConsent = cookies.length > 0;
16
+ const hasConsented = Boolean(settings?.cookieConsent?.consentedCookieHosts?.length);
17
+
18
+ // Determine consent status
19
+ const acceptedHosts = settings?.cookieConsent?.acceptedHosts ?? [];
20
+ const allHosts = cookies.map((c) => c.host);
21
+ const strictNecessaryHosts = cookies
22
+ .filter((c) => c.category === 'strict_necessary')
23
+ .map((c) => c.host);
24
+
25
+ const acceptedAll =
26
+ hasConsented &&
27
+ acceptedHosts.length === allHosts.length &&
28
+ allHosts.every((h) => acceptedHosts.includes(h));
29
+ // "Rejected all" means only strictly necessary cookies are accepted
30
+ const rejectedAll =
31
+ hasConsented &&
32
+ acceptedHosts.length === strictNecessaryHosts.length &&
33
+ strictNecessaryHosts.every((h) => acceptedHosts.includes(h)) &&
34
+ !acceptedHosts.some((h) => !strictNecessaryHosts.includes(h));
35
+ const isCustom = hasConsented && !acceptedAll && !rejectedAll;
36
+
37
+ const handleResetCookieConsent = () => {
38
+ updateSetting('cookieConsent', {
39
+ acceptedHosts: [],
40
+ consentedCookieHosts: [],
41
+ });
42
+ };
43
+
44
+ return (
45
+ <div className="space-y-6">
46
+ {!hasCookieConsent ? (
47
+ <div className="rounded-lg border bg-card p-4 space-y-3">
48
+ <div className="space-y-1">
49
+ <h3
50
+ className="text-sm font-medium leading-none"
51
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
52
+ >
53
+ {t('dataPrivacy.noCookiesConfigured.title')}
54
+ </h3>
55
+ <p className="text-sm text-muted-foreground">
56
+ {t('dataPrivacy.noCookiesConfigured.description')}
57
+ </p>
58
+ </div>
59
+ <div className="flex items-start gap-2 text-sm">
60
+ <span className="text-green-600 dark:text-green-400 mt-0.5">✓</span>
61
+ <span className="text-muted-foreground">
62
+ {t('dataPrivacy.noCookiesConfigured.info')}
63
+ </span>
64
+ </div>
65
+ </div>
66
+ ) : (
67
+ <div className="space-y-2">
68
+ <label
69
+ className="text-sm font-medium leading-none"
70
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
71
+ >
72
+ {t('dataPrivacy.cookieConsent.title')}
73
+ </label>
74
+ <div className="mt-2 space-y-3">
75
+ <p className="text-sm text-muted-foreground">
76
+ {!hasConsented
77
+ ? t('dataPrivacy.cookieConsent.descriptionNotConsented')
78
+ : acceptedAll
79
+ ? t('dataPrivacy.cookieConsent.statusAcceptedAll')
80
+ : rejectedAll
81
+ ? t('dataPrivacy.cookieConsent.statusRejectedAll')
82
+ : t('dataPrivacy.cookieConsent.statusCustom')}
83
+ </p>
84
+ {hasConsented && (
85
+ <div className="flex items-center gap-2 text-sm mt-2">
86
+ <span
87
+ className={cn(
88
+ 'px-2 py-1 rounded-md text-xs font-medium transition-colors',
89
+ acceptedAll
90
+ ? 'bg-primary/10 text-primary border border-primary/20'
91
+ : 'text-muted-foreground',
92
+ )}
93
+ >
94
+ {t('dataPrivacy.cookieConsent.labelAcceptedAll')}
95
+ </span>
96
+ <span className="text-muted-foreground/30">|</span>
97
+ <span
98
+ className={cn(
99
+ 'px-2 py-1 rounded-md text-xs font-medium transition-colors',
100
+ isCustom
101
+ ? 'bg-muted text-muted-foreground border border-border'
102
+ : 'text-muted-foreground',
103
+ )}
104
+ >
105
+ {t('dataPrivacy.cookieConsent.labelCustom')}
106
+ </span>
107
+ <span className="text-muted-foreground/30">|</span>
108
+ <span
109
+ className={cn(
110
+ 'px-2 py-1 rounded-md text-xs font-medium transition-colors',
111
+ rejectedAll
112
+ ? 'bg-muted text-muted-foreground border border-border'
113
+ : 'text-muted-foreground',
114
+ )}
115
+ >
116
+ {t('dataPrivacy.cookieConsent.labelRejectedAll')}
117
+ </span>
118
+ </div>
119
+ )}
120
+ <div className="flex flex-wrap gap-2">
121
+ <Button
122
+ variant="outline"
123
+ onClick={() => shellui.openDrawer({ url: urls.cookiePreferences, size: '420px' })}
124
+ className="w-full sm:w-auto"
125
+ >
126
+ {t('dataPrivacy.cookieConsent.manageButton')}
127
+ </Button>
128
+ <Button
129
+ variant="ghost"
130
+ onClick={handleResetCookieConsent}
131
+ disabled={!hasConsented}
132
+ className="w-full sm:w-auto"
133
+ >
134
+ {t('dataPrivacy.cookieConsent.button')}
135
+ </Button>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ )}
140
+ </div>
141
+ );
142
+ };
@@ -0,0 +1,174 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import { shellui } from '@shellui/sdk';
3
+ import { useSettings } from '../hooks/useSettings';
4
+ import { useConfig } from '../../config/useConfig';
5
+ import { flattenNavigationItems, resolveLocalizedString } from '../../layouts/utils';
6
+ import { Switch } from '@/components/ui/switch';
7
+ import { Select } from '@/components/ui/select';
8
+ import { Button } from '@/components/ui/button';
9
+ import { ToastTestButtons } from './develop/ToastTestButtons';
10
+ import { DialogTestButtons } from './develop/DialogTestButtons';
11
+ import { ModalTestButtons } from './develop/ModalTestButtons';
12
+ import { DrawerTestButtons } from './develop/DrawerTestButtons';
13
+ import type { LayoutType } from '../../config/types';
14
+
15
+ export const Develop = () => {
16
+ const { t } = useTranslation('settings');
17
+ const { settings, updateSetting } = useSettings();
18
+ const { config } = useConfig();
19
+ const currentLanguage = settings.language?.code || 'en';
20
+ const effectiveLayout: LayoutType = settings.layout ?? config?.layout ?? 'sidebar';
21
+ const navItems = config?.navigation?.length
22
+ ? flattenNavigationItems(config?.navigation ?? []).filter(
23
+ (item, index, self) => index === self.findIndex((i) => i.path === item.path),
24
+ )
25
+ : [];
26
+
27
+ return (
28
+ <div className="space-y-6">
29
+ <div>
30
+ <h3
31
+ className="text-lg font-semibold mb-2"
32
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
33
+ >
34
+ {t('develop.logging.title')}
35
+ </h3>
36
+ <div className="space-y-4">
37
+ <div className="flex items-center justify-between">
38
+ <div className="space-y-0.5">
39
+ <label
40
+ htmlFor="log-shellsdk"
41
+ className="text-sm font-medium leading-none"
42
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
43
+ >
44
+ {t('develop.logging.shellsdk.label')}
45
+ </label>
46
+ <p className="text-sm text-muted-foreground">
47
+ {t('develop.logging.shellsdk.description')}
48
+ </p>
49
+ </div>
50
+ <Switch
51
+ id="log-shellsdk"
52
+ checked={settings.logging?.namespaces?.shellsdk || false}
53
+ onCheckedChange={(checked) =>
54
+ updateSetting('logging', {
55
+ namespaces: {
56
+ ...settings.logging?.namespaces,
57
+ shellsdk: checked,
58
+ },
59
+ })
60
+ }
61
+ />
62
+ </div>
63
+
64
+ <div className="flex items-center justify-between">
65
+ <div className="space-y-0.5">
66
+ <label
67
+ htmlFor="log-shellcore"
68
+ className="text-sm font-medium leading-none"
69
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
70
+ >
71
+ {t('develop.logging.shellcore.label')}
72
+ </label>
73
+ <p className="text-sm text-muted-foreground">
74
+ {t('develop.logging.shellcore.description')}
75
+ </p>
76
+ </div>
77
+ <Switch
78
+ id="log-shellcore"
79
+ checked={settings.logging?.namespaces?.shellcore || false}
80
+ onCheckedChange={(checked) =>
81
+ updateSetting('logging', {
82
+ namespaces: {
83
+ ...settings.logging?.namespaces,
84
+ shellcore: checked,
85
+ },
86
+ })
87
+ }
88
+ />
89
+ </div>
90
+ </div>
91
+ </div>
92
+
93
+ <div>
94
+ <h3
95
+ className="text-lg font-semibold mb-2"
96
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
97
+ >
98
+ {t('develop.navigation.title')}
99
+ </h3>
100
+ {navItems.length ? (
101
+ <div className="space-y-2">
102
+ <label
103
+ htmlFor="develop-nav-select"
104
+ className="text-sm font-medium leading-none"
105
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
106
+ >
107
+ {t('develop.navigation.selectLabel')}
108
+ </label>
109
+ <div className="mt-2">
110
+ <Select
111
+ id="develop-nav-select"
112
+ defaultValue=""
113
+ onChange={(e) => {
114
+ const path = e.target.value;
115
+ if (path) {
116
+ shellui.navigate(path.startsWith('/') ? path : `/${path}`);
117
+ }
118
+ }}
119
+ >
120
+ <option value="">{t('develop.navigation.placeholder')}</option>
121
+ {navItems.map((item) => (
122
+ <option
123
+ key={item.path}
124
+ value={`/${item.path}`}
125
+ >
126
+ {resolveLocalizedString(item.label, currentLanguage) || item.path}
127
+ </option>
128
+ ))}
129
+ </Select>
130
+ </div>
131
+ </div>
132
+ ) : (
133
+ <p className="text-sm text-muted-foreground">{t('develop.navigation.empty')}</p>
134
+ )}
135
+ </div>
136
+
137
+ <div>
138
+ <h3
139
+ className="text-lg font-semibold mb-2"
140
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
141
+ >
142
+ {t('develop.layout.title')}
143
+ </h3>
144
+ <div className="flex flex-wrap gap-2">
145
+ {(['sidebar', 'windows'] as const).map((layoutMode) => (
146
+ <Button
147
+ key={layoutMode}
148
+ variant={effectiveLayout === layoutMode ? 'default' : 'outline'}
149
+ size="sm"
150
+ onClick={() => updateSetting('layout', layoutMode as LayoutType)}
151
+ >
152
+ {t(`develop.layout.${layoutMode}`)}
153
+ </Button>
154
+ ))}
155
+ </div>
156
+ </div>
157
+
158
+ <div>
159
+ <h3
160
+ className="text-lg font-semibold mb-2"
161
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
162
+ >
163
+ {t('develop.testing.title')}
164
+ </h3>
165
+ <div className="space-y-4">
166
+ <ToastTestButtons />
167
+ <DialogTestButtons />
168
+ <ModalTestButtons />
169
+ <DrawerTestButtons />
170
+ </div>
171
+ </div>
172
+ </div>
173
+ );
174
+ };