@donotdev/ui 0.0.6 → 0.0.8

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 (55) hide show
  1. package/assets/fonts/fonts.css +4 -4
  2. package/dist/components/auth/AuthHeader.d.ts +5 -0
  3. package/dist/components/auth/AuthHeader.d.ts.map +1 -1
  4. package/dist/components/auth/AuthHeader.js +22 -8
  5. package/dist/components/auth/AuthMenu.d.ts +6 -2
  6. package/dist/components/auth/AuthMenu.d.ts.map +1 -1
  7. package/dist/components/auth/AuthMenu.js +2 -2
  8. package/dist/components/common/RedirectOverlay.d.ts +37 -0
  9. package/dist/components/common/RedirectOverlay.d.ts.map +1 -0
  10. package/dist/components/common/RedirectOverlay.js +243 -0
  11. package/dist/components/common/index.d.ts +1 -0
  12. package/dist/components/common/index.d.ts.map +1 -1
  13. package/dist/components/common/index.js +1 -0
  14. package/dist/components/layout/components/header/SettingsMenu.d.ts.map +1 -1
  15. package/dist/components/layout/components/header/SettingsMenu.js +1 -1
  16. package/dist/crud/components/DisplayFieldRenderer.js +1 -1
  17. package/dist/dndev.css +517 -90
  18. package/dist/index.js +4 -4
  19. package/dist/internal/devtools/components/DesignTab.d.ts.map +1 -1
  20. package/dist/internal/devtools/components/DesignTab.js +25 -3
  21. package/dist/internal/layout/components/PerformanceHints.d.ts +64 -0
  22. package/dist/internal/layout/components/PerformanceHints.d.ts.map +1 -0
  23. package/dist/internal/layout/components/PerformanceHints.js +88 -0
  24. package/dist/internal/layout/components/footer/FooterBranding.js +1 -1
  25. package/dist/internal/layout/config/presets/admin.d.ts +2 -2
  26. package/dist/internal/layout/config/presets/admin.d.ts.map +1 -1
  27. package/dist/internal/layout/config/presets/admin.js +8 -4
  28. package/dist/providers/NextJsAppProviders.d.ts.map +1 -1
  29. package/dist/providers/NextJsAppProviders.js +5 -1
  30. package/dist/providers/ViteAppProviders.d.ts.map +1 -1
  31. package/dist/providers/ViteAppProviders.js +4 -1
  32. package/dist/routing/hooks/hooks.next.d.ts +1 -0
  33. package/dist/routing/hooks/hooks.next.d.ts.map +1 -1
  34. package/dist/routing/hooks/hooks.next.js +1 -1
  35. package/dist/routing/hooks/hooks.vite.d.ts +1 -0
  36. package/dist/routing/hooks/hooks.vite.d.ts.map +1 -1
  37. package/dist/routing/hooks/hooks.vite.js +1 -1
  38. package/dist/routing/hooks/useRouteParam.next.d.ts +18 -0
  39. package/dist/routing/hooks/useRouteParam.next.d.ts.map +1 -0
  40. package/dist/routing/hooks/useRouteParam.next.js +38 -0
  41. package/dist/routing/hooks/useRouteParam.vite.d.ts +18 -0
  42. package/dist/routing/hooks/useRouteParam.vite.d.ts.map +1 -0
  43. package/dist/routing/hooks/useRouteParam.vite.js +38 -0
  44. package/dist/routing/index.d.ts +1 -1
  45. package/dist/routing/index.d.ts.map +1 -1
  46. package/dist/routing/index.js +1 -1
  47. package/dist/routing/useGoTo.d.ts +3 -4
  48. package/dist/routing/useGoTo.d.ts.map +1 -1
  49. package/dist/routing/useGoTo.js +16 -23
  50. package/dist/styles/index.css +513 -86
  51. package/dist/utils/useCrudSafe.d.ts.map +1 -1
  52. package/dist/utils/useCrudSafe.js +2 -1
  53. package/dist/vite-routing/RootLayout.d.ts.map +1 -1
  54. package/dist/vite-routing/RootLayout.js +4 -1
  55. package/package.json +10 -11
