@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
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@shellui/core",
3
+ "version": "0.0.4",
4
+ "description": "ShellUI Core - Core React application runtime",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./types": {
15
+ "types": "./dist/types.d.ts",
16
+ "import": "./dist/types.js",
17
+ "default": "./dist/types.js"
18
+ },
19
+ "./constants/urls": {
20
+ "types": "./dist/constants/urls.d.ts",
21
+ "import": "./dist/constants/urls.js",
22
+ "default": "./dist/constants/urls.js"
23
+ },
24
+ "./style.css": "./dist/style.css"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "postcss.config.js",
30
+ "tailwind.config.js",
31
+ "README.md",
32
+ "package.json"
33
+ ],
34
+ "keywords": [
35
+ "shellui",
36
+ "core",
37
+ "react",
38
+ "microfrontend"
39
+ ],
40
+ "author": "ShellUI",
41
+ "license": "MIT",
42
+ "dependencies": {
43
+ "@radix-ui/react-collapsible": "^1.1.12",
44
+ "@sentry/react": "^8.0.0",
45
+ "@radix-ui/react-alert-dialog": "^1.1.15",
46
+ "@radix-ui/react-dialog": "^1.1.15",
47
+ "@radix-ui/react-separator": "^1.1.8",
48
+ "@radix-ui/react-slot": "^1.2.4",
49
+ "class-variance-authority": "^0.7.1",
50
+ "clsx": "^2.1.1",
51
+ "react": "^18.0.0 || ^19.0.0",
52
+ "react-dom": "^18.0.0 || ^19.0.0",
53
+ "i18next": "^23.15.0",
54
+ "react-i18next": "^15.1.0",
55
+ "react-router": "^7.12.0",
56
+ "roarr": "^7.21.2",
57
+ "sonner": "^2.0.7",
58
+ "tailwind-merge": "^3.4.0",
59
+ "vaul": "^1.1.2",
60
+ "workbox-window": "^7.1.0",
61
+ "workbox-precaching": "^7.1.0",
62
+ "workbox-routing": "^7.1.0",
63
+ "workbox-strategies": "^7.1.0",
64
+ "workbox-cacheable-response": "^7.1.0",
65
+ "workbox-expiration": "^7.1.0",
66
+ "@shellui/sdk": "0.0.4"
67
+ },
68
+ "peerDependencies": {
69
+ "react": "^18.0.0 || ^19.0.0",
70
+ "react-dom": "^18.0.0 || ^19.0.0"
71
+ },
72
+ "publishConfig": {
73
+ "access": "public"
74
+ },
75
+ "devDependencies": {
76
+ "@tailwindcss/postcss": "^4.1.18",
77
+ "@tailwindcss/typography": "^0.5.19",
78
+ "@types/react": "^19.2.7",
79
+ "@types/react-dom": "^19.2.3",
80
+ "@vitejs/plugin-react": "^5.1.2",
81
+ "autoprefixer": "^10.4.23",
82
+ "postcss": "^8.5.6",
83
+ "tailwindcss": "^4.1.18",
84
+ "typescript": "^5.0.0",
85
+ "vite": "^7.3.1",
86
+ "vite-plugin-dts": "^4.5.0"
87
+ },
88
+ "scripts": {
89
+ "build": "vite build",
90
+ "clean": "rm -rf dist",
91
+ "test": "echo \"No tests specified for @shellui/core\""
92
+ }
93
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ autoprefixer: {},
5
+ },
6
+ };
package/src/app.tsx ADDED
@@ -0,0 +1,119 @@
1
+ import { useMemo, useLayoutEffect, useState, useEffect } from 'react';
2
+ import { RouterProvider } from 'react-router';
3
+ import { shellui } from '@shellui/sdk';
4
+ import { useConfig } from './features/config/useConfig';
5
+ import { ConfigProvider } from './features/config/ConfigProvider';
6
+ import { createAppRouter } from './router/router';
7
+ import { SettingsProvider } from './features/settings/SettingsProvider';
8
+ import { ThemeProvider } from './features/theme/ThemeProvider';
9
+ import { I18nProvider } from './i18n/I18nProvider';
10
+ import { DialogProvider } from './features/alertDialog/DialogContext';
11
+ import { CookieConsentModal } from './features/cookieConsent/CookieConsentModal';
12
+ import './features/sentry/initSentry';
13
+ import './i18n/config'; // Initialize i18n
14
+ import './index.css';
15
+ import { registerServiceWorker, unregisterServiceWorker, isTauri } from './service-worker/register';
16
+ import { useSettings } from './features/settings/hooks/useSettings';
17
+
18
+ const AppContent = () => {
19
+ const { config } = useConfig();
20
+ const { settings } = useSettings();
21
+
22
+ // Apply favicon from config when available (allows projects to override default)
23
+ useEffect(() => {
24
+ if (config?.favicon) {
25
+ const link = document.querySelector<HTMLLinkElement>('link[rel="icon"]');
26
+ if (link) link.href = config.favicon;
27
+ }
28
+ }, [config?.favicon]);
29
+
30
+ // Register or unregister service worker based on setting
31
+ useEffect(() => {
32
+ if (isTauri()) {
33
+ unregisterServiceWorker();
34
+ return;
35
+ }
36
+ const serviceWorkerEnabled = settings?.serviceWorker?.enabled ?? true; // Default to enabled
37
+
38
+ // Don't register service worker if navigation is empty or undefined
39
+ // This helps prevent issues in development or misconfigured apps
40
+ if (!config?.navigation || config.navigation.length === 0) {
41
+ if (serviceWorkerEnabled) {
42
+ // eslint-disable-next-line no-console
43
+ console.warn('[Service Worker] Disabled: No navigation items configured');
44
+ unregisterServiceWorker();
45
+ }
46
+ return;
47
+ }
48
+
49
+ if (serviceWorkerEnabled) {
50
+ registerServiceWorker({
51
+ enabled: true,
52
+ });
53
+ } else {
54
+ unregisterServiceWorker();
55
+ }
56
+ }, [settings?.serviceWorker?.enabled, config?.navigation]);
57
+
58
+ // Create router from config using data mode
59
+ const router = useMemo(() => {
60
+ if (!config) {
61
+ return null;
62
+ }
63
+ return createAppRouter(config);
64
+ }, [config]);
65
+
66
+ // If no navigation, show simple layout
67
+ if (!config.navigation || config.navigation.length === 0) {
68
+ return (
69
+ <>
70
+ <CookieConsentModal />
71
+ <div style={{ fontFamily: 'system-ui, sans-serif', padding: '2rem' }}>
72
+ <h1>{config.title || 'ShellUI'}</h1>
73
+ <p>No navigation items configured.</p>
74
+ </div>
75
+ </>
76
+ );
77
+ }
78
+
79
+ if (!router) {
80
+ return null;
81
+ }
82
+
83
+ return (
84
+ <>
85
+ <CookieConsentModal />
86
+ <RouterProvider router={router} />
87
+ </>
88
+ );
89
+ };
90
+
91
+ const App = () => {
92
+ const [isLoading, setIsLoading] = useState(true);
93
+ // Initialize ShellUI SDK to support recursive nesting
94
+ useLayoutEffect(() => {
95
+ shellui.init().then(() => {
96
+ setIsLoading(false);
97
+ });
98
+ }, []);
99
+
100
+ if (isLoading) {
101
+ return null;
102
+ }
103
+
104
+ return (
105
+ <ConfigProvider>
106
+ <SettingsProvider>
107
+ <ThemeProvider>
108
+ <I18nProvider>
109
+ <DialogProvider>
110
+ <AppContent />
111
+ </DialogProvider>
112
+ </I18nProvider>
113
+ </ThemeProvider>
114
+ </SettingsProvider>
115
+ </ConfigProvider>
116
+ );
117
+ };
118
+
119
+ export default App;
@@ -0,0 +1,258 @@
1
+ /* eslint-disable no-console */
2
+ import type { NavigationItem } from '@/features/config/types';
3
+ import {
4
+ addIframe,
5
+ removeIframe,
6
+ shellui,
7
+ getLogger,
8
+ type ShellUIUrlPayload,
9
+ type ShellUIMessage,
10
+ } from '@shellui/sdk';
11
+ import { useEffect, useRef, useState } from 'react';
12
+ import { useNavigate } from 'react-router';
13
+ import { LoadingOverlay } from './LoadingOverlay';
14
+
15
+ const logger = getLogger('shellcore');
16
+
17
+ interface ContentViewProps {
18
+ url: string;
19
+ pathPrefix: string;
20
+ ignoreMessages?: boolean;
21
+ navItem: NavigationItem;
22
+ }
23
+
24
+ export const ContentView = ({
25
+ url,
26
+ pathPrefix,
27
+ ignoreMessages = false,
28
+ navItem,
29
+ }: ContentViewProps) => {
30
+ const navigate = useNavigate();
31
+ const iframeRef = useRef<HTMLIFrameElement>(null);
32
+ const isInternalNavigation = useRef(false);
33
+ const [initialUrl] = useState(url);
34
+ const [isLoading, setIsLoading] = useState(true);
35
+
36
+ useEffect(() => {
37
+ if (!iframeRef.current) {
38
+ return;
39
+ }
40
+ const iframeId = addIframe(iframeRef.current);
41
+ return () => {
42
+ removeIframe(iframeId);
43
+ };
44
+ }, []);
45
+
46
+ // Sync parent URL when iframe notifies us of a change
47
+ useEffect(() => {
48
+ const cleanup = shellui.addMessageListener(
49
+ 'SHELLUI_URL_CHANGED',
50
+ (data: ShellUIMessage, event: MessageEvent) => {
51
+ if (ignoreMessages) {
52
+ return;
53
+ }
54
+
55
+ // Ignore URL CHANGE from other than ContentView iframe
56
+ if (event.source !== iframeRef.current?.contentWindow) {
57
+ return;
58
+ }
59
+
60
+ const { pathname, search, hash } = data.payload as ShellUIUrlPayload;
61
+ // Remove leading slash and trailing slashes from iframe pathname
62
+ let cleanPathname = pathname.startsWith(navItem.url)
63
+ ? pathname.slice(navItem.url.length)
64
+ : pathname;
65
+ cleanPathname = cleanPathname.startsWith('/') ? cleanPathname.slice(1) : cleanPathname;
66
+ cleanPathname = cleanPathname.replace(/\/+$/, ''); // Remove trailing slashes
67
+ // Construct the new path without trailing slashes
68
+ let newShellPath = cleanPathname
69
+ ? `/${pathPrefix}/${cleanPathname}${search}${hash}`
70
+ : `/${pathPrefix}${search}${hash}`;
71
+
72
+ // Normalize: remove trailing slashes from pathname part only (preserve query/hash)
73
+ const urlParts = newShellPath.match(/^([^?#]*)([?#].*)?$/);
74
+ if (urlParts) {
75
+ const pathnamePart = urlParts[1].replace(/\/+$/, '') || '/';
76
+ const queryHashPart = urlParts[2] || '';
77
+ newShellPath = pathnamePart + queryHashPart;
78
+ }
79
+
80
+ // Normalize current path for comparison (remove trailing slashes from pathname)
81
+ const currentPathname = window.location.pathname.replace(/\/+$/, '') || '/';
82
+ const currentPath = currentPathname + window.location.search + window.location.hash;
83
+
84
+ // Normalize new path for comparison
85
+ const newPathParts = newShellPath.match(/^([^?#]*)([?#].*)?$/);
86
+ const normalizedNewPathname = newPathParts?.[1]?.replace(/\/+$/, '') || '/';
87
+ const normalizedNewPath = normalizedNewPathname + (newPathParts?.[2] || '');
88
+
89
+ if (currentPath !== normalizedNewPath) {
90
+ // Mark this navigation as internal so we don't try to "push" it back to the iframe
91
+ isInternalNavigation.current = true;
92
+ navigate(newShellPath, { replace: true });
93
+
94
+ // Reset the flag after a short delay to allow the render cycle to complete
95
+ setTimeout(() => {
96
+ isInternalNavigation.current = false;
97
+ }, 100);
98
+ }
99
+ },
100
+ );
101
+
102
+ return () => {
103
+ cleanup();
104
+ };
105
+ }, [pathPrefix, navigate]);
106
+
107
+ // Hide loading overlay when iframe sends SHELLUI_INITIALIZED
108
+ useEffect(() => {
109
+ const cleanup = shellui.addMessageListener(
110
+ 'SHELLUI_INITIALIZED',
111
+ (_data: ShellUIMessage, event: MessageEvent) => {
112
+ if (event.source === iframeRef.current?.contentWindow) {
113
+ setIsLoading(false);
114
+ }
115
+ },
116
+ );
117
+ return () => cleanup();
118
+ }, []);
119
+
120
+ // Fallback: hide overlay after 400ms if SHELLUI_INITIALIZED was not received
121
+ useEffect(() => {
122
+ if (!isLoading) return;
123
+ const timeoutId = setTimeout(() => {
124
+ logger.info('ContentView: Timeout expired, hiding loading overlay');
125
+ setIsLoading(false);
126
+ }, 400);
127
+ return () => clearTimeout(timeoutId);
128
+ }, [isLoading]);
129
+
130
+ // Handle external URL changes (e.g. from Sidebar)
131
+ useEffect(() => {
132
+ if (iframeRef.current && !isInternalNavigation.current) {
133
+ // Only update iframe src if it's actually different from its current src
134
+ // to avoid unnecessary reloads
135
+ if (iframeRef.current.src !== url) {
136
+ iframeRef.current.src = url;
137
+ setIsLoading(true);
138
+ }
139
+ }
140
+ }, [url]);
141
+
142
+ // Inject script to prevent "Layout was forced" warning by deferring layout until stylesheets load
143
+ useEffect(() => {
144
+ const iframe = iframeRef.current;
145
+ if (!iframe) return;
146
+
147
+ const handleLoad = () => {
148
+ try {
149
+ const iframeWindow = iframe.contentWindow;
150
+ const iframeDoc = iframe.contentDocument || iframeWindow?.document;
151
+ if (!iframeDoc || !iframeWindow) return;
152
+
153
+ // Inject a script that waits for stylesheets before allowing layout calculations
154
+ const script = iframeDoc.createElement('script');
155
+ script.textContent = `
156
+ (function() {
157
+ // Wait for all stylesheets to load
158
+ function waitForStylesheets() {
159
+ const styleSheets = Array.from(document.styleSheets);
160
+ const pendingSheets = styleSheets.filter(function(sheet) {
161
+ try {
162
+ return sheet.cssRules === null;
163
+ } catch (e) {
164
+ return false; // Cross-origin stylesheets, assume loaded
165
+ }
166
+ });
167
+
168
+ if (pendingSheets.length === 0) {
169
+ // All stylesheets loaded
170
+ return;
171
+ }
172
+
173
+ // Check again after a short delay
174
+ setTimeout(waitForStylesheets, 10);
175
+ }
176
+
177
+ // Start checking after DOM is ready
178
+ if (document.readyState === 'complete') {
179
+ waitForStylesheets();
180
+ } else {
181
+ window.addEventListener('load', waitForStylesheets);
182
+ }
183
+ })();
184
+ `;
185
+ iframeDoc.head.appendChild(script);
186
+ } catch (error) {
187
+ // Cross-origin or other errors - ignore (this is expected for some iframes)
188
+ logger.debug('Could not inject stylesheet wait script:', { error });
189
+ }
190
+ };
191
+
192
+ // Wait for iframe to load before injecting script
193
+ iframe.addEventListener('load', handleLoad);
194
+
195
+ // Also try immediately if already loaded
196
+ if (iframe.contentDocument?.readyState === 'complete') {
197
+ handleLoad();
198
+ }
199
+
200
+ return () => {
201
+ iframe.removeEventListener('load', handleLoad);
202
+ };
203
+ }, [initialUrl]);
204
+
205
+ // Suppress browser warnings that are expected and acceptable
206
+ useEffect(() => {
207
+ if (process.env.NODE_ENV === 'development') {
208
+ const originalWarn = console.warn;
209
+ console.warn = (...args: unknown[]) => {
210
+ const message = String(args[0] ?? '');
211
+ // Suppress the specific sandbox warning
212
+ if (
213
+ message.includes('allow-scripts') &&
214
+ message.includes('allow-same-origin') &&
215
+ message.includes('sandbox')
216
+ ) {
217
+ return;
218
+ }
219
+ // Suppress "Layout was forced" warning from iframe content
220
+ // This is a performance warning that occurs when iframe content calculates layout before stylesheets load
221
+ // It's harmless and common in iframe scenarios, especially with React apps
222
+ if (
223
+ message.includes('Layout was forced') &&
224
+ message.includes('before the page was fully loaded')
225
+ ) {
226
+ return;
227
+ }
228
+ originalWarn.apply(console, args);
229
+ };
230
+ return () => {
231
+ console.warn = originalWarn;
232
+ };
233
+ }
234
+ }, []);
235
+
236
+ return (
237
+ <div style={{ width: '100%', height: '100%', display: 'flex', position: 'relative' }}>
238
+ {/* Note: allow-same-origin is required for same-origin iframe content (e.g., Vite dev server, cookies, localStorage).
239
+ While this allows the iframe to remove its own sandboxing, it's acceptable here because the iframe content
240
+ is trusted microfrontend content from the same application origin.
241
+ Browser security warnings about this combination cannot be suppressed programmatically. */}
242
+ <iframe
243
+ ref={iframeRef}
244
+ src={initialUrl}
245
+ style={{
246
+ width: '100%',
247
+ height: '100%',
248
+ border: 'none',
249
+ display: 'block',
250
+ }}
251
+ title="Content Frame"
252
+ sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
253
+ referrerPolicy="no-referrer-when-downgrade"
254
+ />
255
+ {isLoading && <LoadingOverlay />}
256
+ </div>
257
+ );
258
+ };
@@ -0,0 +1,19 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import { useConfig } from '../features/config/useConfig';
3
+
4
+ export const HomeView = () => {
5
+ const { t } = useTranslation('common');
6
+ const { config } = useConfig();
7
+
8
+ return (
9
+ <div className="flex flex-col items-center justify-center h-full p-8 md:p-10">
10
+ <h1
11
+ className="m-0 text-3xl font-light text-foreground"
12
+ style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
13
+ >
14
+ {t('welcome', { title: config.title })}
15
+ </h1>
16
+ <p className="mt-4 text-lg text-muted-foreground">{t('getStarted')}</p>
17
+ </div>
18
+ );
19
+ };
@@ -0,0 +1,12 @@
1
+ export function LoadingOverlay() {
2
+ return (
3
+ <div className="absolute inset-0 z-10 flex flex-col bg-background">
4
+ <div className="h-1 w-full overflow-hidden bg-muted/30">
5
+ <div
6
+ className="h-full w-0 bg-muted-foreground/50"
7
+ style={{ animation: 'loading-bar-slide 400ms linear infinite' }}
8
+ />
9
+ </div>
10
+ </div>
11
+ );
12
+ }
@@ -0,0 +1,84 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import { shellui } from '@shellui/sdk';
3
+ import { useConfig } from '@/features/config/useConfig';
4
+ import type { NavigationItem, NavigationGroup } from '@/features/config/types';
5
+
6
+ const flattenNavigationItems = (
7
+ navigation: (NavigationItem | NavigationGroup)[],
8
+ ): NavigationItem[] => {
9
+ if (navigation.length === 0) return [];
10
+ return navigation.flatMap((item) => {
11
+ if ('title' in item && 'items' in item) {
12
+ return (item as NavigationGroup).items;
13
+ }
14
+ return item as NavigationItem;
15
+ });
16
+ };
17
+
18
+ export const NotFoundView = () => {
19
+ const { config } = useConfig();
20
+ const { i18n } = useTranslation();
21
+ const currentLanguage = i18n.language || 'en';
22
+
23
+ const resolveLocalizedString = (
24
+ value: string | { en: string; fr: string; [key: string]: string },
25
+ lang: string,
26
+ ): string => {
27
+ if (typeof value === 'string') return value;
28
+ return value[lang] || value.en || value.fr || Object.values(value)[0] || '';
29
+ };
30
+
31
+ const navItems =
32
+ config?.navigation && config.navigation.length > 0
33
+ ? flattenNavigationItems(config.navigation)
34
+ .filter((item) => !item.hidden)
35
+ .filter((item, index, self) => index === self.findIndex((i) => i.path === item.path))
36
+ : [];
37
+
38
+ const handleNavigate = (path: string) => {
39
+ shellui.navigate(path.startsWith('/') ? path : `/${path}`);
40
+ };
41
+
42
+ return (
43
+ <div className="flex flex-col min-h-full">
44
+ <div className="flex-1 flex flex-col items-center justify-center px-6 py-12 text-muted-foreground">
45
+ <span className="text-6xl font-light tracking-tighter text-foreground/80 select-none">
46
+ 404
47
+ </span>
48
+ <p className="mt-3 text-lg text-muted-foreground">Page not found</p>
49
+ </div>
50
+
51
+ {navItems.length > 0 && (
52
+ <footer className="border-t border-border py-4 px-6 mt-auto bg-muted/30">
53
+ <nav
54
+ className="flex flex-row flex-wrap justify-center items-center gap-x-2 gap-y-1 text-sm text-muted-foreground"
55
+ aria-label="Available pages"
56
+ >
57
+ {navItems.map((item, index) => (
58
+ <span
59
+ key={item.path}
60
+ className="inline-flex items-center gap-x-2"
61
+ >
62
+ {index > 0 && (
63
+ <span
64
+ className="text-border select-none"
65
+ aria-hidden
66
+ >
67
+ ·
68
+ </span>
69
+ )}
70
+ <button
71
+ type="button"
72
+ onClick={() => handleNavigate(`/${item.path}`)}
73
+ className="text-muted-foreground hover:text-foreground hover:underline underline-offset-2 cursor-pointer bg-transparent border-0 p-0 font-normal"
74
+ >
75
+ {resolveLocalizedString(item.label, currentLanguage)}
76
+ </button>
77
+ </span>
78
+ ))}
79
+ </nav>
80
+ </footer>
81
+ )}
82
+ </div>
83
+ );
84
+ };
@@ -0,0 +1,95 @@
1
+ import { useRouteError, isRouteErrorResponse } from 'react-router';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { shellui } from '@shellui/sdk';
4
+ import { Button } from '@/components/ui/button';
5
+
6
+ function isChunkLoadError(error: unknown): boolean {
7
+ if (error instanceof Error) {
8
+ const msg = error.message.toLowerCase();
9
+ return (
10
+ msg.includes('loading dynamically imported module') ||
11
+ msg.includes('chunk') ||
12
+ msg.includes('failed to fetch dynamically imported module')
13
+ );
14
+ }
15
+ return false;
16
+ }
17
+
18
+ function getErrorMessage(error: unknown): string {
19
+ if (isRouteErrorResponse(error)) {
20
+ return error.data?.message ?? error.statusText ?? 'Something went wrong';
21
+ }
22
+ if (error instanceof Error) {
23
+ return error.message;
24
+ }
25
+ return String(error);
26
+ }
27
+
28
+ function getErrorStack(error: unknown): string | null {
29
+ if (error instanceof Error && error.stack) {
30
+ return error.stack;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function getErrorDetailsText(error: unknown): string {
36
+ const message = getErrorMessage(error);
37
+ const stack = getErrorStack(error);
38
+ if (stack) {
39
+ return `Message:\n${message}\n\nStack:\n${stack}`;
40
+ }
41
+ return message;
42
+ }
43
+
44
+ export function RouteErrorBoundary() {
45
+ const error = useRouteError();
46
+ const { t } = useTranslation('common');
47
+ const isChunkError = isChunkLoadError(error);
48
+ const detailsText = getErrorDetailsText(error);
49
+
50
+ return (
51
+ <div
52
+ className="flex min-h-screen flex-col items-center justify-center bg-background px-4 py-12"
53
+ style={{ fontFamily: 'var(--heading-font-family, system-ui, sans-serif)' }}
54
+ >
55
+ <div className="w-full max-w-md space-y-6 text-center">
56
+ <div className="space-y-2">
57
+ <h1 className="text-xl font-semibold text-foreground">
58
+ {isChunkError ? t('errorBoundary.titleChunk') : t('errorBoundary.titleGeneric')}
59
+ </h1>
60
+ <p className="text-sm text-muted-foreground">
61
+ {isChunkError
62
+ ? t('errorBoundary.descriptionChunk')
63
+ : t('errorBoundary.descriptionGeneric')}
64
+ </p>
65
+ </div>
66
+
67
+ <div className="flex flex-col gap-3 sm:flex-row sm:justify-center">
68
+ <Button
69
+ variant="default"
70
+ onClick={() => window.location.reload()}
71
+ className="shrink-0"
72
+ >
73
+ {t('errorBoundary.tryAgain')}
74
+ </Button>
75
+ <Button
76
+ variant="outline"
77
+ onClick={() => shellui.navigate('/')}
78
+ className="shrink-0"
79
+ >
80
+ {t('errorBoundary.goToHome')}
81
+ </Button>
82
+ </div>
83
+
84
+ <details className="rounded-lg border border-border bg-muted/30 text-left">
85
+ <summary className="cursor-pointer px-4 py-3 text-xs font-medium text-muted-foreground hover:text-foreground">
86
+ {t('errorBoundary.errorDetails')}
87
+ </summary>
88
+ <pre className="max-h-64 overflow-auto whitespace-pre-wrap break-all px-4 pb-3 pt-1 text-xs text-muted-foreground font-mono">
89
+ {detailsText}
90
+ </pre>
91
+ </details>
92
+ </div>
93
+ </div>
94
+ );
95
+ }