@shellui/core 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/ContentView-CZG-ro_B.js +146 -0
- package/dist/ContentView-CZG-ro_B.js.map +1 -0
- package/dist/CookiePreferencesView-MhO9FO-4.js +213 -0
- package/dist/CookiePreferencesView-MhO9FO-4.js.map +1 -0
- package/dist/DefaultLayout-Dbb3uJED.js +394 -0
- package/dist/DefaultLayout-Dbb3uJED.js.map +1 -0
- package/dist/FullscreenLayout-1SgPHWw-.js +30 -0
- package/dist/FullscreenLayout-1SgPHWw-.js.map +1 -0
- package/dist/HomeView-DYU-O_Il.js +21 -0
- package/dist/HomeView-DYU-O_Il.js.map +1 -0
- package/dist/NotFoundView-CeYjJNg0.js +52 -0
- package/dist/NotFoundView-CeYjJNg0.js.map +1 -0
- package/dist/OverlayShell-pzbqQW25.js +642 -0
- package/dist/OverlayShell-pzbqQW25.js.map +1 -0
- package/dist/SettingsView-Bndrta44.js +2207 -0
- package/dist/SettingsView-Bndrta44.js.map +1 -0
- package/dist/ViewRoute-ChSPabOy.js +32 -0
- package/dist/ViewRoute-ChSPabOy.js.map +1 -0
- package/dist/WindowsLayout-CXGNPKoY.js +633 -0
- package/dist/WindowsLayout-CXGNPKoY.js.map +1 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/components/ContentView.d.ts +10 -0
- package/dist/components/ContentView.d.ts.map +1 -0
- package/dist/components/HomeView.d.ts +2 -0
- package/dist/components/HomeView.d.ts.map +1 -0
- package/dist/components/LoadingOverlay.d.ts +2 -0
- package/dist/components/LoadingOverlay.d.ts.map +1 -0
- package/dist/components/NotFoundView.d.ts +2 -0
- package/dist/components/NotFoundView.d.ts.map +1 -0
- package/dist/components/RouteErrorBoundary.d.ts +2 -0
- package/dist/components/RouteErrorBoundary.d.ts.map +1 -0
- package/dist/components/ViewRoute.d.ts +7 -0
- package/dist/components/ViewRoute.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +32 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +20 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/button-group.d.ts +7 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button.d.ts +12 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/dialog.d.ts +24 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/drawer.d.ts +38 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/select.d.ts +5 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/sidebar.d.ts +46 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sonner.d.ts +6 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/switch.d.ts +8 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/constants/urls.d.ts +6 -0
- package/dist/constants/urls.d.ts.map +1 -0
- package/dist/constants/urls.js +8 -0
- package/dist/constants/urls.js.map +1 -0
- package/dist/features/alertDialog/DialogContext.d.ts +12 -0
- package/dist/features/alertDialog/DialogContext.d.ts.map +1 -0
- package/dist/features/config/ConfigProvider.d.ts +15 -0
- package/dist/features/config/ConfigProvider.d.ts.map +1 -0
- package/dist/features/config/types.d.ts +177 -0
- package/dist/features/config/types.d.ts.map +1 -0
- package/dist/features/config/useConfig.d.ts +8 -0
- package/dist/features/config/useConfig.d.ts.map +1 -0
- package/dist/features/cookieConsent/CookieConsentModal.d.ts +6 -0
- package/dist/features/cookieConsent/CookieConsentModal.d.ts.map +1 -0
- package/dist/features/cookieConsent/CookiePreferencesView.d.ts +2 -0
- package/dist/features/cookieConsent/CookiePreferencesView.d.ts.map +1 -0
- package/dist/features/cookieConsent/cookieConsent.d.ts +22 -0
- package/dist/features/cookieConsent/cookieConsent.d.ts.map +1 -0
- package/dist/features/cookieConsent/useCookieConsent.d.ts +15 -0
- package/dist/features/cookieConsent/useCookieConsent.d.ts.map +1 -0
- package/dist/features/drawer/DrawerContext.d.ts +24 -0
- package/dist/features/drawer/DrawerContext.d.ts.map +1 -0
- package/dist/features/layouts/AppLayout.d.ts +12 -0
- package/dist/features/layouts/AppLayout.d.ts.map +1 -0
- package/dist/features/layouts/DefaultLayout.d.ts +10 -0
- package/dist/features/layouts/DefaultLayout.d.ts.map +1 -0
- package/dist/features/layouts/FullscreenLayout.d.ts +9 -0
- package/dist/features/layouts/FullscreenLayout.d.ts.map +1 -0
- package/dist/features/layouts/LayoutProviders.d.ts +9 -0
- package/dist/features/layouts/LayoutProviders.d.ts.map +1 -0
- package/dist/features/layouts/OverlayShell.d.ts +10 -0
- package/dist/features/layouts/OverlayShell.d.ts.map +1 -0
- package/dist/features/layouts/WindowsLayout.d.ts +24 -0
- package/dist/features/layouts/WindowsLayout.d.ts.map +1 -0
- package/dist/features/layouts/utils.d.ts +16 -0
- package/dist/features/layouts/utils.d.ts.map +1 -0
- package/dist/features/modal/ModalContext.d.ts +20 -0
- package/dist/features/modal/ModalContext.d.ts.map +1 -0
- package/dist/features/sentry/initSentry.d.ts +14 -0
- package/dist/features/sentry/initSentry.d.ts.map +1 -0
- package/dist/features/settings/SettingsContext.d.ts +10 -0
- package/dist/features/settings/SettingsContext.d.ts.map +1 -0
- package/dist/features/settings/SettingsIcons.d.ts +22 -0
- package/dist/features/settings/SettingsIcons.d.ts.map +1 -0
- package/dist/features/settings/SettingsProvider.d.ts +5 -0
- package/dist/features/settings/SettingsProvider.d.ts.map +1 -0
- package/dist/features/settings/SettingsRoutes.d.ts +7 -0
- package/dist/features/settings/SettingsRoutes.d.ts.map +1 -0
- package/dist/features/settings/SettingsView.d.ts +2 -0
- package/dist/features/settings/SettingsView.d.ts.map +1 -0
- package/dist/features/settings/components/Advanced.d.ts +2 -0
- package/dist/features/settings/components/Advanced.d.ts.map +1 -0
- package/dist/features/settings/components/Appearance.d.ts +2 -0
- package/dist/features/settings/components/Appearance.d.ts.map +1 -0
- package/dist/features/settings/components/DataPrivacy.d.ts +2 -0
- package/dist/features/settings/components/DataPrivacy.d.ts.map +1 -0
- package/dist/features/settings/components/Develop.d.ts +2 -0
- package/dist/features/settings/components/Develop.d.ts.map +1 -0
- package/dist/features/settings/components/LanguageAndRegion.d.ts +2 -0
- package/dist/features/settings/components/LanguageAndRegion.d.ts.map +1 -0
- package/dist/features/settings/components/ServiceWorker.d.ts +2 -0
- package/dist/features/settings/components/ServiceWorker.d.ts.map +1 -0
- package/dist/features/settings/components/UpdateApp.d.ts +2 -0
- package/dist/features/settings/components/UpdateApp.d.ts.map +1 -0
- package/dist/features/settings/components/develop/DialogTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/DialogTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/DrawerTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/DrawerTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/ModalTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/ModalTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/ToastTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/ToastTestButtons.d.ts.map +1 -0
- package/dist/features/settings/hooks/useSettings.d.ts +2 -0
- package/dist/features/settings/hooks/useSettings.d.ts.map +1 -0
- package/dist/features/sonner/SonnerContext.d.ts +29 -0
- package/dist/features/sonner/SonnerContext.d.ts.map +1 -0
- package/dist/features/theme/ThemeProvider.d.ts +11 -0
- package/dist/features/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/features/theme/themes.d.ts +114 -0
- package/dist/features/theme/themes.d.ts.map +1 -0
- package/dist/features/theme/useTheme.d.ts +10 -0
- package/dist/features/theme/useTheme.d.ts.map +1 -0
- package/dist/i18n/I18nProvider.d.ts +9 -0
- package/dist/i18n/I18nProvider.d.ts.map +1 -0
- package/dist/i18n/config.d.ts +23 -0
- package/dist/i18n/config.d.ts.map +1 -0
- package/dist/i18n/translations/en/common.json.d.ts +19 -0
- package/dist/i18n/translations/en/cookieConsent.json.d.ts +53 -0
- package/dist/i18n/translations/en/settings.json.d.ts +358 -0
- package/dist/i18n/translations/fr/common.json.d.ts +19 -0
- package/dist/i18n/translations/fr/cookieConsent.json.d.ts +53 -0
- package/dist/i18n/translations/fr/settings.json.d.ts +358 -0
- package/dist/index-lmRk5L6z.js +2160 -0
- package/dist/index-lmRk5L6z.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/z-index.d.ts +29 -0
- package/dist/lib/z-index.d.ts.map +1 -0
- package/dist/router/router.d.ts +3 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/routes.d.ts +4 -0
- package/dist/router/routes.d.ts.map +1 -0
- package/dist/sidebar-ClIeZ2zb.js +303 -0
- package/dist/sidebar-ClIeZ2zb.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/switch-8SzUJz7Q.js +44 -0
- package/dist/switch-8SzUJz7Q.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +93 -0
- package/postcss.config.js +6 -0
- package/src/app.tsx +119 -0
- package/src/components/ContentView.tsx +258 -0
- package/src/components/HomeView.tsx +19 -0
- package/src/components/LoadingOverlay.tsx +12 -0
- package/src/components/NotFoundView.tsx +84 -0
- package/src/components/RouteErrorBoundary.tsx +95 -0
- package/src/components/ViewRoute.tsx +47 -0
- package/src/components/ui/alert-dialog.tsx +181 -0
- package/src/components/ui/breadcrumb.tsx +155 -0
- package/src/components/ui/button-group.tsx +52 -0
- package/src/components/ui/button.tsx +51 -0
- package/src/components/ui/dialog.tsx +160 -0
- package/src/components/ui/drawer.tsx +200 -0
- package/src/components/ui/select.tsx +24 -0
- package/src/components/ui/sidebar.tsx +406 -0
- package/src/components/ui/sonner.tsx +36 -0
- package/src/components/ui/switch.tsx +45 -0
- package/src/constants/urls.ts +4 -0
- package/src/features/alertDialog/DialogContext.tsx +468 -0
- package/src/features/config/ConfigProvider.ts +96 -0
- package/src/features/config/types.ts +195 -0
- package/src/features/config/useConfig.ts +15 -0
- package/src/features/cookieConsent/CookieConsentModal.tsx +122 -0
- package/src/features/cookieConsent/CookiePreferencesView.tsx +328 -0
- package/src/features/cookieConsent/cookieConsent.ts +84 -0
- package/src/features/cookieConsent/useCookieConsent.ts +39 -0
- package/src/features/drawer/DrawerContext.tsx +116 -0
- package/src/features/layouts/AppLayout.tsx +63 -0
- package/src/features/layouts/DefaultLayout.tsx +625 -0
- package/src/features/layouts/FullscreenLayout.tsx +55 -0
- package/src/features/layouts/LayoutProviders.tsx +20 -0
- package/src/features/layouts/OverlayShell.tsx +171 -0
- package/src/features/layouts/WindowsLayout.tsx +860 -0
- package/src/features/layouts/utils.ts +99 -0
- package/src/features/modal/ModalContext.tsx +112 -0
- package/src/features/sentry/initSentry.ts +72 -0
- package/src/features/settings/SettingsContext.tsx +19 -0
- package/src/features/settings/SettingsIcons.tsx +452 -0
- package/src/features/settings/SettingsProvider.tsx +341 -0
- package/src/features/settings/SettingsRoutes.tsx +66 -0
- package/src/features/settings/SettingsView.tsx +327 -0
- package/src/features/settings/components/Advanced.tsx +128 -0
- package/src/features/settings/components/Appearance.tsx +306 -0
- package/src/features/settings/components/DataPrivacy.tsx +142 -0
- package/src/features/settings/components/Develop.tsx +174 -0
- package/src/features/settings/components/LanguageAndRegion.tsx +329 -0
- package/src/features/settings/components/ServiceWorker.tsx +363 -0
- package/src/features/settings/components/UpdateApp.tsx +206 -0
- package/src/features/settings/components/develop/DialogTestButtons.tsx +137 -0
- package/src/features/settings/components/develop/DrawerTestButtons.tsx +67 -0
- package/src/features/settings/components/develop/ModalTestButtons.tsx +30 -0
- package/src/features/settings/components/develop/ToastTestButtons.tsx +179 -0
- package/src/features/settings/hooks/useSettings.tsx +10 -0
- package/src/features/sonner/SonnerContext.tsx +286 -0
- package/src/features/theme/ThemeProvider.tsx +16 -0
- package/src/features/theme/themes.ts +561 -0
- package/src/features/theme/useTheme.tsx +71 -0
- package/src/i18n/I18nProvider.tsx +32 -0
- package/src/i18n/config.ts +107 -0
- package/src/i18n/translations/en/common.json +16 -0
- package/src/i18n/translations/en/cookieConsent.json +50 -0
- package/src/i18n/translations/en/settings.json +355 -0
- package/src/i18n/translations/fr/common.json +16 -0
- package/src/i18n/translations/fr/cookieConsent.json +50 -0
- package/src/i18n/translations/fr/settings.json +355 -0
- package/src/index.css +412 -0
- package/src/index.html +100 -0
- package/src/index.ts +31 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/z-index.ts +29 -0
- package/src/main.tsx +26 -0
- package/src/router/router.tsx +8 -0
- package/src/router/routes.tsx +115 -0
- package/src/service-worker/register.ts +1199 -0
- package/src/service-worker/sw-dev.ts +87 -0
- package/src/service-worker/sw.ts +105 -0
- package/tailwind.config.js +60 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { forwardRef, type InputHTMLAttributes, type ChangeEvent } from 'react';
|
|
2
|
+
import { cn } from '@/lib/utils';
|
|
3
|
+
|
|
4
|
+
export interface SwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
5
|
+
checked?: boolean;
|
|
6
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
10
|
+
({ className, checked, onCheckedChange, ...props }, ref) => {
|
|
11
|
+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
12
|
+
onCheckedChange?.(e.target.checked);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<label className="inline-flex items-center cursor-pointer">
|
|
17
|
+
<input
|
|
18
|
+
type="checkbox"
|
|
19
|
+
ref={ref}
|
|
20
|
+
checked={checked}
|
|
21
|
+
onChange={handleChange}
|
|
22
|
+
className="sr-only"
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
<div
|
|
26
|
+
className={cn(
|
|
27
|
+
'relative w-11 h-6 rounded-full border border-border transition-colors focus-within:outline-none focus-within:ring-2 focus-within:ring-ring',
|
|
28
|
+
checked ? 'bg-primary' : 'bg-muted',
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
className={cn(
|
|
34
|
+
'absolute top-[1px] left-[1px] h-5 w-5 bg-background border border-border rounded-full transition-transform',
|
|
35
|
+
checked ? 'translate-x-5' : 'translate-x-0',
|
|
36
|
+
)}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
</label>
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
Switch.displayName = 'Switch';
|
|
44
|
+
|
|
45
|
+
export { Switch };
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { shellui, type ShellUIMessage, type DialogOptions } from '@shellui/sdk';
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { Button } from '@/components/ui/button';
|
|
12
|
+
import { Z_INDEX } from '@/lib/z-index';
|
|
13
|
+
|
|
14
|
+
/** Match exit animation duration in index.css (overlay + content ~0.1s + buffer) */
|
|
15
|
+
const DIALOG_EXIT_ANIMATION_MS = 200;
|
|
16
|
+
import {
|
|
17
|
+
AlertDialog,
|
|
18
|
+
AlertDialogAction,
|
|
19
|
+
AlertDialogCancel,
|
|
20
|
+
AlertDialogContent,
|
|
21
|
+
AlertDialogDescription,
|
|
22
|
+
AlertDialogFooter,
|
|
23
|
+
AlertDialogHeader,
|
|
24
|
+
AlertDialogMedia,
|
|
25
|
+
AlertDialogTitle,
|
|
26
|
+
AlertDialogOverlay,
|
|
27
|
+
AlertDialogPortal,
|
|
28
|
+
} from '@/components/ui/alert-dialog';
|
|
29
|
+
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
30
|
+
|
|
31
|
+
/** Trash icon (matches lucide trash-2) */
|
|
32
|
+
const TrashIcon = () => (
|
|
33
|
+
<svg
|
|
34
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
35
|
+
width="20"
|
|
36
|
+
height="20"
|
|
37
|
+
viewBox="0 0 24 24"
|
|
38
|
+
fill="none"
|
|
39
|
+
stroke="currentColor"
|
|
40
|
+
strokeWidth="2"
|
|
41
|
+
strokeLinecap="round"
|
|
42
|
+
strokeLinejoin="round"
|
|
43
|
+
aria-hidden
|
|
44
|
+
>
|
|
45
|
+
<path d="M3 6h18" />
|
|
46
|
+
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
|
47
|
+
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
|
48
|
+
<line
|
|
49
|
+
x1="10"
|
|
50
|
+
x2="10"
|
|
51
|
+
y1="11"
|
|
52
|
+
y2="17"
|
|
53
|
+
/>
|
|
54
|
+
<line
|
|
55
|
+
x1="14"
|
|
56
|
+
x2="14"
|
|
57
|
+
y1="11"
|
|
58
|
+
y2="17"
|
|
59
|
+
/>
|
|
60
|
+
</svg>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
/** Cookie icon for cookie consent */
|
|
64
|
+
const CookieIcon = ({ className }: { className?: string }) => (
|
|
65
|
+
<svg
|
|
66
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
67
|
+
viewBox="0 0 256 256"
|
|
68
|
+
fill="currentColor"
|
|
69
|
+
className={className}
|
|
70
|
+
>
|
|
71
|
+
<path d="M164.49 163.51a12 12 0 1 1-17 0a12 12 0 0 1 17 0m-81-8a12 12 0 1 0 17 0a12 12 0 0 0-16.98 0Zm9-39a12 12 0 1 0-17 0a12 12 0 0 0 17-.02Zm48-1a12 12 0 1 0 0 17a12 12 0 0 0 0-17M232 128A104 104 0 1 1 128 24a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8m-16.31 7.39A56.13 56.13 0 0 1 168.5 87.5a56.13 56.13 0 0 1-47.89-47.19a88 88 0 1 0 95.08 95.08" />
|
|
72
|
+
</svg>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
interface DialogContextValue {
|
|
76
|
+
dialog: (options: DialogOptions) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const DialogContext = createContext<DialogContextValue | undefined>(undefined);
|
|
80
|
+
|
|
81
|
+
export function useDialog() {
|
|
82
|
+
const context = useContext(DialogContext);
|
|
83
|
+
if (!context) {
|
|
84
|
+
throw new Error('useDialog must be used within a DialogProvider');
|
|
85
|
+
}
|
|
86
|
+
return context;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface DialogProviderProps {
|
|
90
|
+
children: ReactNode;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface DialogState extends Omit<DialogOptions, 'onOk' | 'onCancel' | 'icon'> {
|
|
94
|
+
id: string;
|
|
95
|
+
mode: DialogOptions['mode'];
|
|
96
|
+
from?: string[];
|
|
97
|
+
iconType?: 'cookie';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const DialogProvider = ({ children }: DialogProviderProps) => {
|
|
101
|
+
const [dialogState, setDialogState] = useState<DialogState | null>(null);
|
|
102
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
103
|
+
const unmountTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
104
|
+
|
|
105
|
+
const scheduleUnmount = useCallback(() => {
|
|
106
|
+
if (unmountTimeoutRef.current) clearTimeout(unmountTimeoutRef.current);
|
|
107
|
+
unmountTimeoutRef.current = setTimeout(() => {
|
|
108
|
+
unmountTimeoutRef.current = null;
|
|
109
|
+
setDialogState(null);
|
|
110
|
+
}, DIALOG_EXIT_ANIMATION_MS);
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
const handleOpenChange = useCallback(
|
|
114
|
+
(open: boolean) => {
|
|
115
|
+
setIsOpen(open);
|
|
116
|
+
if (!open && dialogState) {
|
|
117
|
+
// If dialog was closed without clicking a button (X button or outside click),
|
|
118
|
+
// trigger cancel callback if it exists
|
|
119
|
+
if (dialogState.from && dialogState.from.length > 0) {
|
|
120
|
+
shellui.sendMessage({
|
|
121
|
+
type: 'SHELLUI_DIALOG_CANCEL',
|
|
122
|
+
payload: { id: dialogState.id },
|
|
123
|
+
to: dialogState.from,
|
|
124
|
+
});
|
|
125
|
+
} else if (dialogState.id) {
|
|
126
|
+
// Dialog opened from root window - trigger callback directly
|
|
127
|
+
shellui.callbackRegistry.triggerCancel(dialogState.id);
|
|
128
|
+
shellui.callbackRegistry.clear(dialogState.id);
|
|
129
|
+
}
|
|
130
|
+
// Unmount after exit animation finishes
|
|
131
|
+
scheduleUnmount();
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
[dialogState, scheduleUnmount],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const handleOk = useCallback(() => {
|
|
138
|
+
if (dialogState?.from && dialogState.from.length > 0) {
|
|
139
|
+
shellui.sendMessage({
|
|
140
|
+
type: 'SHELLUI_DIALOG_OK',
|
|
141
|
+
payload: { id: dialogState.id },
|
|
142
|
+
to: dialogState.from,
|
|
143
|
+
});
|
|
144
|
+
} else if (dialogState?.id) {
|
|
145
|
+
// Dialog opened from root window - trigger callback directly
|
|
146
|
+
shellui.callbackRegistry.triggerAction(dialogState.id);
|
|
147
|
+
shellui.callbackRegistry.clear(dialogState.id);
|
|
148
|
+
}
|
|
149
|
+
setIsOpen(false);
|
|
150
|
+
scheduleUnmount();
|
|
151
|
+
}, [dialogState, scheduleUnmount]);
|
|
152
|
+
|
|
153
|
+
const handleCancel = useCallback(() => {
|
|
154
|
+
if (dialogState?.from && dialogState.from.length > 0) {
|
|
155
|
+
shellui.sendMessage({
|
|
156
|
+
type: 'SHELLUI_DIALOG_CANCEL',
|
|
157
|
+
payload: { id: dialogState.id },
|
|
158
|
+
to: dialogState.from,
|
|
159
|
+
});
|
|
160
|
+
} else if (dialogState?.id) {
|
|
161
|
+
// Dialog opened from root window - trigger callback directly
|
|
162
|
+
shellui.callbackRegistry.triggerCancel(dialogState.id);
|
|
163
|
+
shellui.callbackRegistry.clear(dialogState.id);
|
|
164
|
+
}
|
|
165
|
+
setIsOpen(false);
|
|
166
|
+
scheduleUnmount();
|
|
167
|
+
}, [dialogState, scheduleUnmount]);
|
|
168
|
+
|
|
169
|
+
const handleSecondary = useCallback(() => {
|
|
170
|
+
if (dialogState?.from && dialogState.from.length > 0) {
|
|
171
|
+
shellui.sendMessage({
|
|
172
|
+
type: 'SHELLUI_DIALOG_SECONDARY',
|
|
173
|
+
payload: { id: dialogState.id },
|
|
174
|
+
to: dialogState.from,
|
|
175
|
+
});
|
|
176
|
+
} else if (dialogState?.id) {
|
|
177
|
+
// Dialog opened from root window - trigger callback directly
|
|
178
|
+
shellui.callbackRegistry.triggerSecondary(dialogState.id);
|
|
179
|
+
shellui.callbackRegistry.clear(dialogState.id);
|
|
180
|
+
}
|
|
181
|
+
setIsOpen(false);
|
|
182
|
+
scheduleUnmount();
|
|
183
|
+
}, [dialogState, scheduleUnmount]);
|
|
184
|
+
|
|
185
|
+
const dialog = useCallback((options: DialogOptions) => {
|
|
186
|
+
// Only show dialog if window is root
|
|
187
|
+
if (typeof window === 'undefined' || window.parent !== window) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (unmountTimeoutRef.current) {
|
|
191
|
+
clearTimeout(unmountTimeoutRef.current);
|
|
192
|
+
unmountTimeoutRef.current = null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const dialogId = options.id || `dialog-${Date.now()}-${Math.random()}`;
|
|
196
|
+
|
|
197
|
+
// Register callbacks for direct calls (not from iframe)
|
|
198
|
+
if (options.onOk || options.onCancel || options.secondaryButton?.onClick) {
|
|
199
|
+
shellui.callbackRegistry.register(dialogId, {
|
|
200
|
+
action: options.onOk,
|
|
201
|
+
cancel: options.onCancel,
|
|
202
|
+
secondary: options.secondaryButton?.onClick,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
setDialogState({
|
|
207
|
+
id: dialogId,
|
|
208
|
+
title: options.title,
|
|
209
|
+
description: options.description,
|
|
210
|
+
mode: options.mode || 'ok',
|
|
211
|
+
okLabel: options.okLabel,
|
|
212
|
+
cancelLabel: options.cancelLabel,
|
|
213
|
+
size: options.size,
|
|
214
|
+
position: options.position,
|
|
215
|
+
secondaryButton: options.secondaryButton,
|
|
216
|
+
iconType: options.icon === 'cookie' ? 'cookie' : undefined,
|
|
217
|
+
});
|
|
218
|
+
setIsOpen(true);
|
|
219
|
+
}, []);
|
|
220
|
+
|
|
221
|
+
// Listen for postMessage events from nested iframes
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
// Only listen if window is root
|
|
224
|
+
if (typeof window === 'undefined' || window.parent !== window) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const cleanupDialog = shellui.addMessageListener('SHELLUI_DIALOG', (data: ShellUIMessage) => {
|
|
229
|
+
if (unmountTimeoutRef.current) {
|
|
230
|
+
clearTimeout(unmountTimeoutRef.current);
|
|
231
|
+
unmountTimeoutRef.current = null;
|
|
232
|
+
}
|
|
233
|
+
const payload = data.payload as DialogOptions & { id: string };
|
|
234
|
+
setDialogState({
|
|
235
|
+
id: payload.id,
|
|
236
|
+
title: payload.title,
|
|
237
|
+
description: payload.description,
|
|
238
|
+
mode: payload.mode || 'ok',
|
|
239
|
+
okLabel: payload.okLabel,
|
|
240
|
+
cancelLabel: payload.cancelLabel,
|
|
241
|
+
size: payload.size,
|
|
242
|
+
position: payload.position,
|
|
243
|
+
secondaryButton: payload.secondaryButton,
|
|
244
|
+
iconType: payload.icon === 'cookie' ? 'cookie' : undefined,
|
|
245
|
+
from: data.from,
|
|
246
|
+
});
|
|
247
|
+
setIsOpen(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const cleanupDialogUpdate = shellui.addMessageListener(
|
|
251
|
+
'SHELLUI_DIALOG_UPDATE',
|
|
252
|
+
(data: ShellUIMessage) => {
|
|
253
|
+
if (unmountTimeoutRef.current) {
|
|
254
|
+
clearTimeout(unmountTimeoutRef.current);
|
|
255
|
+
unmountTimeoutRef.current = null;
|
|
256
|
+
}
|
|
257
|
+
const payload = data.payload as DialogOptions & { id: string };
|
|
258
|
+
setDialogState({
|
|
259
|
+
id: payload.id,
|
|
260
|
+
title: payload.title,
|
|
261
|
+
description: payload.description,
|
|
262
|
+
mode: payload.mode || 'ok',
|
|
263
|
+
okLabel: payload.okLabel,
|
|
264
|
+
cancelLabel: payload.cancelLabel,
|
|
265
|
+
size: payload.size,
|
|
266
|
+
position: payload.position,
|
|
267
|
+
secondaryButton: payload.secondaryButton,
|
|
268
|
+
iconType: payload.icon === 'cookie' ? 'cookie' : undefined,
|
|
269
|
+
from: data.from,
|
|
270
|
+
});
|
|
271
|
+
setIsOpen(true);
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return () => {
|
|
276
|
+
cleanupDialog();
|
|
277
|
+
cleanupDialogUpdate();
|
|
278
|
+
if (unmountTimeoutRef.current) clearTimeout(unmountTimeoutRef.current);
|
|
279
|
+
};
|
|
280
|
+
}, []);
|
|
281
|
+
|
|
282
|
+
const renderButtons = () => {
|
|
283
|
+
if (!dialogState) return null;
|
|
284
|
+
|
|
285
|
+
const { mode, okLabel, cancelLabel, secondaryButton } = dialogState;
|
|
286
|
+
|
|
287
|
+
// Cookie consent layout: secondary button on left, primary buttons on right
|
|
288
|
+
if (secondaryButton && dialogState.position === 'bottom-left') {
|
|
289
|
+
return (
|
|
290
|
+
<>
|
|
291
|
+
<Button
|
|
292
|
+
size="sm"
|
|
293
|
+
variant="ghost"
|
|
294
|
+
onClick={handleSecondary}
|
|
295
|
+
>
|
|
296
|
+
{secondaryButton.label}
|
|
297
|
+
</Button>
|
|
298
|
+
<div className="flex gap-2">
|
|
299
|
+
{mode === 'okCancel' && (
|
|
300
|
+
<Button
|
|
301
|
+
size="sm"
|
|
302
|
+
variant="ghost"
|
|
303
|
+
onClick={handleCancel}
|
|
304
|
+
>
|
|
305
|
+
{cancelLabel || 'Cancel'}
|
|
306
|
+
</Button>
|
|
307
|
+
)}
|
|
308
|
+
<Button
|
|
309
|
+
size="sm"
|
|
310
|
+
onClick={handleOk}
|
|
311
|
+
>
|
|
312
|
+
{okLabel || 'OK'}
|
|
313
|
+
</Button>
|
|
314
|
+
</div>
|
|
315
|
+
</>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
switch (mode) {
|
|
320
|
+
case 'ok':
|
|
321
|
+
return <AlertDialogAction onClick={handleOk}>{okLabel || 'OK'}</AlertDialogAction>;
|
|
322
|
+
|
|
323
|
+
case 'okCancel':
|
|
324
|
+
return (
|
|
325
|
+
<>
|
|
326
|
+
<AlertDialogCancel
|
|
327
|
+
variant="outline"
|
|
328
|
+
onClick={handleCancel}
|
|
329
|
+
>
|
|
330
|
+
{cancelLabel || 'Cancel'}
|
|
331
|
+
</AlertDialogCancel>
|
|
332
|
+
<AlertDialogAction onClick={handleOk}>{okLabel || 'OK'}</AlertDialogAction>
|
|
333
|
+
</>
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
case 'delete':
|
|
337
|
+
return (
|
|
338
|
+
<>
|
|
339
|
+
<AlertDialogCancel
|
|
340
|
+
variant="outline"
|
|
341
|
+
onClick={handleCancel}
|
|
342
|
+
>
|
|
343
|
+
{cancelLabel || 'Cancel'}
|
|
344
|
+
</AlertDialogCancel>
|
|
345
|
+
<AlertDialogAction
|
|
346
|
+
variant="destructive"
|
|
347
|
+
onClick={handleOk}
|
|
348
|
+
>
|
|
349
|
+
{okLabel || 'Delete'}
|
|
350
|
+
</AlertDialogAction>
|
|
351
|
+
</>
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
case 'confirm':
|
|
355
|
+
return (
|
|
356
|
+
<>
|
|
357
|
+
<AlertDialogCancel
|
|
358
|
+
variant="outline"
|
|
359
|
+
onClick={handleCancel}
|
|
360
|
+
>
|
|
361
|
+
{cancelLabel || 'Cancel'}
|
|
362
|
+
</AlertDialogCancel>
|
|
363
|
+
<AlertDialogAction onClick={handleOk}>{okLabel || 'Confirm'}</AlertDialogAction>
|
|
364
|
+
</>
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
case 'onlyCancel':
|
|
368
|
+
return (
|
|
369
|
+
<AlertDialogCancel
|
|
370
|
+
variant="outline"
|
|
371
|
+
onClick={handleCancel}
|
|
372
|
+
>
|
|
373
|
+
{cancelLabel || 'Cancel'}
|
|
374
|
+
</AlertDialogCancel>
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
default:
|
|
378
|
+
return <AlertDialogAction onClick={handleOk}>{okLabel || 'OK'}</AlertDialogAction>;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Render cookie consent dialog with custom layout
|
|
383
|
+
const renderCookieConsentDialog = () => {
|
|
384
|
+
if (!dialogState || dialogState.position !== 'bottom-left') return null;
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<AlertDialog
|
|
388
|
+
open={isOpen}
|
|
389
|
+
onOpenChange={handleOpenChange}
|
|
390
|
+
>
|
|
391
|
+
<AlertDialogPortal>
|
|
392
|
+
<AlertDialogOverlay style={{ zIndex: Z_INDEX.COOKIE_CONSENT_OVERLAY }} />
|
|
393
|
+
<AlertDialogPrimitive.Content
|
|
394
|
+
className="fixed w-[calc(100%-32px)] max-w-[520px] rounded-xl border border-border bg-background text-foreground shadow-lg sm:w-full"
|
|
395
|
+
style={{
|
|
396
|
+
bottom: 16,
|
|
397
|
+
left: 16,
|
|
398
|
+
zIndex: Z_INDEX.COOKIE_CONSENT_CONTENT,
|
|
399
|
+
backgroundColor: 'hsl(var(--background))',
|
|
400
|
+
top: 'auto',
|
|
401
|
+
right: 'auto',
|
|
402
|
+
transform: 'none',
|
|
403
|
+
}}
|
|
404
|
+
data-dialog-content
|
|
405
|
+
data-cookie-consent
|
|
406
|
+
>
|
|
407
|
+
<div className="flex items-start gap-4 p-6 sm:gap-5 sm:p-7">
|
|
408
|
+
{dialogState.iconType === 'cookie' && (
|
|
409
|
+
<div
|
|
410
|
+
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10"
|
|
411
|
+
aria-hidden
|
|
412
|
+
>
|
|
413
|
+
<CookieIcon className="h-5 w-5 text-primary" />
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
<div className="flex-1 space-y-2">
|
|
417
|
+
<AlertDialogTitle className="text-base font-semibold leading-tight">
|
|
418
|
+
{dialogState.title}
|
|
419
|
+
</AlertDialogTitle>
|
|
420
|
+
{dialogState.description && (
|
|
421
|
+
<AlertDialogDescription className="text-sm leading-relaxed">
|
|
422
|
+
{dialogState.description}
|
|
423
|
+
</AlertDialogDescription>
|
|
424
|
+
)}
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
<div className="flex items-center justify-between rounded-b-xl border-t border-border bg-muted/50 px-6 py-4 sm:px-7">
|
|
428
|
+
{renderButtons()}
|
|
429
|
+
</div>
|
|
430
|
+
</AlertDialogPrimitive.Content>
|
|
431
|
+
</AlertDialogPortal>
|
|
432
|
+
</AlertDialog>
|
|
433
|
+
);
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
return (
|
|
437
|
+
<DialogContext.Provider value={{ dialog }}>
|
|
438
|
+
{children}
|
|
439
|
+
{dialogState && (
|
|
440
|
+
<>
|
|
441
|
+
{dialogState.position === 'bottom-left' ? (
|
|
442
|
+
renderCookieConsentDialog()
|
|
443
|
+
) : (
|
|
444
|
+
<AlertDialog
|
|
445
|
+
open={isOpen}
|
|
446
|
+
onOpenChange={handleOpenChange}
|
|
447
|
+
>
|
|
448
|
+
<AlertDialogContent size={dialogState.size ?? 'default'}>
|
|
449
|
+
<AlertDialogHeader>
|
|
450
|
+
{dialogState.mode === 'delete' && (
|
|
451
|
+
<AlertDialogMedia className="bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive">
|
|
452
|
+
<TrashIcon />
|
|
453
|
+
</AlertDialogMedia>
|
|
454
|
+
)}
|
|
455
|
+
<AlertDialogTitle>{dialogState.title}</AlertDialogTitle>
|
|
456
|
+
{dialogState.description && (
|
|
457
|
+
<AlertDialogDescription>{dialogState.description}</AlertDialogDescription>
|
|
458
|
+
)}
|
|
459
|
+
</AlertDialogHeader>
|
|
460
|
+
<AlertDialogFooter>{renderButtons()}</AlertDialogFooter>
|
|
461
|
+
</AlertDialogContent>
|
|
462
|
+
</AlertDialog>
|
|
463
|
+
)}
|
|
464
|
+
</>
|
|
465
|
+
)}
|
|
466
|
+
</DialogContext.Provider>
|
|
467
|
+
);
|
|
468
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createContext, useState, createElement, type ReactNode } from 'react';
|
|
2
|
+
import { getLogger } from '@shellui/sdk';
|
|
3
|
+
import type { ShellUIConfig } from './types';
|
|
4
|
+
|
|
5
|
+
const logger = getLogger('shellcore');
|
|
6
|
+
|
|
7
|
+
export interface ConfigContextValue {
|
|
8
|
+
config: ShellUIConfig;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ConfigContext = createContext<ConfigContextValue | null>(null);
|
|
12
|
+
|
|
13
|
+
export interface ConfigProviderProps {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Track if config has been logged to prevent duplicate logs in dev mode
|
|
18
|
+
// (can happen with React StrictMode or when app renders in multiple contexts)
|
|
19
|
+
let configLogged = false;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Loads ShellUI config from __SHELLUI_CONFIG__ (injected by Vite at build time)
|
|
23
|
+
* and provides it via context. Children can use useConfig() to read config.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export function ConfigProvider(props: ConfigProviderProps): ReturnType<typeof createElement> {
|
|
27
|
+
const [config] = useState<ShellUIConfig>(() => {
|
|
28
|
+
try {
|
|
29
|
+
// __SHELLUI_CONFIG__ is replaced by Vite at build time via define
|
|
30
|
+
// After replacement, it will be a JSON string like: "{\"title\":\"shellui\",...}"
|
|
31
|
+
// Vite's define inserts the string value directly, so we only need to parse once
|
|
32
|
+
// Access it directly (no typeof check) so Vite can statically analyze and replace it
|
|
33
|
+
// @ts-expect-error - __SHELLUI_CONFIG__ is injected by Vite at build time
|
|
34
|
+
const configValue: unknown = __SHELLUI_CONFIG__;
|
|
35
|
+
|
|
36
|
+
// After Vite replacement, configValue will be a JSON string
|
|
37
|
+
// Example: "{\"title\":\"shellui\"}" -> parse -> {title: "shellui"}
|
|
38
|
+
if (configValue !== undefined && configValue !== null && typeof configValue === 'string') {
|
|
39
|
+
try {
|
|
40
|
+
// Parse the JSON string to get the config object
|
|
41
|
+
const parsedConfig: ShellUIConfig = JSON.parse(configValue);
|
|
42
|
+
if (typeof window !== 'undefined' && parsedConfig.runtime === 'tauri') {
|
|
43
|
+
(window as Window & { __SHELLUI_TAURI__?: boolean }).__SHELLUI_TAURI__ = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Log in dev mode to help debug (only once per page load)
|
|
47
|
+
if (process.env.NODE_ENV === 'development' && !configLogged) {
|
|
48
|
+
configLogged = true;
|
|
49
|
+
logger.info('Config loaded from __SHELLUI_CONFIG__', {
|
|
50
|
+
hasNavigation: !!parsedConfig.navigation,
|
|
51
|
+
navigationItems: parsedConfig.navigation?.length || 0,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parsedConfig;
|
|
56
|
+
} catch (parseError) {
|
|
57
|
+
logger.error('Failed to parse config JSON:', { error: parseError });
|
|
58
|
+
logger.error('Config value (first 200 chars):', { value: configValue.substring(0, 200) });
|
|
59
|
+
// Fall through to return empty config
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fallback: try to read from globalThis (for edge cases or if define didn't work)
|
|
64
|
+
const g = globalThis as unknown as { __SHELLUI_CONFIG__?: unknown };
|
|
65
|
+
if (typeof g.__SHELLUI_CONFIG__ !== 'undefined') {
|
|
66
|
+
const fallbackValue = g.__SHELLUI_CONFIG__;
|
|
67
|
+
const parsedConfig: ShellUIConfig =
|
|
68
|
+
typeof fallbackValue === 'string'
|
|
69
|
+
? JSON.parse(fallbackValue)
|
|
70
|
+
: (fallbackValue as ShellUIConfig);
|
|
71
|
+
if (typeof window !== 'undefined' && parsedConfig.runtime === 'tauri') {
|
|
72
|
+
(window as Window & { __SHELLUI_TAURI__?: boolean }).__SHELLUI_TAURI__ = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (process.env.NODE_ENV === 'development') {
|
|
76
|
+
logger.warn('Config loaded from globalThis fallback (define may not have worked)');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return parsedConfig;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Return empty config if __SHELLUI_CONFIG__ is undefined (fallback for edge cases)
|
|
83
|
+
logger.warn(
|
|
84
|
+
'Config not found. Using empty config. Make sure shellui.config.ts is properly loaded during build.',
|
|
85
|
+
);
|
|
86
|
+
return {} as ShellUIConfig;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
logger.error('Failed to load ShellUI config:', { error: err });
|
|
89
|
+
// Don't throw - return empty config instead to prevent app crash
|
|
90
|
+
return {} as ShellUIConfig;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const value: ConfigContextValue = { config };
|
|
95
|
+
return createElement(ConfigContext.Provider, { value }, props.children);
|
|
96
|
+
}
|