@@ -4,7 +4,7 @@
4
4
  font-family: Roboto;
5
5
  font-style: normal;
6
6
  font-weight: 400;
7
- font-display: optional;
7
+ font-display: swap;
8
8
  src: url('/fonts/Roboto-400-latin.woff2') format('woff2');
9
9
  unicode-range: U+0000-00FF; /* Basic Latin - loads first for performance */
10
10
  }
@@ -13,7 +13,7 @@
13
13
  font-family: Roboto;
14
14
  font-style: normal;
15
15
  font-weight: 700;
16
- font-display: optional;
16
+ font-display: swap;
17
17
  src: url('/fonts/Roboto-700-latin.woff2') format('woff2');
18
18
  unicode-range: U+0000-00FF; /* Basic Latin - loads first for performance */
19
19
  }
@@ -153,7 +153,7 @@
153
153
  font-family: Inter;
154
154
  font-style: normal;
155
155
  font-weight: 400 700;
156
- font-display: optional;
156
+ font-display: swap; /* Changed from optional to swap - we preload this font */
157
157
  src: url('/fonts/Inter-latin.woff2') format('woff2');
158
158
  unicode-range:
159
159
  U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
@@ -184,7 +184,7 @@
184
184
  font-family: 'Playfair Display';
185
185
  font-style: normal;
186
186
  font-weight: 400 700;
187
- font-display: optional;
187
+ font-display: swap;
188
188
  src: url('/fonts/PlayfairDisplay-latin.woff2') format('woff2');
189
189
  unicode-range:
190
190
  U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
@@ -1,3 +1,4 @@
1
+ import { type CSSProperties } from 'react';
1
2
  import { DISPLAY } from '@donotdev/components';
2
3
  import type { ComponentType } from 'react';
3
4
  /**
@@ -46,6 +47,10 @@ export interface AuthHeaderProps {
46
47
  * @default 'auto'
47
48
  */
48
49
  display?: (typeof DISPLAY)[keyof typeof DISPLAY];
50
+ /** Optional className for styling the button */
51
+ className?: string;
52
+ /** Optional inline styles for the button */
53
+ style?: CSSProperties;
49
54
  }
50
55
  /**
51
56
  * Auth header component
@@ -1 +1 @@
1
- {"version":3,"file":"AuthHeader.d.ts","sourceRoot":"","sources":["../../../src/components/auth/AuthHeader.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAIL,OAAO,EACR,MAAM,sBAAsB,CAAC;AAU9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAyB3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;CAClD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,eAAe,CA0FrD,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"AuthHeader.d.ts","sourceRoot":"","sources":["../../../src/components/auth/AuthHeader.tsx"],"names":[],"mappings":"AAcA,OAAc,EAA4B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAE5E,OAAO,EAKL,OAAO,EACR,MAAM,sBAAsB,CAAC;AAU9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAyB3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;IACjD,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,eAAe,CA4IrD,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -10,8 +10,8 @@
10
10
  'use client';
11
11
  import { jsx as _jsx } from "react/jsx-runtime";
12
12
  import { User } from 'lucide-react';
13
- import React, { Suspense, lazy } from 'react';
14
- import { Button, BUTTON_VARIANT, DropdownMenu, DISPLAY, } from '@donotdev/components';
13
+ import React, { Suspense, lazy, useState } from 'react';
14
+ import { Button, BUTTON_VARIANT, DropdownMenu, Dialog, DISPLAY, } from '@donotdev/components';
15
15
  import { useTranslation } from '@donotdev/core';
16
16
  import { useAuthConfig } from '@donotdev/core';
17
17
  import { getEnabledAuthPartners } from '@donotdev/core';
@@ -47,34 +47,48 @@ const MultipleAuthProviders = lazy(async () => {
47
47
  * @since 0.0.1
48
48
  * @author AMBROISE PARK Consulting
49
49
  */
