@lerx/promise-modal 0.2.8 → 0.3.0

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.
@@ -1,4 +1,119 @@
1
1
  import type { BootstrapProviderHandle } from './type';
2
+ /**
3
+ * Provider component that bootstraps the promise-modal system.
4
+ *
5
+ * Sets up the modal rendering infrastructure by creating a portal anchor point
6
+ * and managing the lifecycle of modal components. Can be initialized automatically
7
+ * on mount or manually via ref handle.
8
+ *
9
+ * @example
10
+ * Basic usage with automatic initialization:
11
+ * ```tsx
12
+ * import { ModalProvider, alert, confirm } from '@lerx/promise-modal';
13
+ *
14
+ * function App() {
15
+ * return (
16
+ * <ModalProvider>
17
+ * <button onClick={() => alert({ title: 'Hello!' })}>Alert</button>
18
+ * <button onClick={() => confirm({ title: 'Confirm?' })}>Confirm</button>
19
+ * </ModalProvider>
20
+ * );
21
+ * }
22
+ * ```
23
+ *
24
+ * @example
25
+ * Custom modal components:
26
+ * ```tsx
27
+ * const CustomForeground = ({ children, visible }) => (
28
+ * <div className={`modal ${visible ? 'show' : 'hide'}`}>
29
+ * {children}
30
+ * </div>
31
+ * );
32
+ *
33
+ * const CustomTitle = ({ children }) => (
34
+ * <h2 className="modal-title">{children}</h2>
35
+ * );
36
+ *
37
+ * <ModalProvider
38
+ * ForegroundComponent={CustomForeground}
39
+ * TitleComponent={CustomTitle}
40
+ * options={{
41
+ * duration: 300,
42
+ * backdrop: 'rgba(0, 0, 0, 0.5)',
43
+ * closeOnBackdropClick: false,
44
+ * }}
45
+ * >
46
+ * <App />
47
+ * </ModalProvider>
48
+ * ```
49
+ *
50
+ * @example
51
+ * Manual initialization with custom anchor:
52
+ * ```tsx
53
+ * function CustomModalContainer() {
54
+ * const modalRef = useRef<ModalProviderHandle>(null);
55
+ * const containerRef = useRef<HTMLDivElement>(null);
56
+ *
57
+ * useEffect(() => {
58
+ * if (containerRef.current) {
59
+ * modalRef.current?.initialize(containerRef.current);
60
+ * }
61
+ * }, []);
62
+ *
63
+ * return (
64
+ * <ModalProvider ref={modalRef}>
65
+ * <div className="app-layout">
66
+ * <main>Main Content</main>
67
+ * <div ref={containerRef} className="modal-container" />
68
+ * </div>
69
+ * </ModalProvider>
70
+ * );
71
+ * }
72
+ * ```
73
+ *
74
+ * @example
75
+ * With routing integration:
76
+ * ```tsx
77
+ * import { useLocation } from 'react-router-dom';
78
+ *
79
+ * function useRouterPathname() {
80
+ * const location = useLocation();
81
+ * return { pathname: location.pathname };
82
+ * }
83
+ *
84
+ * <ModalProvider usePathname={useRouterPathname}>
85
+ * <RouterApp />
86
+ * </ModalProvider>
87
+ * ```
88
+ *
89
+ * @example
90
+ * Sharing context with modals:
91
+ * ```tsx
92
+ * const ThemeContext = createContext({ theme: 'light' });
93
+ *
94
+ * function ThemedModal() {
95
+ * const { theme } = useContext(ThemeContext);
96
+ * return <div className={`modal-${theme}`}>...</div>;
97
+ * }
98
+ *
99
+ * <ModalProvider
100
+ * context={{ userId: currentUser.id }}
101
+ * ContentComponent={({ children, context }) => (
102
+ * <ThemeContext.Provider value={{ theme: context.theme }}>
103
+ * {children}
104
+ * </ThemeContext.Provider>
105
+ * )}
106
+ * >
107
+ * <App />
108
+ * </ModalProvider>
109
+ * ```
110
+ *
111
+ * @remarks
112
+ * - Without a ref, the provider initializes automatically on mount
113
+ * - With a ref, you must call `initialize()` manually with target element
114
+ * - All modals created within this provider share the same configuration
115
+ * - The provider creates a portal for rendering modals outside the React tree
116
+ */
2
117
  export declare const BootstrapProvider: import("react").ForwardRefExoticComponent<{
3
118
  usePathname?: import("../../@aileron/declare").Fn<[], {
4
119
  pathname: string;
@@ -1,4 +1,186 @@
1
1
  import type { BootstrapProviderProps } from './type';
2
+ /**
3
+ * Hook for bootstrapping the promise-modal system without a provider component.
4
+ *
5
+ * Provides the same functionality as BootstrapProvider but as a hook, allowing
6
+ * more flexible integration patterns. Returns a portal element and an initialize
7
+ * function for manual control.
8
+ *
9
+ * @param props - Configuration options (same as BootstrapProvider)
10
+ * @param props.mode - 'auto' for automatic initialization, 'manual' for explicit control
11
+ * @returns Object containing portal element and initialize function
12
+ *
13
+ * @example
14
+ * Basic usage with automatic initialization:
15
+ * ```tsx
16
+ * function App() {
17
+ * const { portal } = useBootstrap({
18
+ * options: {
19
+ * duration: 200,
20
+ * backdrop: 'rgba(0, 0, 0, 0.8)',
21
+ * },
22
+ * });
23
+ *
24
+ * return (
25
+ * <>
26
+ * <button onClick={() => alert({ title: 'Hello!' })}>Alert</button>
27
+ * <button onClick={() => confirm({ title: 'Confirm?' })}>Confirm</button>
28
+ * {portal}
29
+ * </>
30
+ * );
31
+ * }
32
+ * ```
33
+ *
34
+ * @example
35
+ * Manual initialization with custom container:
36
+ * ```tsx
37
+ * function CustomModalApp() {
38
+ * const { portal, initialize } = useBootstrap({
39
+ * mode: 'manual',
40
+ * ForegroundComponent: CustomModal,
41
+ * options: {
42
+ * closeOnBackdropClick: false,
43
+ * },
44
+ * });
45
+ *
46
+ * useEffect(() => {
47
+ * const container = document.getElementById('modal-root');
48
+ * if (container) {
49
+ * initialize(container);
50
+ * }
51
+ * }, [initialize]);
52
+ *
53
+ * return (
54
+ * <div>
55
+ * <main>App Content</main>
56
+ * <div id="modal-root" />
57
+ * {portal}
58
+ * </div>
59
+ * );
60
+ * }
61
+ * ```
62
+ *
63
+ * @example
64
+ * Integration with state management:
65
+ * ```tsx
66
+ * function ConnectedApp() {
67
+ * const user = useSelector(selectCurrentUser);
68
+ * const theme = useSelector(selectTheme);
69
+ *
70
+ * const { portal } = useBootstrap({
71
+ * context: { userId: user?.id, theme },
72
+ * ContentComponent: ({ children, context }) => (
73
+ * <div data-theme={context.theme}>
74
+ * {children}
75
+ * </div>
76
+ * ),
77
+ * });
78
+ *
79
+ * const handleDelete = async () => {
80
+ * const confirmed = await confirm({
81
+ * title: 'Delete Account',
82
+ * content: `Are you sure, ${user.name}?`,
83
+ * });
84
+ * if (confirmed) {
85
+ * dispatch(deleteAccount());
86
+ * }
87
+ * };
88
+ *
89
+ * return (
90
+ * <>
91
+ * <button onClick={handleDelete}>Delete Account</button>
92
+ * {portal}
93
+ * </>
94
+ * );
95
+ * }
96
+ * ```
97
+ *
98
+ * @example
99
+ * Multiple modal systems:
100
+ * ```tsx
101
+ * function MultiModalApp() {
102
+ * // System modals (errors, confirmations)
103
+ * const { portal: systemPortal } = useBootstrap({
104
+ * ForegroundComponent: SystemModal,
105
+ * options: { backdrop: 'rgba(255, 0, 0, 0.8)' },
106
+ * });
107
+ *
108
+ * // Feature modals (forms, wizards)
109
+ * const { portal: featurePortal } = useBootstrap({
110
+ * ForegroundComponent: FeatureModal,
111
+ * options: { backdrop: 'rgba(0, 0, 255, 0.8)' },
112
+ * });
113
+ *
114
+ * return (
115
+ * <>
116
+ * <SystemSection />
117
+ * <FeatureSection />
118
+ * {systemPortal}
119
+ * {featurePortal}
120
+ * </>
121
+ * );
122
+ * }
123
+ * ```
124
+ *
125
+ * @example
126
+ * With custom hooks for specific modal types:
127
+ * ```tsx
128
+ * function useConfirmDialog() {
129
+ * const { portal } = useBootstrap({
130
+ * TitleComponent: ({ children }) => (
131
+ * <h3 className="confirm-title">
132
+ * <Icon name="warning" />
133
+ * {children}
134
+ * </h3>
135
+ * ),
136
+ * FooterComponent: ({ onConfirm, onClose }) => (
137
+ * <div className="confirm-footer">
138
+ * <button onClick={onClose}>Cancel</button>
139
+ * <button onClick={onConfirm} className="danger">
140
+ * Confirm
141
+ * </button>
142
+ * </div>
143
+ * ),
144
+ * });
145
+ *
146
+ * const confirmAction = useCallback(
147
+ * (title: string, content: string) => {
148
+ * return confirm({ title, content });
149
+ * },
150
+ * [],
151
+ * );
152
+ *
153
+ * return { portal, confirmAction };
154
+ * }
155
+ *
156
+ * function App() {
157
+ * const { portal, confirmAction } = useConfirmDialog();
158
+ *
159
+ * const handleDelete = async () => {
160
+ * const confirmed = await confirmAction(
161
+ * 'Delete Item',
162
+ * 'This action cannot be undone.',
163
+ * );
164
+ * if (confirmed) {
165
+ * // Perform deletion
166
+ * }
167
+ * };
168
+ *
169
+ * return (
170
+ * <>
171
+ * <button onClick={handleDelete}>Delete</button>
172
+ * {portal}
173
+ * </>
174
+ * );
175
+ * }
176
+ * ```
177
+ *
178
+ * @remarks
179
+ * - Use `mode: 'manual'` when you need to control the initialization timing
180
+ * - The portal element must be rendered in your component tree
181
+ * - Each hook instance creates an independent modal system
182
+ * - Context changes will affect all modals created after the change
183
+ */
2
184
  export declare const useBootstrap: ({ usePathname: useExternalPathname, ForegroundComponent, BackgroundComponent, TitleComponent, SubtitleComponent, ContentComponent, FooterComponent, options, context, mode, }?: BootstrapProviderProps & {
3
185
  mode?: "manual" | "auto";
4
186
  }) => {
@@ -1,12 +1,12 @@
1
1
  import type { ComponentType, ReactNode } from 'react';
2
2
  import type { AlertContentProps, AlertFooterRender, BackgroundComponent, FooterOptions, ForegroundComponent, ModalBackground } from '../../types';
3
- interface AlertProps<B> {
3
+ interface AlertProps<BackgroundValue> {
4
4
  group?: string;
5
5
  subtype?: 'info' | 'success' | 'warning' | 'error';
6
6
  title?: ReactNode;
7
7
  subtitle?: ReactNode;
8
8
  content?: ReactNode | ComponentType<AlertContentProps>;
9
- background?: ModalBackground<B>;
9
+ background?: ModalBackground<BackgroundValue>;
10
10
  footer?: AlertFooterRender | Pick<FooterOptions, 'confirm' | 'hideConfirm'> | false;
11
11
  dimmed?: boolean;
12
12
  manualDestroy?: boolean;
@@ -14,5 +14,146 @@ interface AlertProps<B> {
14
14
  ForegroundComponent?: ForegroundComponent;
15
15
  BackgroundComponent?: BackgroundComponent;
16
16
  }
17
- export declare const alert: <B = any>({ group, subtype, title, subtitle, content, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }: AlertProps<B>) => Promise<void>;
17
+ /**
18
+ * Displays a promise-based alert modal that resolves when the user acknowledges.
19
+ *
20
+ * Creates a modal dialog with a single action button (typically "OK" or "Confirm").
21
+ * The promise resolves when the user clicks the button or closes the modal.
22
+ *
23
+ * @typeParam BackgroundValue - Type of background data passed to BackgroundComponent
24
+ * @param props - Alert configuration options
25
+ * @returns Promise that resolves when the alert is dismissed
26
+ *
27
+ * @example
28
+ * Basic alert:
29
+ * ```tsx
30
+ * await alert({
31
+ * title: 'Success!',
32
+ * content: 'Your changes have been saved.',
33
+ * });
34
+ * console.log('Alert closed');
35
+ * ```
36
+ *
37
+ * @example
38
+ * Alert with subtype styling:
39
+ * ```tsx
40
+ * // Error alert
41
+ * alert({
42
+ * subtype: 'error',
43
+ * title: 'Operation Failed',
44
+ * content: 'Unable to connect to server. Please try again later.',
45
+ * });
46
+ *
47
+ * // Success alert
48
+ * alert({
49
+ * subtype: 'success',
50
+ * title: 'Upload Complete',
51
+ * content: 'Your file has been successfully uploaded.',
52
+ * });
53
+ *
54
+ * // Warning alert
55
+ * alert({
56
+ * subtype: 'warning',
57
+ * title: 'Storage Almost Full',
58
+ * content: 'You have used 90% of your storage quota.',
59
+ * });
60
+ * ```
61
+ *
62
+ * @example
63
+ * Custom content component:
64
+ * ```tsx
65
+ * const ErrorDetails = ({ onConfirm }) => (
66
+ * <div className="error-details">
67
+ * <p>Error Code: 500</p>
68
+ * <p>Time: {new Date().toLocaleString()}</p>
69
+ * <details>
70
+ * <summary>Stack Trace</summary>
71
+ * <pre>{error.stack}</pre>
72
+ * </details>
73
+ * </div>
74
+ * );
75
+ *
76
+ * alert({
77
+ * title: 'Application Error',
78
+ * content: ErrorDetails,
79
+ * subtype: 'error',
80
+ * });
81
+ * ```
82
+ *
83
+ * @example
84
+ * Custom footer:
85
+ * ```tsx
86
+ * alert({
87
+ * title: 'Terms Updated',
88
+ * content: 'Our terms of service have been updated.',
89
+ * footer: ({ onConfirm, context }) => (
90
+ * <div className="custom-footer">
91
+ * <a href="/terms" target="_blank">Read Terms</a>
92
+ * <button onClick={onConfirm}>I Understand</button>
93
+ * </div>
94
+ * ),
95
+ * });
96
+ * ```
97
+ *
98
+ * @example
99
+ * Alert with background animation:
100
+ * ```tsx
101
+ * alert({
102
+ * title: 'Achievement Unlocked!',
103
+ * content: 'You completed your first task!',
104
+ * background: {
105
+ * data: { type: 'confetti', color: 'gold' },
106
+ * },
107
+ * BackgroundComponent: ({ background, visible }) => (
108
+ * <ConfettiAnimation
109
+ * active={visible}
110
+ * color={background.data.color}
111
+ * />
112
+ * ),
113
+ * });
114
+ * ```
115
+ *
116
+ * @example
117
+ * Non-closeable critical alert:
118
+ * ```tsx
119
+ * alert({
120
+ * title: 'System Maintenance',
121
+ * content: 'The system will restart in 5 minutes. Please save your work.',
122
+ * closeOnBackdropClick: false,
123
+ * footer: {
124
+ * confirm: 'Got it',
125
+ * },
126
+ * manualDestroy: true,
127
+ * });
128
+ * ```
129
+ *
130
+ * @example
131
+ * Chaining alerts:
132
+ * ```tsx
133
+ * async function showWelcomeTour() {
134
+ * await alert({
135
+ * title: 'Welcome!',
136
+ * content: 'Let\'s take a quick tour of the app.',
137
+ * });
138
+ *
139
+ * await alert({
140
+ * title: 'Dashboard',
141
+ * content: 'This is where you\'ll see your daily summary.',
142
+ * });
143
+ *
144
+ * await alert({
145
+ * title: 'Get Started',
146
+ * content: 'You\'re all set! Start exploring the app.',
147
+ * subtype: 'success',
148
+ * });
149
+ * }
150
+ * ```
151
+ *
152
+ * @remarks
153
+ * - The promise always resolves (never rejects) unless an error occurs during modal creation
154
+ * - Use `manualDestroy: true` to keep the modal in DOM after closing (for animations)
155
+ * - Set `closeOnBackdropClick: false` to prevent closing by clicking outside
156
+ * - The `group` prop can be used to manage multiple related modals
157
+ */
158
+ export declare const alert: <BackgroundValue = any>({ group, subtype, title, subtitle, content, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }: AlertProps<BackgroundValue>) => Promise<void>;
18
159
  export {};
@@ -1,12 +1,12 @@
1
1
  import type { ComponentType, ReactNode } from 'react';
2
2
  import type { BackgroundComponent, ConfirmContentProps, ConfirmFooterRender, FooterOptions, ForegroundComponent, ModalBackground } from '../../types';
3
- interface ConfirmProps<B> {
3
+ interface ConfirmProps<BackgroundValue> {
4
4
  group?: string;
5
5
  subtype?: 'info' | 'success' | 'warning' | 'error';
6
6
  title?: ReactNode;
7
7
  subtitle?: ReactNode;
8
8
  content?: ReactNode | ComponentType<ConfirmContentProps>;
9
- background?: ModalBackground<B>;
9
+ background?: ModalBackground<BackgroundValue>;
10
10
  footer?: ConfirmFooterRender | FooterOptions | false;
11
11
  dimmed?: boolean;
12
12
  manualDestroy?: boolean;
@@ -14,5 +14,152 @@ interface ConfirmProps<B> {
14
14
  ForegroundComponent?: ForegroundComponent;
15
15
  BackgroundComponent?: BackgroundComponent;
16
16
  }
17
- export declare const confirm: <B = any>({ group, subtype, title, subtitle, content, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }: ConfirmProps<B>) => Promise<boolean>;
17
+ /**
18
+ * Displays a promise-based confirmation modal that resolves with a boolean result.
19
+ *
20
+ * Creates a modal dialog with two action buttons (typically "OK/Cancel" or "Yes/No").
21
+ * The promise resolves with `true` when confirmed, `false` when cancelled.
22
+ *
23
+ * @typeParam BackgroundValue - Type of background data passed to BackgroundComponent
24
+ * @param props - Confirmation dialog configuration options
25
+ * @returns Promise that resolves to true if confirmed, false if cancelled
26
+ *
27
+ * @example
28
+ * Basic confirmation:
29
+ * ```tsx
30
+ * const shouldDelete = await confirm({
31
+ * title: 'Delete Item?',
32
+ * content: 'This action cannot be undone.',
33
+ * });
34
+ *
35
+ * if (shouldDelete) {
36
+ * await deleteItem();
37
+ * }
38
+ * ```
39
+ *
40
+ * @example
41
+ * Destructive action with warning:
42
+ * ```tsx
43
+ * const shouldProceed = await confirm({
44
+ * subtype: 'error',
45
+ * title: 'Delete Account',
46
+ * content: 'Are you sure you want to delete your account? All data will be permanently lost.',
47
+ * footer: {
48
+ * confirm: 'Delete Account',
49
+ * cancel: 'Keep Account',
50
+ * confirmDanger: true,
51
+ * },
52
+ * });
53
+ * ```
54
+ *
55
+ * @example
56
+ * Custom content with details:
57
+ * ```tsx
58
+ * const ConfirmDetails = ({ onConfirm, onCancel }) => (
59
+ * <div>
60
+ * <p>The following items will be affected:</p>
61
+ * <ul>
62
+ * <li>15 documents</li>
63
+ * <li>42 images</li>
64
+ * <li>3 videos</li>
65
+ * </ul>
66
+ * <p>Total size: 1.2 GB</p>
67
+ * </div>
68
+ * );
69
+ *
70
+ * const confirmed = await confirm({
71
+ * title: 'Move to Trash?',
72
+ * content: ConfirmDetails,
73
+ * });
74
+ * ```
75
+ *
76
+ * @example
77
+ * Custom footer with additional actions:
78
+ * ```tsx
79
+ * confirm({
80
+ * title: 'Save Changes?',
81
+ * content: 'You have unsaved changes.',
82
+ * footer: ({ onConfirm, onCancel, context }) => (
83
+ * <div className="save-dialog-footer">
84
+ * <button onClick={onCancel}>Don\'t Save</button>
85
+ * <button onClick={() => {
86
+ * saveAsDraft();
87
+ * onCancel();
88
+ * }}>Save as Draft</button>
89
+ * <button onClick={onConfirm} className="primary">
90
+ * Save and Publish
91
+ * </button>
92
+ * </div>
93
+ * ),
94
+ * });
95
+ * ```
96
+ *
97
+ * @example
98
+ * Conditional confirmation flow:
99
+ * ```tsx
100
+ * async function deleteWithConfirmation(item) {
101
+ * // First confirmation
102
+ * const confirmDelete = await confirm({
103
+ * title: `Delete "${item.name}"?`,
104
+ * content: 'This item will be moved to trash.',
105
+ * });
106
+ *
107
+ * if (!confirmDelete) return;
108
+ *
109
+ * // Check if item has dependencies
110
+ * if (item.dependencies.length > 0) {
111
+ * const confirmForce = await confirm({
112
+ * subtype: 'warning',
113
+ * title: 'Item has dependencies',
114
+ * content: `${item.dependencies.length} other items depend on this. Delete anyway?`,
115
+ * footer: {
116
+ * confirm: 'Force Delete',
117
+ * confirmDanger: true,
118
+ * },
119
+ * });
120
+ *
121
+ * if (!confirmForce) return;
122
+ * }
123
+ *
124
+ * await deleteItem(item.id);
125
+ * }
126
+ * ```
127
+ *
128
+ * @example
129
+ * With loading state:
130
+ * ```tsx
131
+ * const shouldExport = await confirm({
132
+ * title: 'Export Data',
133
+ * content: 'Export may take several minutes for large datasets.',
134
+ * footer: ({ onConfirm, onCancel }) => {
135
+ * const [loading, setLoading] = useState(false);
136
+ *
137
+ * const handleExport = async () => {
138
+ * setLoading(true);
139
+ * await startExport();
140
+ * onConfirm();
141
+ * };
142
+ *
143
+ * return (
144
+ * <>
145
+ * <button onClick={onCancel}>Cancel</button>
146
+ * <button
147
+ * onClick={handleExport}
148
+ * disabled={loading}
149
+ * >
150
+ * {loading ? 'Exporting...' : 'Start Export'}
151
+ * </button>
152
+ * </>
153
+ * );
154
+ * },
155
+ * });
156
+ * ```
157
+ *
158
+ * @remarks
159
+ * - Returns `true` only when explicitly confirmed via the confirm action
160
+ * - Returns `false` for cancel action or backdrop click (if enabled)
161
+ * - Use `subtype` to indicate severity (error for destructive actions)
162
+ * - The `footer` prop allows complete customization of button layout and behavior
163
+ */
164
+ export declare const confirm: <BackgroundValue = any>({ group, subtype, title, subtitle, content, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }: ConfirmProps<BackgroundValue>) => Promise<boolean>;
18
165
  export {};