@lerx/promise-modal 0.0.59 → 0.0.61
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 +1197 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -10,34 +10,1221 @@
|
|
|
10
10
|
|
|
11
11
|
`@lerx/promise-modal` is a universal modal utility based on React.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Key features include:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
- Can be used even in places not included in React components
|
|
16
|
+
- After opening a modal, you can retrieve the result as a promise
|
|
17
|
+
- Supports various modal types (alert, confirm, prompt)
|
|
18
|
+
- Highly customizable component structure
|
|
16
19
|
|
|
17
20
|
---
|
|
18
21
|
|
|
19
|
-
##
|
|
22
|
+
## Installation
|
|
20
23
|
|
|
21
24
|
```bash
|
|
22
25
|
yarn add @lerx/promise-modal
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
or
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @lerx/promise-modal
|
|
32
|
+
```
|
|
26
33
|
|
|
27
34
|
---
|
|
28
35
|
|
|
29
|
-
##
|
|
36
|
+
## How to Use
|
|
37
|
+
|
|
38
|
+
### 1. Setting up the Modal Provider
|
|
39
|
+
|
|
40
|
+
Install `ModalProvider` at the root of your application:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ModalProvider } from '@lerx/promise-modal';
|
|
44
|
+
|
|
45
|
+
function App() {
|
|
46
|
+
return (
|
|
47
|
+
<ModalProvider>
|
|
48
|
+
<YourApp />
|
|
49
|
+
</ModalProvider>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You can also apply custom options and components:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { ModalProvider } from '@lerx/promise-modal';
|
|
58
|
+
|
|
59
|
+
import {
|
|
60
|
+
CustomBackground,
|
|
61
|
+
CustomContent,
|
|
62
|
+
CustomFooter,
|
|
63
|
+
CustomForeground,
|
|
64
|
+
CustomSubtitle,
|
|
65
|
+
CustomTitle,
|
|
66
|
+
} from './components';
|
|
67
|
+
|
|
68
|
+
function App() {
|
|
69
|
+
return (
|
|
70
|
+
<ModalProvider
|
|
71
|
+
ForegroundComponent={CustomForeground}
|
|
72
|
+
BackgroundComponent={CustomBackground}
|
|
73
|
+
TitleComponent={CustomTitle}
|
|
74
|
+
SubtitleComponent={CustomSubtitle}
|
|
75
|
+
ContentComponent={CustomContent}
|
|
76
|
+
FooterComponent={CustomFooter}
|
|
77
|
+
options={{
|
|
78
|
+
duration: '250ms', // Animation duration
|
|
79
|
+
backdrop: 'rgba(0, 0, 0, 0.35)', // Background overlay color
|
|
80
|
+
manualDestroy: false, // Default auto-destroy behavior
|
|
81
|
+
closeOnBackdropClick: true, // Default backdrop click behavior
|
|
82
|
+
}}
|
|
83
|
+
context={{
|
|
84
|
+
// Context values accessible in all modal components
|
|
85
|
+
theme: 'light',
|
|
86
|
+
locale: 'en-US',
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<YourApp />
|
|
90
|
+
</ModalProvider>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2. Using Basic Modals
|
|
96
|
+
|
|
97
|
+
#### Alert Modal
|
|
98
|
+
|
|
99
|
+
Alert modals provide simple information display with a confirmation button.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { alert } from '@lerx/promise-modal';
|
|
103
|
+
|
|
104
|
+
// Basic usage
|
|
105
|
+
async function showAlert() {
|
|
106
|
+
await alert({
|
|
107
|
+
title: 'Notification',
|
|
108
|
+
content: 'The task has been completed.',
|
|
109
|
+
});
|
|
110
|
+
console.log('User closed the modal.');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Using various options
|
|
114
|
+
async function showDetailedAlert() {
|
|
115
|
+
await alert({
|
|
116
|
+
subtype: 'success', // 'info' | 'success' | 'warning' | 'error'
|
|
117
|
+
title: 'Success',
|
|
118
|
+
subtitle: 'Details',
|
|
119
|
+
content: 'The task has been successfully completed.',
|
|
120
|
+
dimmed: true, // Dim the background
|
|
121
|
+
closeOnBackdropClick: true, // Close on backdrop click
|
|
122
|
+
// If use close animation, you need to set manualDestroy to true
|
|
123
|
+
manualDestroy: false, // Auto-destroy (false: auto, true: manual)
|
|
124
|
+
// Data to pass to the background
|
|
125
|
+
background: {
|
|
126
|
+
data: 'custom-data',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Confirm Modal
|
|
133
|
+
|
|
134
|
+
Confirm modals are used for actions that require user confirmation.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { confirm } from '@lerx/promise-modal';
|
|
138
|
+
|
|
139
|
+
async function showConfirm() {
|
|
140
|
+
const result = await confirm({
|
|
141
|
+
title: 'Confirm',
|
|
142
|
+
content: 'Are you sure you want to delete this?',
|
|
143
|
+
// Custom footer text
|
|
144
|
+
footer: {
|
|
145
|
+
confirm: 'Delete',
|
|
146
|
+
cancel: 'Cancel',
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (result) {
|
|
151
|
+
console.log('User clicked confirm.');
|
|
152
|
+
// Execute delete logic
|
|
153
|
+
} else {
|
|
154
|
+
console.log('User clicked cancel.');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Prompt Modal
|
|
160
|
+
|
|
161
|
+
Prompt modals are used to receive input from users.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { prompt } from '@lerx/promise-modal';
|
|
165
|
+
|
|
166
|
+
async function showPrompt() {
|
|
167
|
+
// Text input
|
|
168
|
+
const name = await prompt<string>({
|
|
169
|
+
title: 'Enter Name',
|
|
170
|
+
content: 'Please enter your name.',
|
|
171
|
+
defaultValue: '', // Default value
|
|
172
|
+
Input: ({ value, onChange }) => {
|
|
173
|
+
// Important: value is the current value, onChange is the function to update the value
|
|
174
|
+
return (
|
|
175
|
+
<input
|
|
176
|
+
value={value}
|
|
177
|
+
onChange={(e) => onChange(e.target.value)}
|
|
178
|
+
placeholder="Enter name"
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
// Validate input to control the confirmation button
|
|
183
|
+
disabled: (value) => value.length < 2,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
console.log('Entered name:', name);
|
|
187
|
+
|
|
188
|
+
// Complex data input
|
|
189
|
+
const userInfo = await prompt<{ name: string; age: number }>({
|
|
190
|
+
title: 'User Information',
|
|
191
|
+
defaultValue: { name: '', age: 0 },
|
|
192
|
+
Input: ({ value, onChange }) => (
|
|
193
|
+
<div>
|
|
194
|
+
<input
|
|
195
|
+
value={value.name}
|
|
196
|
+
onChange={(e) => onChange({ ...value, name: e.target.value })}
|
|
197
|
+
placeholder="Name"
|
|
198
|
+
/>
|
|
199
|
+
<input
|
|
200
|
+
type="number"
|
|
201
|
+
value={value.age}
|
|
202
|
+
onChange={(e) => onChange({ ...value, age: Number(e.target.value) })}
|
|
203
|
+
placeholder="Age"
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
console.log('User info:', userInfo);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 3. Using Custom Components
|
|
214
|
+
|
|
215
|
+
You can customize the appearance and behavior of modals:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { css } from '@emotion/css';
|
|
219
|
+
import { ModalProvider, alert, confirm } from '@lerx/promise-modal';
|
|
220
|
+
|
|
221
|
+
// Custom foreground component
|
|
222
|
+
const CustomForegroundComponent = ({ children, type, ...props }) => {
|
|
223
|
+
return (
|
|
224
|
+
<div
|
|
225
|
+
className={css`
|
|
226
|
+
background-color: #ffffff;
|
|
227
|
+
border-radius: 12px;
|
|
228
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
|
|
229
|
+
width: 90%;
|
|
230
|
+
max-width: 500px;
|
|
231
|
+
padding: 24px;
|
|
232
|
+
position: relative;
|
|
233
|
+
`}
|
|
234
|
+
>
|
|
235
|
+
{children}
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Custom background component (utilizing background data)
|
|
241
|
+
const CustomBackgroundComponent = ({ children, onClick, background }) => {
|
|
242
|
+
// Apply different styles based on background data
|
|
243
|
+
const getBgColor = () => {
|
|
244
|
+
if (background?.data === 'alert') return 'rgba(0, 0, 0, 0.7)';
|
|
245
|
+
if (background?.data === 'confirm') return 'rgba(0, 0, 0, 0.5)';
|
|
246
|
+
if (background?.data === 'prompt') return 'rgba(0, 0, 0, 0.6)';
|
|
247
|
+
return 'rgba(0, 0, 0, 0.4)';
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div
|
|
252
|
+
className={css`
|
|
253
|
+
position: fixed;
|
|
254
|
+
top: 0;
|
|
255
|
+
left: 0;
|
|
256
|
+
right: 0;
|
|
257
|
+
bottom: 0;
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: center;
|
|
261
|
+
background-color: ${getBgColor()};
|
|
262
|
+
z-index: 1000;
|
|
263
|
+
`}
|
|
264
|
+
onClick={onClick}
|
|
265
|
+
>
|
|
266
|
+
{children}
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Custom title component
|
|
272
|
+
const CustomTitleComponent = ({ children, context }) => {
|
|
273
|
+
return (
|
|
274
|
+
<h2
|
|
275
|
+
className={css`
|
|
276
|
+
font-size: 24px;
|
|
277
|
+
margin-bottom: 8px;
|
|
278
|
+
color: ${context?.theme === 'dark' ? '#ffffff' : '#333333'};
|
|
279
|
+
`}
|
|
280
|
+
>
|
|
281
|
+
{children}
|
|
282
|
+
</h2>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// Custom subtitle component
|
|
287
|
+
const CustomSubtitleComponent = ({ children, context }) => {
|
|
288
|
+
return (
|
|
289
|
+
<h3
|
|
290
|
+
className={css`
|
|
291
|
+
font-size: 16px;
|
|
292
|
+
margin-bottom: 16px;
|
|
293
|
+
color: ${context?.theme === 'dark' ? '#cccccc' : '#666666'};
|
|
294
|
+
`}
|
|
295
|
+
>
|
|
296
|
+
{children}
|
|
297
|
+
</h3>
|
|
298
|
+
);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// Custom content component
|
|
302
|
+
const CustomContentComponent = ({ children, context }) => {
|
|
303
|
+
return (
|
|
304
|
+
<div
|
|
305
|
+
className={css`
|
|
306
|
+
margin-bottom: 24px;
|
|
307
|
+
color: ${context?.theme === 'dark' ? '#eeeeee' : '#444444'};
|
|
308
|
+
`}
|
|
309
|
+
>
|
|
310
|
+
{children}
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Custom footer component
|
|
316
|
+
const CustomFooterComponent = ({
|
|
317
|
+
onConfirm,
|
|
318
|
+
onClose,
|
|
319
|
+
onCancel,
|
|
320
|
+
type,
|
|
321
|
+
disabled,
|
|
322
|
+
}) => {
|
|
323
|
+
return (
|
|
324
|
+
<div
|
|
325
|
+
className={css`
|
|
326
|
+
display: flex;
|
|
327
|
+
justify-content: flex-end;
|
|
328
|
+
gap: 12px;
|
|
329
|
+
margin-top: 24px;
|
|
330
|
+
`}
|
|
331
|
+
>
|
|
332
|
+
{/* Display cancel button for confirm and prompt modals */}
|
|
333
|
+
{(type === 'confirm' || type === 'prompt') && (
|
|
334
|
+
<button
|
|
335
|
+
className={css`
|
|
336
|
+
padding: 8px 16px;
|
|
337
|
+
border: 1px solid #ccc;
|
|
338
|
+
background: none;
|
|
339
|
+
border-radius: 4px;
|
|
340
|
+
cursor: pointer;
|
|
341
|
+
&:hover {
|
|
342
|
+
background-color: #f3f3f3;
|
|
343
|
+
}
|
|
344
|
+
`}
|
|
345
|
+
onClick={type === 'confirm' ? () => onConfirm(false) : onCancel}
|
|
346
|
+
>
|
|
347
|
+
Cancel
|
|
348
|
+
</button>
|
|
349
|
+
)}
|
|
350
|
+
<button
|
|
351
|
+
className={css`
|
|
352
|
+
padding: 8px 16px;
|
|
353
|
+
background-color: #4a90e2;
|
|
354
|
+
color: white;
|
|
355
|
+
border: none;
|
|
356
|
+
border-radius: 4px;
|
|
357
|
+
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
|
358
|
+
opacity: ${disabled ? 0.6 : 1};
|
|
359
|
+
&:hover {
|
|
360
|
+
background-color: ${disabled ? '#4a90e2' : '#357ac7'};
|
|
361
|
+
}
|
|
362
|
+
`}
|
|
363
|
+
onClick={() => onConfirm(type === 'confirm' ? true : undefined)}
|
|
364
|
+
disabled={disabled}
|
|
365
|
+
>
|
|
366
|
+
Confirm
|
|
367
|
+
</button>
|
|
368
|
+
</div>
|
|
369
|
+
);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Global setup in provider
|
|
373
|
+
function App() {
|
|
374
|
+
return (
|
|
375
|
+
<ModalProvider
|
|
376
|
+
ForegroundComponent={CustomForegroundComponent}
|
|
377
|
+
BackgroundComponent={CustomBackgroundComponent}
|
|
378
|
+
TitleComponent={CustomTitleComponent}
|
|
379
|
+
SubtitleComponent={CustomSubtitleComponent}
|
|
380
|
+
ContentComponent={CustomContentComponent}
|
|
381
|
+
FooterComponent={CustomFooterComponent}
|
|
382
|
+
options={{
|
|
383
|
+
duration: 300, // Animation duration (ms)
|
|
384
|
+
}}
|
|
385
|
+
context={{
|
|
386
|
+
// Context accessible in all modals
|
|
387
|
+
theme: 'light',
|
|
388
|
+
locale: 'en-US',
|
|
389
|
+
}}
|
|
390
|
+
>
|
|
391
|
+
<YourApp />
|
|
392
|
+
</ModalProvider>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Applying custom components to specific modals
|
|
397
|
+
async function showCustomAlert() {
|
|
398
|
+
await alert({
|
|
399
|
+
title: 'Notification',
|
|
400
|
+
content: 'Content',
|
|
401
|
+
// Custom component only for this modal
|
|
402
|
+
ForegroundComponent: ({ children }) => (
|
|
403
|
+
<div
|
|
404
|
+
className={css`
|
|
405
|
+
background-color: #f0f8ff;
|
|
406
|
+
padding: 30px;
|
|
407
|
+
border-radius: 16px;
|
|
408
|
+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
|
|
409
|
+
`}
|
|
410
|
+
>
|
|
411
|
+
{children}
|
|
412
|
+
</div>
|
|
413
|
+
),
|
|
414
|
+
// Pass background data
|
|
415
|
+
background: {
|
|
416
|
+
data: 'custom-alert',
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### 4. Using Custom Anchors and Initialization
|
|
423
|
+
|
|
424
|
+
You can specify the DOM element where modals will be rendered:
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
import { useEffect, useRef } from 'react';
|
|
428
|
+
|
|
429
|
+
import {
|
|
430
|
+
ModalProvider,
|
|
431
|
+
ModalProviderHandle,
|
|
432
|
+
alert,
|
|
433
|
+
useInitializeModal,
|
|
434
|
+
} from '@lerx/promise-modal';
|
|
30
435
|
|
|
31
|
-
|
|
436
|
+
// Using refs
|
|
437
|
+
function CustomAnchorExample() {
|
|
438
|
+
// Modal provider handle reference
|
|
439
|
+
const modalProviderRef = useRef<ModalProviderHandle>(null);
|
|
440
|
+
// Container reference for modal display
|
|
441
|
+
const modalContainerRef = useRef<HTMLDivElement>(null);
|
|
442
|
+
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
// Initialize modal when container is ready
|
|
445
|
+
if (modalContainerRef.current && modalProviderRef.current) {
|
|
446
|
+
modalProviderRef.current.initialize(modalContainerRef.current);
|
|
447
|
+
}
|
|
448
|
+
}, []);
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
<ModalProvider ref={modalProviderRef}>
|
|
452
|
+
<div>
|
|
453
|
+
{/* Modals will render inside this div */}
|
|
454
|
+
<div
|
|
455
|
+
ref={modalContainerRef}
|
|
456
|
+
style={{
|
|
457
|
+
backgroundColor: '#f0f0f0',
|
|
458
|
+
width: '100%',
|
|
459
|
+
height: '500px',
|
|
460
|
+
position: 'relative',
|
|
461
|
+
overflow: 'hidden',
|
|
462
|
+
}}
|
|
463
|
+
/>
|
|
464
|
+
|
|
465
|
+
<button
|
|
466
|
+
onClick={() =>
|
|
467
|
+
alert({ title: 'Notice', content: 'Displayed in custom anchor.' })
|
|
468
|
+
}
|
|
469
|
+
>
|
|
470
|
+
Show Modal
|
|
471
|
+
</button>
|
|
472
|
+
</div>
|
|
473
|
+
</ModalProvider>
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Using the useInitializeModal hook
|
|
478
|
+
function CustomAnchorWithHookExample() {
|
|
479
|
+
// useInitializeModal hook (manual mode: manual initialization)
|
|
480
|
+
const { initialize, portal } = useInitializeModal({ mode: 'manual' });
|
|
481
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
482
|
+
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (containerRef.current) {
|
|
485
|
+
// Initialize modal with container element
|
|
486
|
+
initialize(containerRef.current);
|
|
487
|
+
}
|
|
488
|
+
}, [initialize]);
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<div>
|
|
492
|
+
{/* Container for modal rendering */}
|
|
493
|
+
<div
|
|
494
|
+
ref={containerRef}
|
|
495
|
+
style={{
|
|
496
|
+
backgroundColor: '#f0f0f0',
|
|
497
|
+
width: '100%',
|
|
498
|
+
height: '500px',
|
|
499
|
+
}}
|
|
500
|
+
/>
|
|
501
|
+
|
|
502
|
+
<button
|
|
503
|
+
onClick={() =>
|
|
504
|
+
alert({ title: 'Notice', content: 'Displayed in custom anchor.' })
|
|
505
|
+
}
|
|
506
|
+
>
|
|
507
|
+
Show Modal
|
|
508
|
+
</button>
|
|
509
|
+
|
|
510
|
+
{/* Portal rendering in another location (optional) */}
|
|
511
|
+
<div id="another-container">{portal}</div>
|
|
512
|
+
</div>
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### 5. Implementing Toast Messages
|
|
518
|
+
|
|
519
|
+
You can implement toast message functionality using `promise-modal`. This example is based on actual implementation in the project:
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
import React, { type ReactNode, useEffect, useRef } from 'react';
|
|
523
|
+
|
|
524
|
+
import { css } from '@emotion/css';
|
|
525
|
+
import {
|
|
526
|
+
ModalFrameProps,
|
|
527
|
+
alert,
|
|
528
|
+
useDestroyAfter,
|
|
529
|
+
useModalAnimation,
|
|
530
|
+
useModalDuration,
|
|
531
|
+
} from '@lerx/promise-modal';
|
|
532
|
+
|
|
533
|
+
// Toast foreground component definition
|
|
534
|
+
const ToastForeground = ({
|
|
535
|
+
id,
|
|
536
|
+
visible,
|
|
537
|
+
children,
|
|
538
|
+
onClose,
|
|
539
|
+
hideAfterMs = 3000,
|
|
540
|
+
}) => {
|
|
541
|
+
const modalRef = useRef(null);
|
|
542
|
+
const { duration } = useModalDuration();
|
|
543
|
+
|
|
544
|
+
// Auto-close after specified time
|
|
545
|
+
useEffect(() => {
|
|
546
|
+
const timer = setTimeout(onClose, hideAfterMs);
|
|
547
|
+
return () => clearTimeout(timer);
|
|
548
|
+
}, [onClose, hideAfterMs]);
|
|
549
|
+
|
|
550
|
+
// Animation handling
|
|
551
|
+
useModalAnimation(visible, {
|
|
552
|
+
onVisible: () => {
|
|
553
|
+
modalRef.current?.classList.add('visible');
|
|
554
|
+
},
|
|
555
|
+
onHidden: () => {
|
|
556
|
+
modalRef.current?.classList.remove('visible');
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Destroy after closing
|
|
561
|
+
useDestroyAfter(id, duration);
|
|
562
|
+
|
|
563
|
+
return (
|
|
564
|
+
<div
|
|
565
|
+
ref={modalRef}
|
|
566
|
+
className={css`
|
|
567
|
+
position: fixed;
|
|
568
|
+
bottom: 20px;
|
|
569
|
+
left: 50%;
|
|
570
|
+
transform: translateX(-50%) translateY(100px);
|
|
571
|
+
opacity: 0;
|
|
572
|
+
transition:
|
|
573
|
+
transform ${duration}ms,
|
|
574
|
+
opacity ${duration}ms;
|
|
575
|
+
&.visible {
|
|
576
|
+
transform: translateX(-50%) translateY(0);
|
|
577
|
+
opacity: 1;
|
|
578
|
+
}
|
|
579
|
+
`}
|
|
580
|
+
>
|
|
581
|
+
{children}
|
|
582
|
+
</div>
|
|
583
|
+
);
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// Toast message interface
|
|
587
|
+
interface ToastProps {
|
|
588
|
+
message: ReactNode;
|
|
589
|
+
duration?: number;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Handler to remove previous toast
|
|
593
|
+
let onDestroyPrevToast: () => void;
|
|
594
|
+
|
|
595
|
+
// Toast display function
|
|
596
|
+
export const toast = ({ message, duration = 1250 }: ToastProps) => {
|
|
597
|
+
// Remove previous toast if exists
|
|
598
|
+
onDestroyPrevToast?.();
|
|
599
|
+
|
|
600
|
+
return alert({
|
|
601
|
+
content: message,
|
|
602
|
+
ForegroundComponent: (props: ModalFrameProps) => {
|
|
603
|
+
// Store destroy function of new toast
|
|
604
|
+
onDestroyPrevToast = props.onDestroy;
|
|
605
|
+
return <ToastForeground {...props} hideAfterMs={duration} />;
|
|
606
|
+
},
|
|
607
|
+
footer: false, // Hide footer
|
|
608
|
+
dimmed: false, // Disable background dim
|
|
609
|
+
closeOnBackdropClick: false, // Disable closing on backdrop click
|
|
610
|
+
});
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// Usage example
|
|
614
|
+
function showToastExample() {
|
|
615
|
+
toast({
|
|
616
|
+
message: (
|
|
617
|
+
<div
|
|
618
|
+
className={css`
|
|
619
|
+
background-color: #333;
|
|
620
|
+
color: white;
|
|
621
|
+
padding: 12px 24px;
|
|
622
|
+
border-radius: 8px;
|
|
623
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
624
|
+
`}
|
|
625
|
+
>
|
|
626
|
+
Task completed!
|
|
627
|
+
</div>
|
|
628
|
+
),
|
|
629
|
+
duration: 2000, // Auto-close after 2 seconds
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### 6. Nested Modal Providers
|
|
635
|
+
|
|
636
|
+
You can use multiple modal providers with different settings within the same application:
|
|
637
|
+
|
|
638
|
+
```tsx
|
|
639
|
+
import { ModalProvider, alert } from '@lerx/promise-modal';
|
|
640
|
+
|
|
641
|
+
function NestedModalProviders() {
|
|
642
|
+
return (
|
|
643
|
+
<ModalProvider
|
|
644
|
+
options={{
|
|
645
|
+
duration: 300,
|
|
646
|
+
backdrop: 'rgba(0, 0, 0, 0.5)',
|
|
647
|
+
}}
|
|
648
|
+
>
|
|
649
|
+
{/* Modal using outer provider settings */}
|
|
650
|
+
<button
|
|
651
|
+
onClick={() =>
|
|
652
|
+
alert({
|
|
653
|
+
title: 'Outer Modal',
|
|
654
|
+
content: 'This uses outer modal provider settings.',
|
|
655
|
+
})
|
|
656
|
+
}
|
|
657
|
+
>
|
|
658
|
+
Open Outer Modal
|
|
659
|
+
</button>
|
|
660
|
+
|
|
661
|
+
{/* Inner provider with different settings */}
|
|
662
|
+
<div className="inner-section">
|
|
663
|
+
<ModalProvider
|
|
664
|
+
SubtitleComponent={() => (
|
|
665
|
+
<div className="custom-subtitle">Inner Modal</div>
|
|
666
|
+
)}
|
|
667
|
+
options={{
|
|
668
|
+
duration: 500,
|
|
669
|
+
backdrop: 'rgba(0, 0, 0, 0.8)',
|
|
670
|
+
}}
|
|
671
|
+
>
|
|
672
|
+
{/* Modal using inner provider settings */}
|
|
673
|
+
<button
|
|
674
|
+
onClick={() =>
|
|
675
|
+
alert({
|
|
676
|
+
title: 'Inner Modal',
|
|
677
|
+
content: 'This uses inner modal provider settings.',
|
|
678
|
+
})
|
|
679
|
+
}
|
|
680
|
+
>
|
|
681
|
+
Open Inner Modal
|
|
682
|
+
</button>
|
|
683
|
+
</ModalProvider>
|
|
684
|
+
</div>
|
|
685
|
+
</ModalProvider>
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### 7. Various Modal Configuration Options
|
|
691
|
+
|
|
692
|
+
Examples utilizing various modal configuration options:
|
|
693
|
+
|
|
694
|
+
```tsx
|
|
695
|
+
import { alert, confirm } from '@lerx/promise-modal';
|
|
696
|
+
|
|
697
|
+
// Basic alert modal
|
|
698
|
+
async function showBasicAlert() {
|
|
699
|
+
await alert({
|
|
700
|
+
title: 'Basic Notification',
|
|
701
|
+
content: 'This is an alert modal with default settings.',
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Modal settings by type
|
|
706
|
+
async function showModalByType() {
|
|
707
|
+
// Success notification
|
|
708
|
+
await alert({
|
|
709
|
+
title: 'Success',
|
|
710
|
+
content: 'Task completed successfully.',
|
|
711
|
+
subtype: 'success',
|
|
712
|
+
dimmed: true,
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// Warning confirmation
|
|
716
|
+
const result = await confirm({
|
|
717
|
+
title: 'Warning',
|
|
718
|
+
content: 'This action cannot be undone. Continue?',
|
|
719
|
+
subtype: 'warning',
|
|
720
|
+
closeOnBackdropClick: false, // Prevent closing by backdrop click
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
// Error notification
|
|
724
|
+
await alert({
|
|
725
|
+
title: 'Error',
|
|
726
|
+
content: 'Unable to complete the task.',
|
|
727
|
+
subtype: 'error',
|
|
728
|
+
// Custom footer text
|
|
729
|
+
footer: {
|
|
730
|
+
confirm: 'I understand',
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Using manual destroy mode
|
|
736
|
+
async function showManualDestroyModal() {
|
|
737
|
+
await alert({
|
|
738
|
+
title: 'Notification',
|
|
739
|
+
content:
|
|
740
|
+
"This modal won't close on backdrop click and requires confirmation button to close.",
|
|
741
|
+
manualDestroy: true, // Enable manual destroy mode
|
|
742
|
+
closeOnBackdropClick: false, // Prevent closing by backdrop click
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Using background data
|
|
747
|
+
async function showModalWithBackground() {
|
|
748
|
+
await alert({
|
|
749
|
+
title: 'Background Data Usage',
|
|
750
|
+
content: 'This modal passes data to the background component.',
|
|
751
|
+
background: {
|
|
752
|
+
data: 'custom-background-data',
|
|
753
|
+
opacity: 0.8,
|
|
754
|
+
blur: '5px',
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
```
|
|
32
759
|
|
|
33
760
|
---
|
|
34
761
|
|
|
35
|
-
##
|
|
762
|
+
## API Reference
|
|
763
|
+
|
|
764
|
+
### Core Functions
|
|
765
|
+
|
|
766
|
+
#### `alert(options)`
|
|
767
|
+
|
|
768
|
+
Opens a simple alert modal to display information to the user.
|
|
769
|
+
|
|
770
|
+
**Parameters:**
|
|
771
|
+
|
|
772
|
+
- `options`: Alert modal configuration object
|
|
773
|
+
- `title?`: Modal title (ReactNode)
|
|
774
|
+
- `subtitle?`: Subtitle (ReactNode)
|
|
775
|
+
- `content?`: Modal content (ReactNode or component)
|
|
776
|
+
- `subtype?`: Modal type ('info' | 'success' | 'warning' | 'error')
|
|
777
|
+
- `background?`: Background settings (ModalBackground object)
|
|
778
|
+
- `footer?`: Footer settings
|
|
779
|
+
- Function: `(props: FooterComponentProps) => ReactNode`
|
|
780
|
+
- Object: `{ confirm?: string; hideConfirm?: boolean }`
|
|
781
|
+
- `false`: Hide footer
|
|
782
|
+
- `dimmed?`: Whether to dim the background (boolean)
|
|
783
|
+
- `manualDestroy?`: Enable manual destroy mode (boolean)
|
|
784
|
+
- `closeOnBackdropClick?`: Whether to close on backdrop click (boolean)
|
|
785
|
+
- `ForegroundComponent?`: Custom foreground component
|
|
786
|
+
- `BackgroundComponent?`: Custom background component
|
|
787
|
+
|
|
788
|
+
**Returns:** `Promise<void>` - Resolved when the modal is closed
|
|
789
|
+
|
|
790
|
+
```typescript
|
|
791
|
+
// Example
|
|
792
|
+
await alert({
|
|
793
|
+
title: 'Notification',
|
|
794
|
+
content: 'Content',
|
|
795
|
+
subtype: 'info',
|
|
796
|
+
closeOnBackdropClick: true,
|
|
797
|
+
});
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
#### `confirm(options)`
|
|
801
|
+
|
|
802
|
+
Opens a confirmation modal that requests user confirmation.
|
|
803
|
+
|
|
804
|
+
**Parameters:**
|
|
36
805
|
|
|
37
|
-
|
|
806
|
+
- `options`: Confirm modal configuration
|
|
807
|
+
- Same options as `alert`, plus:
|
|
808
|
+
- `footer?`: Footer settings
|
|
809
|
+
- Function: `(props: FooterComponentProps) => ReactNode`
|
|
810
|
+
- Object: `{ confirm?: string; cancel?: string; hideConfirm?: boolean; hideCancel?: boolean }`
|
|
811
|
+
- `false`: Hide footer
|
|
812
|
+
|
|
813
|
+
**Returns:** `Promise<boolean>` - Resolves to true if confirmed, false if canceled
|
|
814
|
+
|
|
815
|
+
```typescript
|
|
816
|
+
// Example
|
|
817
|
+
const result = await confirm({
|
|
818
|
+
title: 'Confirm',
|
|
819
|
+
content: 'Do you want to proceed?',
|
|
820
|
+
footer: {
|
|
821
|
+
confirm: 'Confirm',
|
|
822
|
+
cancel: 'Cancel',
|
|
823
|
+
},
|
|
824
|
+
});
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
#### `prompt<T>(options)`
|
|
828
|
+
|
|
829
|
+
Opens a prompt modal to receive input from the user.
|
|
830
|
+
|
|
831
|
+
**Parameters:**
|
|
832
|
+
|
|
833
|
+
- `options`: Prompt modal configuration object
|
|
834
|
+
- Same options as `alert`, plus:
|
|
835
|
+
- `Input`: Function to render input field
|
|
836
|
+
- `(props: PromptInputProps<T>) => ReactNode`
|
|
837
|
+
- props: `{ value: T; onChange: (value: T) => void }`
|
|
838
|
+
- `defaultValue?`: Default value (T)
|
|
839
|
+
- `disabled?`: Function to determine if confirm button should be disabled
|
|
840
|
+
- `(value: T) => boolean`
|
|
841
|
+
- `returnOnCancel?`: Whether to return default value on cancel (boolean)
|
|
842
|
+
- `footer?`: Footer settings (similar to confirm)
|
|
843
|
+
|
|
844
|
+
**Returns:** `Promise<T>` - Resolves to the input value
|
|
845
|
+
|
|
846
|
+
```typescript
|
|
847
|
+
// Example
|
|
848
|
+
const value = await prompt<string>({
|
|
849
|
+
title: 'Input',
|
|
850
|
+
defaultValue: '',
|
|
851
|
+
Input: ({ value, onChange }) => (
|
|
852
|
+
<input
|
|
853
|
+
value={value}
|
|
854
|
+
onChange={(e) => onChange(e.target.value)}
|
|
855
|
+
/>
|
|
856
|
+
),
|
|
857
|
+
disabled: (value) => value.trim() === '',
|
|
858
|
+
});
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### Components
|
|
862
|
+
|
|
863
|
+
#### `ModalProvider` (or `BootstrapProvider`)
|
|
864
|
+
|
|
865
|
+
Component that initializes and provides the modal service.
|
|
866
|
+
|
|
867
|
+
**Props:**
|
|
868
|
+
|
|
869
|
+
- `ForegroundComponent?`: Custom foreground component
|
|
870
|
+
- `(props: WrapperComponentProps) => ReactNode`
|
|
871
|
+
- `BackgroundComponent?`: Custom background component
|
|
872
|
+
- `(props: WrapperComponentProps) => ReactNode`
|
|
873
|
+
- `TitleComponent?`: Custom title component
|
|
874
|
+
- `SubtitleComponent?`: Custom subtitle component
|
|
875
|
+
- `ContentComponent?`: Custom content component
|
|
876
|
+
- `FooterComponent?`: Custom footer component
|
|
877
|
+
- `(props: FooterComponentProps) => ReactNode`
|
|
878
|
+
- `options?`: Global modal options
|
|
879
|
+
- `duration?`: Animation duration (milliseconds)
|
|
880
|
+
- `backdrop?`: Backdrop click handling
|
|
881
|
+
- Other options...
|
|
882
|
+
- `context?`: Context object to pass to modal components
|
|
883
|
+
- `usePathname?`: Custom pathname hook function
|
|
884
|
+
|
|
885
|
+
**Handle:**
|
|
886
|
+
|
|
887
|
+
- `initialize`: Method to manually initialize modal service
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
// Example using ref for control
|
|
891
|
+
import { useRef } from 'react';
|
|
892
|
+
import { ModalProvider, ModalProviderHandle } from '@lerx/promise-modal';
|
|
893
|
+
|
|
894
|
+
function App() {
|
|
895
|
+
const modalProviderRef = useRef<ModalProviderHandle>(null);
|
|
896
|
+
|
|
897
|
+
const handleInitialize = () => {
|
|
898
|
+
modalProviderRef.current?.initialize();
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
return (
|
|
902
|
+
<ModalProvider ref={modalProviderRef}>
|
|
903
|
+
<YourApp />
|
|
904
|
+
</ModalProvider>
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
### Hooks
|
|
910
|
+
|
|
911
|
+
#### `useModalOptions`
|
|
912
|
+
|
|
913
|
+
Read global options for modals.
|
|
914
|
+
|
|
915
|
+
```typescript
|
|
916
|
+
import { useModalOptions } from '@lerx/promise-modal';
|
|
917
|
+
|
|
918
|
+
function Component() {
|
|
919
|
+
// ConfigurationContextProps
|
|
920
|
+
const options = useModalOptions();
|
|
921
|
+
|
|
922
|
+
// ...
|
|
923
|
+
}
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
#### `useModalDuration`
|
|
927
|
+
|
|
928
|
+
Read modal animation duration.
|
|
929
|
+
|
|
930
|
+
```typescript
|
|
931
|
+
import { useModalDuration } from '@lerx/promise-modal';
|
|
932
|
+
|
|
933
|
+
function Component() {
|
|
934
|
+
// duration is 300ms, milliseconds is 300
|
|
935
|
+
const { duration, milliseconds } = useModalDuration();
|
|
936
|
+
// ...
|
|
937
|
+
}
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
#### `useModalBackdrop`
|
|
941
|
+
|
|
942
|
+
Read modal backdrop settings.
|
|
943
|
+
|
|
944
|
+
```typescript
|
|
945
|
+
import { useModalBackdrop } from '@lerx/promise-modal';
|
|
946
|
+
|
|
947
|
+
function Component() {
|
|
948
|
+
// backdrop is Color(#000000~#ffffff or rgba(0,0,0,0.5))
|
|
949
|
+
const backdrop = useModalBackdrop();
|
|
950
|
+
// ...
|
|
951
|
+
}
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
#### `useInitializeModal`
|
|
955
|
+
|
|
956
|
+
Initializes the modal service. Usually called automatically by `ModalProvider`.
|
|
957
|
+
|
|
958
|
+
```typescript
|
|
959
|
+
import { useInitializeModal } from '@lerx/promise-modal';
|
|
960
|
+
|
|
961
|
+
function Component() {
|
|
962
|
+
const { initialize } = useInitializeModal();
|
|
963
|
+
|
|
964
|
+
// Manually initialize if needed
|
|
965
|
+
useEffect(() => {
|
|
966
|
+
initialize();
|
|
967
|
+
}, [initialize]);
|
|
968
|
+
|
|
969
|
+
// ...
|
|
970
|
+
}
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
#### `useSubscribeModal`
|
|
974
|
+
|
|
975
|
+
Subscribes to modal state changes.
|
|
976
|
+
|
|
977
|
+
```typescript
|
|
978
|
+
import { useSubscribeModal } from '@lerx/promise-modal';
|
|
979
|
+
|
|
980
|
+
function Component({ modal }) {
|
|
981
|
+
// Component rerenders when modal state changes
|
|
982
|
+
const version = useSubscribeModal(modal);
|
|
983
|
+
|
|
984
|
+
// ...
|
|
985
|
+
}
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
#### `useDestroyAfter`
|
|
989
|
+
|
|
990
|
+
Automatically destroys a modal after specified time.
|
|
991
|
+
|
|
992
|
+
```typescript
|
|
993
|
+
import { useDestroyAfter } from '@lerx/promise-modal';
|
|
994
|
+
|
|
995
|
+
function Component({ modalId }) {
|
|
996
|
+
// Auto-close modal after 3 seconds
|
|
997
|
+
useDestroyAfter(modalId, 3000);
|
|
998
|
+
|
|
999
|
+
// ...
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
#### `useActiveModalCount`
|
|
1004
|
+
|
|
1005
|
+
Returns the number of currently active modals.
|
|
1006
|
+
|
|
1007
|
+
```typescript
|
|
1008
|
+
import { useActiveModalCount } from '@lerx/promise-modal';
|
|
1009
|
+
|
|
1010
|
+
function Component() {
|
|
1011
|
+
const count = useActiveModalCount();
|
|
1012
|
+
|
|
1013
|
+
return (
|
|
1014
|
+
<div>
|
|
1015
|
+
Currently open modals: {count}
|
|
1016
|
+
</div>
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
#### `useModalAnimation`
|
|
1022
|
+
|
|
1023
|
+
Provides modal animation state and control.
|
|
1024
|
+
|
|
1025
|
+
```typescript
|
|
1026
|
+
import { useModalAnimation } from '@lerx/promise-modal';
|
|
1027
|
+
|
|
1028
|
+
function Component() {
|
|
1029
|
+
const { isAnimating, animate } = useModalAnimation();
|
|
1030
|
+
|
|
1031
|
+
// ...
|
|
1032
|
+
}
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
### Type Definitions
|
|
1036
|
+
|
|
1037
|
+
The library provides various types for TypeScript compatibility:
|
|
1038
|
+
|
|
1039
|
+
#### `ModalFrameProps`
|
|
1040
|
+
|
|
1041
|
+
Properties passed to the modal frame component.
|
|
1042
|
+
|
|
1043
|
+
```typescript
|
|
1044
|
+
interface ModalFrameProps<Context = any, B = any> {
|
|
1045
|
+
id: number;
|
|
1046
|
+
type: 'alert' | 'confirm' | 'prompt';
|
|
1047
|
+
alive: boolean;
|
|
1048
|
+
visible: boolean;
|
|
1049
|
+
initiator: string;
|
|
1050
|
+
manualDestroy: boolean;
|
|
1051
|
+
closeOnBackdropClick: boolean;
|
|
1052
|
+
background?: ModalBackground<B>;
|
|
1053
|
+
onConfirm: () => void;
|
|
1054
|
+
onClose: () => void;
|
|
1055
|
+
onChange: (value: any) => void;
|
|
1056
|
+
onDestroy: () => void;
|
|
1057
|
+
onChangeOrder: Function;
|
|
1058
|
+
context: Context;
|
|
1059
|
+
}
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
#### `FooterComponentProps`
|
|
1063
|
+
|
|
1064
|
+
Properties passed to the footer component.
|
|
1065
|
+
|
|
1066
|
+
```typescript
|
|
1067
|
+
interface FooterComponentProps {
|
|
1068
|
+
onConfirm: () => void;
|
|
1069
|
+
onClose: () => void;
|
|
1070
|
+
// Other props...
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
#### `PromptInputProps<T>`
|
|
1075
|
+
|
|
1076
|
+
Properties passed to the input component in prompt modals.
|
|
1077
|
+
|
|
1078
|
+
```typescript
|
|
1079
|
+
interface PromptInputProps<T> {
|
|
1080
|
+
value: T;
|
|
1081
|
+
onChange: (value: T) => void;
|
|
1082
|
+
}
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
Additional types provided:
|
|
1086
|
+
|
|
1087
|
+
- `ModalBackground`: Modal background settings type
|
|
1088
|
+
- `AlertContentProps`: Alert modal content component props
|
|
1089
|
+
- `ConfirmContentProps`: Confirm modal content component props
|
|
1090
|
+
- `PromptContentProps`: Prompt modal content component props
|
|
1091
|
+
- `WrapperComponentProps`: Modal wrapper component props
|
|
38
1092
|
|
|
39
1093
|
---
|
|
40
1094
|
|
|
41
|
-
##
|
|
1095
|
+
## Advanced Usage Examples
|
|
1096
|
+
|
|
1097
|
+
### 1. Nested Modals (Opening Modals inside Other Modals)
|
|
1098
|
+
|
|
1099
|
+
You can open modals inside other modals to create complex user interactions.
|
|
1100
|
+
|
|
1101
|
+
```tsx
|
|
1102
|
+
import { alert, confirm, prompt } from '@lerx/promise-modal';
|
|
1103
|
+
|
|
1104
|
+
// Multi-step modal workflow example
|
|
1105
|
+
async function multiStepProcess() {
|
|
1106
|
+
// First modal: confirm to proceed
|
|
1107
|
+
const shouldProceed = await confirm({
|
|
1108
|
+
title: 'Start Task',
|
|
1109
|
+
content: 'This task involves multiple steps. Do you want to continue?',
|
|
1110
|
+
footer: {
|
|
1111
|
+
confirm: 'Proceed',
|
|
1112
|
+
cancel: 'Cancel',
|
|
1113
|
+
},
|
|
1114
|
+
});
|
|
42
1115
|
|
|
43
|
-
|
|
1116
|
+
if (!shouldProceed) return;
|
|
1117
|
+
|
|
1118
|
+
// Second modal: get user input
|
|
1119
|
+
const userName = await prompt<string>({
|
|
1120
|
+
title: 'User Information',
|
|
1121
|
+
content: 'Please enter your name.',
|
|
1122
|
+
defaultValue: '',
|
|
1123
|
+
Input: ({ value, onChange }) => (
|
|
1124
|
+
<input
|
|
1125
|
+
value={value}
|
|
1126
|
+
onChange={(e) => onChange(e.target.value)}
|
|
1127
|
+
placeholder="Name"
|
|
1128
|
+
/>
|
|
1129
|
+
),
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
if (!userName) return;
|
|
1133
|
+
|
|
1134
|
+
// Third modal: final confirmation
|
|
1135
|
+
const confirmed = await confirm({
|
|
1136
|
+
title: 'Final Confirmation',
|
|
1137
|
+
content: `${userName}, are you sure you want to proceed?`,
|
|
1138
|
+
subtype: 'warning',
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
if (confirmed) {
|
|
1142
|
+
// Last modal: completion notification
|
|
1143
|
+
await alert({
|
|
1144
|
+
title: 'Complete',
|
|
1145
|
+
content: `${userName}, the task has been completed successfully.`,
|
|
1146
|
+
subtype: 'success',
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Opening modal from within a footer
|
|
1152
|
+
async function nestedModalInFooter() {
|
|
1153
|
+
const result = await confirm({
|
|
1154
|
+
title: 'Confirmation Required',
|
|
1155
|
+
content: 'Do you want to proceed with this task?',
|
|
1156
|
+
// Custom footer that opens another modal
|
|
1157
|
+
footer: ({ onConfirm, onClose }) => {
|
|
1158
|
+
const handleConfirm = async () => {
|
|
1159
|
+
// Open another modal when confirm button is clicked
|
|
1160
|
+
const isConfirmed = await confirm({
|
|
1161
|
+
title: 'Final Confirmation',
|
|
1162
|
+
content: 'Are you really sure? This action cannot be undone.',
|
|
1163
|
+
closeOnBackdropClick: false,
|
|
1164
|
+
// Apply different design to second modal
|
|
1165
|
+
ForegroundComponent: CustomForegroundComponent,
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
// Process first modal result based on second modal result
|
|
1169
|
+
if (isConfirmed) onConfirm();
|
|
1170
|
+
else onClose();
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
return (
|
|
1174
|
+
<div>
|
|
1175
|
+
<button onClick={onClose}>Cancel</button>
|
|
1176
|
+
<button onClick={handleConfirm}>Next Step</button>
|
|
1177
|
+
</div>
|
|
1178
|
+
);
|
|
1179
|
+
},
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
if (result) {
|
|
1183
|
+
console.log('Proceeding with the task.');
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Opening modal from within a prompt modal
|
|
1188
|
+
async function promptWithNestedModal() {
|
|
1189
|
+
const value = await prompt<{ title: string; description: string }>({
|
|
1190
|
+
title: 'Create Content',
|
|
1191
|
+
defaultValue: { title: '', description: '' },
|
|
1192
|
+
Input: ({ value, onChange }) => {
|
|
1193
|
+
// Show help modal when help button is clicked
|
|
1194
|
+
const showHelp = () => {
|
|
1195
|
+
alert({
|
|
1196
|
+
title: 'Help',
|
|
1197
|
+
content: 'Enter a title and description. Title is required.',
|
|
1198
|
+
});
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
return (
|
|
1202
|
+
<div>
|
|
1203
|
+
<div>
|
|
1204
|
+
<label>Title</label>
|
|
1205
|
+
<input
|
|
1206
|
+
value={value.title}
|
|
1207
|
+
onChange={(e) => onChange({ ...value, title: e.target.value })}
|
|
1208
|
+
/>
|
|
1209
|
+
</div>
|
|
1210
|
+
<div>
|
|
1211
|
+
<label>Description</label>
|
|
1212
|
+
<textarea
|
|
1213
|
+
value={value.description}
|
|
1214
|
+
onChange={(e) =>
|
|
1215
|
+
onChange({ ...value, description: e.target.value })
|
|
1216
|
+
}
|
|
1217
|
+
/>
|
|
1218
|
+
</div>
|
|
1219
|
+
<button type="button" onClick={showHelp}>
|
|
1220
|
+
Help
|
|
1221
|
+
</button>
|
|
1222
|
+
</div>
|
|
1223
|
+
);
|
|
1224
|
+
},
|
|
1225
|
+
disabled: (value) => !value.title,
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
console.log('Input value:', value);
|
|
1229
|
+
}
|
|
1230
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lerx/promise-modal",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.61",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/vincent-kk/albatrion.git",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@emotion/css": "^11.0.0",
|
|
51
|
-
"@winglet/common-utils": "^0.0.
|
|
52
|
-
"@winglet/react-utils": "^0.0.
|
|
51
|
+
"@winglet/common-utils": "^0.0.61",
|
|
52
|
+
"@winglet/react-utils": "^0.0.61"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@babel/core": "^7.26.0",
|