50
- export const AuthHeader = ({ display = DISPLAY.AUTO, }) => {
50
+ export const AuthHeader = ({ display = DISPLAY.AUTO, className, style, }) => {
51
51
  const { t } = useTranslation('dndev');
52
52
  const { shouldHide, isLoading, isReady, isAuthenticated } = useAuthVisibility();
53
53
  const authConfig = useAuthConfig();
54
54
  const enabled = getEnabledAuthPartners();
55
+ const [dialogOpen, setDialogOpen] = useState(false);
55
56
  const loginPath = authConfig.loginPath;
56
57
  const signInLabel = t('auth.signIn', { defaultValue: 'Sign In' });
57
58
  const loadingLabel = t('auth.loading', { defaultValue: 'Loading...' });
59
+ const hasPasswordProvider = enabled.includes('password') || enabled.includes('emailLink');
58
60
  if (shouldHide || !isReady) {
59
61
  return null;
60
62
  }
63
+ // Local component for Sign In button (DRY)
64
+ const SignInButton = ({ children, ...props }) => (_jsx(Button, { variant: BUTTON_VARIANT.OUTLINE, icon: User, display: display, tooltip: signInLabel, className: className, style: style, ...props, children: children ?? signInLabel }));
61
65
  // NOT AUTHENTICATED
62
66
  // Button's display prop + CSS container queries handle responsive label visibility
63
67
  if (!isAuthenticated) {
64
68
  if (loginPath !== undefined) {
65
- return (_jsx(Link, { path: loginPath, prefetch: true, children: _jsx(Button, { variant: BUTTON_VARIANT.GHOST, icon: User, display: display, tooltip: signInLabel, children: signInLabel }) }));
69
+ return (_jsx(Link, { path: loginPath, prefetch: true, children: _jsx(SignInButton, {}) }));
66
70
  }
71
+ const targetProvider = enabled[0];
72
+ // Use Dialog if password/emailLink provider is present (better UX for forms)
73
+ if (hasPasswordProvider) {
74
+ return (_jsx(Dialog, { trigger: _jsx(SignInButton, {}), title: signInLabel, open: dialogOpen, onOpenChange: setDialogOpen, "data-content-size": "form", children: _jsx(Suspense, { fallback: _jsx("div", { style: {
75
+ padding: 'var(--gap-md)',
76
+ fontSize: 'var(--font-size-sm)',
77
+ color: 'var(--muted-foreground)',
78
+ }, children: loadingLabel }), children: _jsx(MultipleAuthProviders, { spacing: "medium", onSuccess: () => setDialogOpen(false) }) }) }));
79
+ }
80
+ // Use DropdownMenu if multiple OAuth providers (no password/emailLink)
67
81
  if (enabled.length > 1) {
68
- return (_jsx(DropdownMenu, { trigger: _jsx(Button, { variant: BUTTON_VARIANT.GHOST, icon: User, display: display, tooltip: signInLabel, children: signInLabel }), children: _jsx(Suspense, { fallback: _jsx("div", { style: {
82
+ return (_jsx(DropdownMenu, { trigger: _jsx(SignInButton, {}), children: _jsx(Suspense, { fallback: _jsx("div", { style: {
69
83
  padding: 'var(--gap-md)',
70
84
  fontSize: 'var(--font-size-sm)',
71
85
  color: 'var(--muted-foreground)',
72
86
  }, children: loadingLabel }), children: _jsx(MultipleAuthProviders, { spacing: "tight" }) }) }));
73
87
  }
74
- const targetProvider = enabled[0];
75
- return (_jsx(Suspense, { fallback: _jsx(Button, { variant: BUTTON_VARIANT.GHOST, icon: User, display: display, disabled: isLoading, tooltip: isLoading ? loadingLabel : signInLabel, children: isLoading ? loadingLabel : signInLabel }), children: _jsx(AuthPartnerButton, { partnerId: targetProvider, display: display }) }));
88
+ // Normal case: single non-password provider → use AuthPartnerButton directly
89
+ return (_jsx(Suspense, { fallback: _jsx(SignInButton, { disabled: isLoading, tooltip: isLoading ? loadingLabel : signInLabel, children: isLoading ? loadingLabel : signInLabel }), children: _jsx(AuthPartnerButton, { partnerId: targetProvider, display: display, className: className, style: style }) }));
76
90
  }
77
91
  // AUTHENTICATED - Delegate to AuthMenu
78
- return _jsx(AuthMenu, { display: display, loginPath: loginPath });
92
+ return (_jsx(AuthMenu, { display: display, loginPath: loginPath, className: className, style: style }));
79
93
  };
80
94
  export default AuthHeader;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { type CSSProperties } from 'react';
2
2
  import { DISPLAY } from '@donotdev/components';
3
3
  import type { ComponentType } from 'react';
4
4
  /**
@@ -53,7 +53,11 @@ export interface AuthMenuProps {
53
53
  * Order: Profile (if configured) → authMenuItems → customItems → Delete Account → Sign Out
54
54
  */
55
55
  customItems?: AuthMenuCustomItem[];
56
+ /** Optional className for styling the button */
57
+ className?: string;
58
+ /** Optional inline styles for the button */
59
+ style?: CSSProperties;
56
60
  }
57
- export declare const AuthMenu: ({ loginPath, display, "no-tooltip": noTooltip, customItems, }: AuthMenuProps) => import("react/jsx-runtime").JSX.Element | null;
61
+ export declare const AuthMenu: ({ loginPath, display, "no-tooltip": noTooltip, customItems, className, style, }: AuthMenuProps) => import("react/jsx-runtime").JSX.Element | null;
58
62
  export default AuthMenu;
59
63
  //# sourceMappingURL=AuthMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuthMenu.d.ts","sourceRoot":"","sources":["../../../src/components/auth/AuthMenu.tsx"],"names":[],"mappings":"AA4EA,OAAO,KAAkC,MAAM,OAAO,CAAC;AAEvD,OAAO,EAML,OAAO,EACR,MAAM,sBAAsB,CAAC;AAc9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAqE3C;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mBAAmB;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;IAEjD,sBAAsB;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACpC;AAsBD,eAAO,MAAM,QAAQ,GAAI,+DAKtB,aAAa,mDAwOf,CAAC;AAEF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"AuthMenu.d.ts","sourceRoot":"","sources":["../../../src/components/auth/AuthMenu.tsx"],"names":[],"mappings":"AA4EA,OAAO,KAAK,EAAE,EAA2B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3E,OAAO,EAML,OAAO,EACR,MAAM,sBAAsB,CAAC;AAc9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAqE3C;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mBAAmB;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;IAEjD,sBAAsB;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAEnC,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAsBD,eAAO,MAAM,QAAQ,GAAI,iFAOtB,aAAa,mDA0Of,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -154,7 +154,7 @@ const getIcon = (iconName) => {
154
154
  const cleanIconName = iconName.replace(/['"]/g, '');
155
155
  return LucideIcons[cleanIconName] || Settings;
156
156
  };
157
- export const AuthMenu = ({ loginPath, display = DISPLAY.AUTO, 'no-tooltip': noTooltip = false, customItems = [], }) => {
157
+ export const AuthMenu = ({ loginPath, display = DISPLAY.AUTO, 'no-tooltip': noTooltip = false, customItems = [], className, style, }) => {
158
158
  const { t } = useTranslation('dndev');
159
159
  const { shouldHide, isReady, isAuthenticated } = useAuthVisibility();
160
160
  const authConfig = useAuthConfig();
@@ -312,6 +312,6 @@ export const AuthMenu = ({ loginPath, display = DISPLAY.AUTO, 'no-tooltip': noTo
312
312
  ? getUserTooltip()
313
313
  : !noTooltip
314
314
  ? t('auth.userMenu', { defaultValue: 'User menu' })
315
- : undefined, children: getUserDisplayName() }), items: menuItems, contentAlign: "end" })] }));
315
+ : undefined, className: className, style: style, children: getUserDisplayName() }), items: menuItems, contentAlign: "end" })] }));
316
316
  };
