@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,99 @@
1
+ import type { NavigationItem, NavigationGroup, LocalizedString } from '../config/types';
2
+
3
+ /** Resolve a localized string to a single string for the given language. */
4
+ export function resolveLocalizedString(value: LocalizedString | undefined, lang: string): string {
5
+ if (value === null || value === undefined) return '';
6
+ if (typeof value === 'string') return value;
7
+ return value[lang] || value.en || value.fr || Object.values(value)[0] || '';
8
+ }
9
+
10
+ /** Flatten navigation items from groups or flat array. */
11
+ export function flattenNavigationItems(
12
+ navigation: (NavigationItem | NavigationGroup)[],
13
+ ): NavigationItem[] {
14
+ if (navigation.length === 0) {
15
+ return [];
16
+ }
17
+ return navigation.flatMap((item) => {
18
+ if ('title' in item && 'items' in item) {
19
+ return (item as NavigationGroup).items;
20
+ }
21
+ return item as NavigationItem;
22
+ });
23
+ }
24
+
25
+ export type Viewport = 'mobile' | 'desktop';
26
+
27
+ /** Filter navigation by viewport: remove hidden and viewport-specific hidden items (and empty groups). */
28
+ export function filterNavigationByViewport(
29
+ navigation: (NavigationItem | NavigationGroup)[],
30
+ viewport: Viewport,
31
+ ): (NavigationItem | NavigationGroup)[] {
32
+ if (navigation.length === 0) return [];
33
+ const hideOnMobile = viewport === 'mobile';
34
+ const hideOnDesktop = viewport === 'desktop';
35
+ return navigation
36
+ .map((item) => {
37
+ if ('title' in item && 'items' in item) {
38
+ const group = item as NavigationGroup;
39
+ const visibleItems = group.items.filter((navItem) => {
40
+ if (navItem.hidden) return false;
41
+ if (hideOnMobile && navItem.hiddenOnMobile) return false;
42
+ if (hideOnDesktop && navItem.hiddenOnDesktop) return false;
43
+ return true;
44
+ });
45
+ if (visibleItems.length === 0) return null;
46
+ return { ...group, items: visibleItems };
47
+ }
48
+ const navItem = item as NavigationItem;
49
+ if (navItem.hidden) return null;
50
+ if (hideOnMobile && navItem.hiddenOnMobile) return null;
51
+ if (hideOnDesktop && navItem.hiddenOnDesktop) return null;
52
+ return item;
53
+ })
54
+ .filter((item): item is NavigationItem | NavigationGroup => item !== null);
55
+ }
56
+
57
+ /** Filter navigation for sidebar: remove hidden items and groups that become empty. */
58
+ export function filterNavigationForSidebar(
59
+ navigation: (NavigationItem | NavigationGroup)[],
60
+ ): (NavigationItem | NavigationGroup)[] {
61
+ if (navigation.length === 0) return [];
62
+ return navigation
63
+ .map((item) => {
64
+ if ('title' in item && 'items' in item) {
65
+ const group = item as NavigationGroup;
66
+ const visibleItems = group.items.filter((navItem) => !navItem.hidden);
67
+ if (visibleItems.length === 0) return null;
68
+ return { ...group, items: visibleItems };
69
+ }
70
+ const navItem = item as NavigationItem;
71
+ if (navItem.hidden) return null;
72
+ return item;
73
+ })
74
+ .filter((item): item is NavigationItem | NavigationGroup => item !== null);
75
+ }
76
+
77
+ /** Split navigation by position: start (main content) and end (footer). */
78
+ export function splitNavigationByPosition(navigation: (NavigationItem | NavigationGroup)[]): {
79
+ start: (NavigationItem | NavigationGroup)[];
80
+ end: NavigationItem[];
81
+ } {
82
+ const start: (NavigationItem | NavigationGroup)[] = [];
83
+ const end: NavigationItem[] = [];
84
+ for (const item of navigation) {
85
+ const position = 'position' in item ? (item.position ?? 'start') : 'start';
86
+ if (position === 'end') {
87
+ if ('title' in item && 'items' in item) {
88
+ const group = item as NavigationGroup;
89
+ end.push(...group.items.filter((navItem) => !navItem.hidden));
90
+ } else {
91
+ const navItem = item as NavigationItem;
92
+ if (!navItem.hidden) end.push(navItem);
93
+ }
94
+ } else {
95
+ start.push(item);
96
+ }
97
+ }
98
+ return { start, end };
99
+ }
@@ -0,0 +1,112 @@
1
+ import { shellui, type ShellUIMessage } from '@shellui/sdk';
2
+ import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react';
3
+
4
+ /**
5
+ * Validates and normalizes a URL to ensure it's from the same domain or localhost
6
+ * @param url - The URL or path to validate
7
+ * @returns The normalized absolute URL or null if invalid
8
+ */
9
+ export const validateAndNormalizeUrl = (url: string | undefined | null): string | null => {
10
+ if (!url || typeof url !== 'string') {
11
+ return null;
12
+ }
13
+
14
+ try {
15
+ // If it's already an absolute URL, check if it's same origin or localhost
16
+ if (url.startsWith('http://') || url.startsWith('https://')) {
17
+ const urlObj = new URL(url);
18
+ const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
19
+
20
+ // Allow same origin
21
+ if (urlObj.origin === currentOrigin) {
22
+ return url;
23
+ }
24
+
25
+ // Allow localhost URLs (for development)
26
+ if (urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1') {
27
+ return url;
28
+ }
29
+
30
+ return null; // Different origin, reject for security
31
+ }
32
+
33
+ // If it's a relative URL, make it absolute using current origin
34
+ if (url.startsWith('/') || url.startsWith('./') || !url.startsWith('//')) {
35
+ const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
36
+ // Ensure relative paths start with /
37
+ const normalizedPath = url.startsWith('/') ? url : `/${url}`;
38
+ return `${currentOrigin}${normalizedPath}`;
39
+ }
40
+
41
+ // Reject protocol-relative URLs (//example.com) for security
42
+ return null;
43
+ } catch (error) {
44
+ // eslint-disable-next-line no-console
45
+ console.error('Invalid URL:', url, error);
46
+ return null;
47
+ }
48
+ };
49
+
50
+ interface ModalContextValue {
51
+ isOpen: boolean;
52
+ modalUrl: string | null;
53
+ openModal: (url?: string) => void;
54
+ closeModal: () => void;
55
+ }
56
+
57
+ const ModalContext = createContext<ModalContextValue | undefined>(undefined);
58
+
59
+ export const useModal = () => {
60
+ const context = useContext(ModalContext);
61
+ if (!context) {
62
+ throw new Error('useModal must be used within a ModalProvider');
63
+ }
64
+ return context;
65
+ };
66
+
67
+ interface ModalProviderProps {
68
+ children: ReactNode;
69
+ }
70
+
71
+ export const ModalProvider = ({ children }: ModalProviderProps) => {
72
+ const [isOpen, setIsOpen] = useState(false);
73
+ const [modalUrl, setModalUrl] = useState<string | null>(null);
74
+
75
+ const openModal = useCallback((url?: string) => {
76
+ const validatedUrl = url ? validateAndNormalizeUrl(url) : null;
77
+ setModalUrl(validatedUrl);
78
+ setIsOpen(true);
79
+ }, []);
80
+
81
+ const closeModal = useCallback(() => {
82
+ setIsOpen(false);
83
+ // Clear URL after a short delay to allow animation to complete
84
+ setTimeout(() => setModalUrl(null), 200);
85
+ }, []);
86
+
87
+ // Listen for postMessage events from nested iframes
88
+ useEffect(() => {
89
+ const cleanupOpenModal = shellui.addMessageListener(
90
+ 'SHELLUI_OPEN_MODAL',
91
+ (data: ShellUIMessage) => {
92
+ const payload = data.payload as { url?: string };
93
+ openModal(payload.url);
94
+ },
95
+ );
96
+
97
+ const cleanupCloseModal = shellui.addMessageListener('SHELLUI_CLOSE_MODAL', () => {
98
+ closeModal();
99
+ });
100
+
101
+ return () => {
102
+ cleanupOpenModal();
103
+ cleanupCloseModal();
104
+ };
105
+ }, [openModal, closeModal]);
106
+
107
+ return (
108
+ <ModalContext.Provider value={{ isOpen, modalUrl, openModal, closeModal }}>
109
+ {children}
110
+ </ModalContext.Provider>
111
+ );
112
+ };
@@ -0,0 +1,72 @@
1
+ import { getCookieConsentAccepted } from '../cookieConsent/cookieConsent';
2
+
3
+ const SETTINGS_KEY = 'shellui:settings';
4
+
5
+ /** True after @sentry/react has been lazy-loaded and init() was called. */
6
+ let sentryLoaded = false;
7
+
8
+ function isErrorReportingEnabled(): boolean {
9
+ if (typeof window === 'undefined') return true;
10
+ // If cookie consent is configured with a cookie for sentry.io, only enable when user accepted it
11
+ if (!getCookieConsentAccepted('sentry.io')) return false;
12
+ try {
13
+ const stored = localStorage.getItem(SETTINGS_KEY);
14
+ if (!stored) return true;
15
+ const parsed = JSON.parse(stored) as { errorReporting?: { enabled?: boolean } };
16
+ return parsed?.errorReporting?.enabled !== false;
17
+ } catch {
18
+ return true;
19
+ }
20
+ }
21
+
22
+ type SentryGlobals = {
23
+ __SHELLUI_SENTRY_DSN__?: string;
24
+ __SHELLUI_SENTRY_ENVIRONMENT__?: string;
25
+ __SHELLUI_SENTRY_RELEASE__?: string;
26
+ };
27
+
28
+ /**
29
+ * Initialize error reporting only in production when configured and user has not disabled it.
30
+ * Lazy-loads @sentry/react only when needed so the bundle is not loaded when Sentry is unused.
31
+ * Reads DSN, environment, and release from __SHELLUI_SENTRY_DSN__, __SHELLUI_SENTRY_ENVIRONMENT__,
32
+ * and __SHELLUI_SENTRY_RELEASE__ (injected at build time) and user preference from settings.
33
+ * Exported so the settings UI can re-initialize when the user re-enables reporting.
34
+ */
35
+ export function initSentry(): void {
36
+ const isDev = (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV;
37
+ if (isDev) {
38
+ return;
39
+ }
40
+ if (!isErrorReportingEnabled()) {
41
+ return;
42
+ }
43
+ const g = globalThis as unknown as SentryGlobals;
44
+ const dsn = g.__SHELLUI_SENTRY_DSN__;
45
+ if (!dsn || typeof dsn !== 'string') {
46
+ return;
47
+ }
48
+ void import('@sentry/react').then((Sentry) => {
49
+ Sentry.init({
50
+ dsn,
51
+ environment: g.__SHELLUI_SENTRY_ENVIRONMENT__ ?? 'production',
52
+ release: g.__SHELLUI_SENTRY_RELEASE__,
53
+ });
54
+ sentryLoaded = true;
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Close Sentry when the user disables error reporting in settings.
60
+ * Only loads @sentry/react if it was already initialized (avoids loading the chunk just to close).
61
+ */
62
+ export function closeSentry(): void {
63
+ if (!sentryLoaded) {
64
+ return;
65
+ }
66
+ void import('@sentry/react').then((Sentry) => {
67
+ Sentry.close();
68
+ sentryLoaded = false;
69
+ });
70
+ }
71
+
72
+ initSentry();
@@ -0,0 +1,19 @@
1
+ import { useContext, createContext } from 'react';
2
+ import type { Settings } from '@shellui/sdk';
3
+
4
+ export interface SettingsContextValue {
5
+ settings: Settings;
6
+ updateSettings: (updates: Partial<Settings>) => void;
7
+ updateSetting: <K extends keyof Settings>(key: K, updates: Partial<Settings[K]>) => void;
8
+ resetAllData: () => void;
9
+ }
10
+
11
+ export const SettingsContext = createContext<SettingsContextValue | undefined>(undefined);
12
+
13
+ export function useSettings() {
14
+ const context = useContext(SettingsContext);
15
+ if (context === undefined) {
16
+ throw new Error('useSettings must be used within a SettingsProvider');
17
+ }
18
+ return context;
19
+ }