317
317
  export default AuthMenu;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * RedirectOverlay - Industry-standard redirect overlay with phase progression
3
+ *
4
+ * Features:
5
+ * - Phase-based progress: connecting → preparing → redirecting → timeout
6
+ * - Cancel button appears after 10s (configurable)
7
+ * - i18n support with operation-specific messaging
8
+ * - Browser event handling (popstate, visibilitychange)
9
+ * - Accessible (ARIA labels, role="status")
10
+ * - RTL-safe styling
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * // Include once in app root (layout.tsx or App.tsx)
15
+ * import { RedirectOverlay } from '@donotdev/ui';
16
+ *
17
+ * function RootLayout({ children }) {
18
+ * return (
19
+ * <>
20
+ * {children}
21
+ * <RedirectOverlay />
22
+ * </>
23
+ * );
24
+ * }
25
+ *
26
+ * // Hooks trigger it automatically
27
+ * const checkout = useStripeBilling('checkout', authState);
28
+ * await checkout({ priceId: 'price_123' }); // Shows overlay automatically
29
+ * ```
30
+ *
31
+ * @version 0.0.1
32
+ * @since 0.0.1
33
+ * @author AMBROISE PARK Consulting
34
+ */
35
+ export declare function RedirectOverlay(): import("react/jsx-runtime").JSX.Element | null;
36
+ export default RedirectOverlay;
37
+ //# sourceMappingURL=RedirectOverlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedirectOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/common/RedirectOverlay.tsx"],"names":[],"mappings":"AAoEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,eAAe,mDA+P9B;AAED,eAAe,eAAe,CAAC"}
@@ -0,0 +1,243 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // packages/ui/src/components/common/RedirectOverlay.tsx
4
+ /**
5
+ * @fileoverview RedirectOverlay component
6
+ * @description Industry-standard fullscreen overlay for redirect operations (payments, OAuth, etc.).
7
+ * Features phase-based progress, 10s cancel timeout, i18n support, and browser event handling.
8
+ *
9
+ * @version 0.0.1
10
+ * @since 0.0.1
11
+ * @author AMBROISE PARK Consulting
12
+ */
13
+ import { useEffect, useRef, useCallback } from 'react';
14
+ import { Lock, Shield } from 'lucide-react';
15
+ import { Portal, Spinner, Stack, Text, Button } from '@donotdev/components';
16
+ import { useTranslation, useOverlay, } from '@donotdev/core';
17
+ /** Phase progression timings (ms from start) */
18
+ const PHASE_TIMINGS = {
19
+ connecting: 0,
20
+ preparing: 2000,
21
+ redirecting: 5000,
22
+ };
23
+ /** Default timeout before showing cancel button (10 seconds) */
24
+ const DEFAULT_CANCEL_TIMEOUT = 10000;
25
+ /**
26
+ * Get icon component based on operation type
27
+ */
28
+ function getOperationIcon(operation, configIcon) {
29
+ if (configIcon === 'none')
30
+ return null;
31
+ if (configIcon === 'shield')
32
+ return Shield;
33
+ if (configIcon === 'lock')
34
+ return Lock;
35
+ // Infer from operation
36
+ if (!operation)
37
+ return Lock;
38
+ if (operation.startsWith('stripe-') ||
39
+ operation.includes('payment') ||
40
+ operation.includes('checkout')) {
41
+ return Lock;
42
+ }
43
+ if (operation.startsWith('oauth-') || operation.startsWith('auth-')) {
44
+ return Shield;
45
+ }
46
+ return Lock;
47
+ }
48
+ /**
49
+ * Get i18n key for operation
50
+ */
51
+ function getOperationKey(operation) {
52
+ if (!operation)
53
+ return 'default';
54
+ // Check if operation has a specific translation, otherwise use default
55
+ return operation;
56
+ }
57
+ /**
58
+ * RedirectOverlay - Industry-standard redirect overlay with phase progression
59
+ *
60
+ * Features:
61
+ * - Phase-based progress: connecting → preparing → redirecting → timeout
62
+ * - Cancel button appears after 10s (configurable)
63
+ * - i18n support with operation-specific messaging
64
+ * - Browser event handling (popstate, visibilitychange)
65
+ * - Accessible (ARIA labels, role="status")
66
+ * - RTL-safe styling
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * // Include once in app root (layout.tsx or App.tsx)
71
+ * import { RedirectOverlay } from '@donotdev/ui';
72
+ *
73
+ * function RootLayout({ children }) {
74
+ * return (
75
+ * <>
76
+ * {children}
77
+ * <RedirectOverlay />
78
+ * </>
79
+ * );
80
+ * }
81
+ *
82
+ * // Hooks trigger it automatically
83
+ * const checkout = useStripeBilling('checkout', authState);
84
+ * await checkout({ priceId: 'price_123' }); // Shows overlay automatically
85
+ * ```
86
+ *
87
+ * @version 0.0.1
88
+ * @since 0.0.1
89
+ * @author AMBROISE PARK Consulting
90
+ */
91
+ export function RedirectOverlay() {
92
+ const { t } = useTranslation('dndev');
93
+ // Subscribe to overlay store
94
+ const isOpen = useOverlay('isRedirectOverlayOpen');
95
+ const operation = useOverlay('redirectOperation');
96
+ const phase = useOverlay('redirectPhase');
97
+ const showCancelButton = useOverlay('showCancelButton');
98
+ const config = useOverlay('redirectConfig');
99
+ const startTime = useOverlay('redirectStartTime');
100
+ // Actions
101
+ const setRedirectPhase = useOverlay('setRedirectPhase');
102
+ const setShowCancelButton = useOverlay('setShowCancelButton');
103
+ const hideRedirectOverlay = useOverlay('hideRedirectOverlay');
104
+ // Refs for cleanup
105
+ const phaseTimersRef = useRef([]);
106
+ const cancelTimerRef = useRef(null);
107
+ // Cancel handler
108
+ const handleCancel = useCallback(() => {
109
+ hideRedirectOverlay();
110
+ }, [hideRedirectOverlay]);
111
+ // Setup phase progression and cancel timer when overlay opens
112
+ useEffect(() => {
113
+ if (!isOpen || !startTime)
114
+ return;
115
+ const cancelTimeout = config?.cancelTimeout ?? DEFAULT_CANCEL_TIMEOUT;
116
+ const elapsed = Date.now() - startTime;
117
+ // Clear any existing timers
118
+ phaseTimersRef.current.forEach(clearTimeout);
119
+ phaseTimersRef.current = [];
120
+ if (cancelTimerRef.current) {
121
+ clearTimeout(cancelTimerRef.current);
122
+ cancelTimerRef.current = null;
123
+ }
124
+ // Schedule phase transitions
125
+ const schedulePhase = (targetPhase, delay) => {
126
+ const adjustedDelay = Math.max(0, delay - elapsed);
127
+ if (adjustedDelay > 0) {
128
+ const timer = setTimeout(() => {
129
+ setRedirectPhase(targetPhase);
130
+ }, adjustedDelay);
131
+ phaseTimersRef.current.push(timer);
132
+ }
133
+ else {
134
+ // Already past this phase
135
+ setRedirectPhase(targetPhase);
136
+ }
137
+ };
138
+ schedulePhase('preparing', PHASE_TIMINGS.preparing);
139
+ schedulePhase('redirecting', PHASE_TIMINGS.redirecting);
140
+ // Schedule cancel button
141
+ const cancelDelay = Math.max(0, cancelTimeout - elapsed);
142
+ cancelTimerRef.current = setTimeout(() => {
143
+ setShowCancelButton(true);
144
+ setRedirectPhase('timeout');
145
+ }, cancelDelay);
146
+ // Cleanup
147
+ return () => {
148
+ phaseTimersRef.current.forEach(clearTimeout);
149
+ phaseTimersRef.current = [];
150
+ if (cancelTimerRef.current) {
151
+ clearTimeout(cancelTimerRef.current);
152
+ cancelTimerRef.current = null;
153
+ }
154
+ };
155
+ }, [
156
+ isOpen,
157
+ startTime,
158
+ config?.cancelTimeout,
159
+ setRedirectPhase,
160
+ setShowCancelButton,
161
+ ]);
162
+ // Handle browser back button (popstate)
163
+ useEffect(() => {
164
+ if (!isOpen)
165
+ return;
166
+ const handlePopState = () => {
167
+ // User pressed back - hide overlay
168
+ hideRedirectOverlay();
169
+ };
170
+ window.addEventListener('popstate', handlePopState);
171
+ return () => window.removeEventListener('popstate', handlePopState);
172
+ }, [isOpen, hideRedirectOverlay]);
173
+ // Handle tab visibility change
174
+ useEffect(() => {
175
+ if (!isOpen)
176
+ return;
177
+ const handleVisibilityChange = () => {
178
+ // If user switches away and comes back, the redirect probably failed
179
+ // We could add logic here to show a "retry" state, but for now we keep overlay visible
180
+ // This prevents the overlay from disappearing when user tabs away briefly
181
+ };
182
+ document.addEventListener('visibilitychange', handleVisibilityChange);
183
+ return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
184
+ }, [isOpen]);
185
+ // Don't render if not open
186
+ if (!isOpen)
187
+ return null;
188
+ // Get operation-specific translations
189
+ const operationKey = getOperationKey(operation);
190
+ // Try operation-specific translation, fallback to default
191
+ const title = config?.title ??
192
+ t(`redirectOverlay.${operationKey}.title`, {
193
+ defaultValue: t('redirectOverlay.default.title'),
194
+ });
195
+ const message = config?.message ??
196
+ t(`redirectOverlay.${operationKey}.message`, {
197
+ defaultValue: t('redirectOverlay.default.message'),
198
+ });
199
+ const subtitle = config?.subtitle ??
200
+ t(`redirectOverlay.${operationKey}.subtitle`, {
201
+ defaultValue: t('redirectOverlay.default.subtitle'),
202
+ });
203
+ const ariaLabel = t(`redirectOverlay.${operationKey}.ariaLabel`, {
204
+ defaultValue: t('redirectOverlay.default.ariaLabel'),
205
+ });
206
+ // Get phase message
207
+ const phaseMessage = t(`redirectOverlay.phases.${phase}`);
208
+ // Get icon
209
+ const IconComponent = getOperationIcon(operation, config?.icon);
210
+ return (_jsx(Portal, { children: _jsx("div", { className: "dndev-spinner-overlay", role: "status", "aria-busy": "true", "aria-label": ariaLabel, style: {
211
+ // Ensure highest z-index for redirect overlay
212
+ zIndex: 'var(--z-overlay, 9999)',
213
+ }, children: _jsxs("div", { style: {
214
+ display: 'flex',
215
+ flexDirection: 'column',
216
+ alignItems: 'center',
217
+ justifyContent: 'center',
218
+ gap: 'var(--gap-md)',
219
+ textAlign: 'center',
220
+ maxWidth: '400px',
221
+ padding: 'var(--gap-lg)',
222
+ }, children: [_jsxs(Stack, { direction: "row", gap: "medium", align: "center", children: [IconComponent && (_jsx(IconComponent, { style: {
223
+ width: '1.5rem',
224
+ height: '1.5rem',
225
+ color: 'var(--primary)',
226
+ }, "aria-hidden": "true" })), _jsx(Spinner, { variant: "primary" })] }), _jsx(Text, { as: "h3", style: {
227
+ color: 'var(--foreground)',
228
+ margin: 0,
229
+ }, children: title }), _jsx(Text, { variant: "muted", style: {
230
+ color: 'var(--foreground)',
231
+ minHeight: '1.5em',
232
+ }, children: phaseMessage }), _jsx(Text, { variant: "muted", level: "small", style: {
233
+ opacity: 0.8,
234
+ }, children: message }), _jsx(Text, { variant: "muted", level: "small", style: {
235
+ opacity: 0.6,
236
+ }, children: subtitle }), _jsx(Text, { variant: "muted", level: "small", style: {
237
+ opacity: 0.5,
238
+ marginTop: 'var(--gap-sm)',
239
+ }, children: t('redirectOverlay.doNotRefresh') }), showCancelButton && (_jsx(Button, { variant: "ghost", onClick: handleCancel, style: {
240
+ marginTop: 'var(--gap-md)',
241
+ }, children: t('redirectOverlay.cancel') }))] }) }) }));
242
+ }
243
+ export default RedirectOverlay;
@@ -13,6 +13,7 @@ export { default as Loader } from './Loader';
13
13
  export { default as LoadingMessage, LoadingMessageSimple, LoadingMessageProcessing, LoadingMessageAlmostDone, LoadingMessageComplete, LoadingMessageSaving, LoadingMessageUploading, LoadingMessageSearching, LoadingMessageConnecting, } from './LoadingMessage';
14
14
  export { default as LoadingOverlay, LoadingContainerOverlay, } from './LoadingOverlay';
15
15
  export { default as LoadingScreen } from './LoadingScreen';
16
+ export { RedirectOverlay } from './RedirectOverlay';
16
17
  export { default as ProgressBar } from './ProgressBar';
17
18
  export { default as Skeleton } from './Skeleton';
18
19
  export { default as FeatureCard } from './FeatureCard';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/common/index.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,OAAO,IAAI,cAAc,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,OAAO,IAAI,cAAc,EACzB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/common/index.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,OAAO,IAAI,cAAc,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,OAAO,IAAI,cAAc,EACzB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
@@ -14,6 +14,7 @@ export { default as Loader } from './Loader';
14
14
  export { default as LoadingMessage, LoadingMessageSimple, LoadingMessageProcessing, LoadingMessageAlmostDone, LoadingMessageComplete, LoadingMessageSaving, LoadingMessageUploading, LoadingMessageSearching, LoadingMessageConnecting, } from './LoadingMessage';
15
15
  export { default as LoadingOverlay, LoadingContainerOverlay, } from './LoadingOverlay';
16
16
  export { default as LoadingScreen } from './LoadingScreen';
17
+ export { RedirectOverlay } from './RedirectOverlay';
17
18
  export { default as ProgressBar } from './ProgressBar';
18
19
  export { default as Skeleton } from './Skeleton';
19
20
  export { default as FeatureCard } from './FeatureCard';
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsMenu.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/components/header/SettingsMenu.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAYhF,MAAM,WAAW,iBAAiB;IAChC,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;;;;GAMG;AACH,iBAAS,YAAY,CAAC,EACpB,OAAgC,GACjC,GAAE,iBAAsB,2CA4FxB;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"SettingsMenu.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/components/header/SettingsMenu.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAYhF,MAAM,WAAW,iBAAiB;IAChC,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;;;;GAMG;AACH,iBAAS,YAAY,CAAC,EACpB,OAAgC,GACjC,GAAE,iBAAsB,2CA+FxB;AAED,eAAe,YAAY,CAAC"}
@@ -54,7 +54,7 @@ function SettingsMenu({ variant = BUTTON_VARIANT.OUTLINE, } = {}) {
54
54
  checked: isActive,
55
55
  className: isActive ? 'bg-accent' : undefined,
56
56
  iconEnd: isActive && !isLoading ? Check : undefined,
57
- children: (_jsxs(Stack, { direction: "row", align: "center", gap: "tight", children: [_jsx(Flag, { code: language.id, title: `${language.name} flag` }), _jsx("span", { children: language.name }), isLoading && isActive && (_jsx("div", { style: { marginInlineStart: 'auto' }, children: _jsx("div", { style: {
57
+ children: (_jsxs(Stack, { direction: "row", align: "center", gap: "tight", children: [_jsx(Flag, { code: language.flagCode || language.id, title: `${language.name} flag` }), _jsx("span", { children: language.name }), isLoading && isActive && (_jsx("div", { style: { marginInlineStart: 'auto' }, children: _jsx("div", { style: {
58
58
  width: '0.5rem',
59
59
  height: '0.5rem',
60
60
  borderRadius: '50%',
@@ -94,7 +94,7 @@ export function DisplayFieldRenderer({ name, config, t, value, className, }) {
94
94
  }
95
95
  };
96
96
  // Don't render hidden fields
97
- if (config.hidden) {
97
+ if (config.visibility === 'hidden') {
98
98
  return _jsx(_Fragment, {});
99
99
  }
100
100
  const displayComponent = renderDisplayComponent();