@lerx/promise-modal 0.10.4 → 0.11.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.
- package/docs/claude/commands/guide.md +760 -0
- package/docs/claude/skills/expert/SKILL.md +171 -0
- package/docs/claude/skills/expert/knowledge/advanced-patterns.md +294 -0
- package/docs/claude/skills/expert/knowledge/api-reference.md +175 -0
- package/docs/claude/skills/expert/knowledge/hooks-reference.md +207 -0
- package/docs/claude/skills/expert/knowledge/type-definitions.md +172 -0
- package/docs/en/SPECIFICATION.md +1185 -0
- package/docs/ko/SPECIFICATION.md +1193 -0
- package/package.json +5 -4
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
# Promise Modal Guide Command
|
|
2
|
+
|
|
3
|
+
**Package**: `@lerx/promise-modal`
|
|
4
|
+
**Expert Skill**: `promise-modal-expert` (directory-based skill)
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
|
|
8
|
+
This command provides an interactive Q&A guide for users of the `@lerx/promise-modal` library. When invoked, help users understand and effectively use the library based on their specific questions and use cases.
|
|
9
|
+
|
|
10
|
+
## Activation
|
|
11
|
+
|
|
12
|
+
This command should be used when users:
|
|
13
|
+
- Have questions about `@lerx/promise-modal`
|
|
14
|
+
- Need help implementing modals
|
|
15
|
+
- Want to understand specific API usage
|
|
16
|
+
- Experience library-related issues
|
|
17
|
+
- Need code examples for specific scenarios
|
|
18
|
+
|
|
19
|
+
## Response Strategy
|
|
20
|
+
|
|
21
|
+
### Step 1: Identify Question Category
|
|
22
|
+
|
|
23
|
+
Classify user questions into one of the following:
|
|
24
|
+
|
|
25
|
+
1. **Getting Started** - Installation, setup, basic usage
|
|
26
|
+
2. **API Usage** - Specific function/hook usage
|
|
27
|
+
3. **Customization** - Custom components, styling, theming
|
|
28
|
+
4. **Advanced Patterns** - Toast, nested modals, animations, AbortSignal
|
|
29
|
+
5. **Troubleshooting** - Error resolution, debugging
|
|
30
|
+
|
|
31
|
+
### Step 2: Provide Structured Response
|
|
32
|
+
|
|
33
|
+
Follow this response pattern for each category:
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Category: Getting Started
|
|
38
|
+
|
|
39
|
+
### Related Questions:
|
|
40
|
+
- "How do I install promise-modal?"
|
|
41
|
+
- "How do I set up the Provider?"
|
|
42
|
+
- "What is the basic usage?"
|
|
43
|
+
|
|
44
|
+
### Response Template:
|
|
45
|
+
|
|
46
|
+
```markdown
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
yarn add @lerx/promise-modal
|
|
51
|
+
# or
|
|
52
|
+
npm install @lerx/promise-modal
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Basic Setup
|
|
56
|
+
|
|
57
|
+
1. Wrap your app with ModalProvider:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { ModalProvider } from '@lerx/promise-modal';
|
|
61
|
+
import { useLocation } from 'react-router-dom'; // Optional router integration
|
|
62
|
+
|
|
63
|
+
function App() {
|
|
64
|
+
return (
|
|
65
|
+
<ModalProvider
|
|
66
|
+
usePathname={useLocation} // Router integration (auto-close on path change)
|
|
67
|
+
options={{
|
|
68
|
+
duration: '200ms',
|
|
69
|
+
backdrop: 'rgba(0, 0, 0, 0.35)',
|
|
70
|
+
manualDestroy: true,
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<YourApp />
|
|
74
|
+
</ModalProvider>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
2. Use modal functions anywhere:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { alert, confirm, prompt } from '@lerx/promise-modal';
|
|
83
|
+
|
|
84
|
+
// Simple alert
|
|
85
|
+
await alert({ title: 'Alert', content: 'Hello!' });
|
|
86
|
+
|
|
87
|
+
// Confirmation
|
|
88
|
+
const result = await confirm({ title: 'Confirm', content: 'Continue?' });
|
|
89
|
+
|
|
90
|
+
// User input
|
|
91
|
+
const value = await prompt<string>({
|
|
92
|
+
title: 'Input',
|
|
93
|
+
defaultValue: '',
|
|
94
|
+
Input: ({ value, onChange }) => (
|
|
95
|
+
<input value={value} onChange={(e) => onChange(e.target.value)} />
|
|
96
|
+
),
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Category: API Usage
|
|
104
|
+
|
|
105
|
+
### `alert` Questions:
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
## alert() API
|
|
109
|
+
|
|
110
|
+
Opens a simple notification modal.
|
|
111
|
+
|
|
112
|
+
**Signature:**
|
|
113
|
+
```typescript
|
|
114
|
+
alert(options: AlertProps): Promise<void>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Key Options:**
|
|
118
|
+
- `title`: Modal title (ReactNode)
|
|
119
|
+
- `content`: Modal content (ReactNode or Component)
|
|
120
|
+
- `subtype`: 'info' | 'success' | 'warning' | 'error'
|
|
121
|
+
- `dimmed`: Darken background (boolean)
|
|
122
|
+
- `closeOnBackdropClick`: Close on backdrop click (boolean)
|
|
123
|
+
- `signal`: AbortSignal for modal cancellation
|
|
124
|
+
|
|
125
|
+
**Example:**
|
|
126
|
+
```tsx
|
|
127
|
+
await alert({
|
|
128
|
+
title: 'Success',
|
|
129
|
+
content: 'Task completed!',
|
|
130
|
+
subtype: 'success',
|
|
131
|
+
dimmed: true,
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `confirm` Questions:
|
|
137
|
+
|
|
138
|
+
```markdown
|
|
139
|
+
## confirm() API
|
|
140
|
+
|
|
141
|
+
Opens a confirmation modal with yes/no options.
|
|
142
|
+
|
|
143
|
+
**Signature:**
|
|
144
|
+
```typescript
|
|
145
|
+
confirm(options: ConfirmProps): Promise<boolean>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Return Value:**
|
|
149
|
+
- `true` - User clicked confirm
|
|
150
|
+
- `false` - User clicked cancel or backdrop
|
|
151
|
+
|
|
152
|
+
**Example:**
|
|
153
|
+
```tsx
|
|
154
|
+
const shouldDelete = await confirm({
|
|
155
|
+
title: 'Delete Item',
|
|
156
|
+
content: 'Are you sure you want to delete this?',
|
|
157
|
+
footer: {
|
|
158
|
+
confirm: 'Delete',
|
|
159
|
+
cancel: 'Cancel',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (shouldDelete) {
|
|
164
|
+
// Perform deletion
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `prompt` Questions:
|
|
170
|
+
|
|
171
|
+
```markdown
|
|
172
|
+
## prompt<T>() API
|
|
173
|
+
|
|
174
|
+
Opens a modal to collect user input.
|
|
175
|
+
|
|
176
|
+
**Signature:**
|
|
177
|
+
```typescript
|
|
178
|
+
prompt<T>(options: PromptProps<T>): Promise<T>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Key Options:**
|
|
182
|
+
- `Input`: Custom input component (required)
|
|
183
|
+
- `defaultValue`: Initial value
|
|
184
|
+
- `disabled`: Function to disable confirm button
|
|
185
|
+
|
|
186
|
+
**Simple Example:**
|
|
187
|
+
```tsx
|
|
188
|
+
const name = await prompt<string>({
|
|
189
|
+
title: 'Enter Name',
|
|
190
|
+
defaultValue: '',
|
|
191
|
+
Input: ({ value, onChange }) => (
|
|
192
|
+
<input
|
|
193
|
+
value={value}
|
|
194
|
+
onChange={(e) => onChange(e.target.value)}
|
|
195
|
+
/>
|
|
196
|
+
),
|
|
197
|
+
disabled: (value) => value.trim() === '',
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Complex Data Example:**
|
|
202
|
+
```tsx
|
|
203
|
+
interface UserData {
|
|
204
|
+
name: string;
|
|
205
|
+
email: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const userData = await prompt<UserData>({
|
|
209
|
+
title: 'User Information',
|
|
210
|
+
defaultValue: { name: '', email: '' },
|
|
211
|
+
Input: ({ value, onChange }) => (
|
|
212
|
+
<form>
|
|
213
|
+
<input
|
|
214
|
+
value={value.name}
|
|
215
|
+
onChange={(e) => onChange({ ...value, name: e.target.value })}
|
|
216
|
+
placeholder="Name"
|
|
217
|
+
/>
|
|
218
|
+
<input
|
|
219
|
+
value={value.email}
|
|
220
|
+
onChange={(e) => onChange({ ...value, email: e.target.value })}
|
|
221
|
+
placeholder="Email"
|
|
222
|
+
/>
|
|
223
|
+
</form>
|
|
224
|
+
),
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### `useModal` Questions:
|
|
230
|
+
|
|
231
|
+
```markdown
|
|
232
|
+
## useModal() Hook
|
|
233
|
+
|
|
234
|
+
Returns modal handlers tied to component lifecycle.
|
|
235
|
+
|
|
236
|
+
**Key Feature:** Automatic cleanup on component unmount.
|
|
237
|
+
|
|
238
|
+
**Example:**
|
|
239
|
+
```tsx
|
|
240
|
+
import { useModal } from '@lerx/promise-modal';
|
|
241
|
+
|
|
242
|
+
function MyComponent() {
|
|
243
|
+
const modal = useModal();
|
|
244
|
+
|
|
245
|
+
const handleAction = async () => {
|
|
246
|
+
// These modals will auto-close if component unmounts
|
|
247
|
+
if (await modal.confirm({ content: 'Proceed?' })) {
|
|
248
|
+
await modal.alert({ content: 'Done!' });
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
return <button onClick={handleAction}>Execute</button>;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**With Configuration:**
|
|
257
|
+
```tsx
|
|
258
|
+
const modal = useModal({
|
|
259
|
+
ForegroundComponent: CustomForeground,
|
|
260
|
+
dimmed: true,
|
|
261
|
+
duration: 300,
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Category: Configuration Priority
|
|
269
|
+
|
|
270
|
+
```markdown
|
|
271
|
+
## Configuration Priority
|
|
272
|
+
|
|
273
|
+
Configuration is applied hierarchically:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
Provider Config (Lowest) < Hook Config < Handler Config (Highest)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Example:**
|
|
280
|
+
```tsx
|
|
281
|
+
// Provider level: Global defaults
|
|
282
|
+
<ModalProvider options={{ duration: '500ms', closeOnBackdropClick: true }}>
|
|
283
|
+
<App />
|
|
284
|
+
</ModalProvider>
|
|
285
|
+
|
|
286
|
+
// Hook level: Component defaults (overrides Provider config)
|
|
287
|
+
const modal = useModal({
|
|
288
|
+
ForegroundComponent: CustomForeground,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Handler level: Individual modal (overrides Hook config)
|
|
292
|
+
modal.alert({
|
|
293
|
+
title: 'Alert',
|
|
294
|
+
duration: 200, // Override 500ms → 200ms
|
|
295
|
+
ForegroundComponent: SpecialForeground, // Override CustomForeground
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Category: Customization
|
|
303
|
+
|
|
304
|
+
### Component Customization Questions:
|
|
305
|
+
|
|
306
|
+
```markdown
|
|
307
|
+
## Custom Components
|
|
308
|
+
|
|
309
|
+
### Custom Foreground
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
const CustomForeground = ({ children, visible, id }) => {
|
|
313
|
+
const ref = useRef(null);
|
|
314
|
+
const { duration } = useModalDuration();
|
|
315
|
+
|
|
316
|
+
useModalAnimation(visible, {
|
|
317
|
+
onVisible: () => ref.current?.classList.add('active'),
|
|
318
|
+
onHidden: () => ref.current?.classList.remove('active'),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
useDestroyAfter(id, duration);
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<div
|
|
325
|
+
ref={ref}
|
|
326
|
+
style={{
|
|
327
|
+
background: 'white',
|
|
328
|
+
borderRadius: 12,
|
|
329
|
+
padding: 24,
|
|
330
|
+
maxWidth: 500,
|
|
331
|
+
}}
|
|
332
|
+
>
|
|
333
|
+
{children}
|
|
334
|
+
</div>
|
|
335
|
+
);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Use in Provider
|
|
339
|
+
<ModalProvider ForegroundComponent={CustomForeground}>
|
|
340
|
+
<App />
|
|
341
|
+
</ModalProvider>
|
|
342
|
+
|
|
343
|
+
// Or in individual modal
|
|
344
|
+
alert({
|
|
345
|
+
content: 'Hello',
|
|
346
|
+
ForegroundComponent: CustomForeground,
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Custom Footer
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
const CustomFooter = ({ onConfirm, onClose, type, disabled }) => (
|
|
354
|
+
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
|
|
355
|
+
{type !== 'alert' && (
|
|
356
|
+
<button onClick={onClose}>Cancel</button>
|
|
357
|
+
)}
|
|
358
|
+
<button onClick={() => onConfirm()} disabled={disabled}>
|
|
359
|
+
Confirm
|
|
360
|
+
</button>
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
```
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Styling/Theming Questions:
|
|
367
|
+
|
|
368
|
+
```markdown
|
|
369
|
+
## Theming with Context
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
<ModalProvider
|
|
373
|
+
context={{
|
|
374
|
+
theme: 'dark',
|
|
375
|
+
primaryColor: '#007bff',
|
|
376
|
+
}}
|
|
377
|
+
>
|
|
378
|
+
<App />
|
|
379
|
+
</ModalProvider>
|
|
380
|
+
|
|
381
|
+
// Access in custom components
|
|
382
|
+
const CustomTitle = ({ children, context }) => (
|
|
383
|
+
<h2 style={{ color: context.theme === 'dark' ? '#fff' : '#333' }}>
|
|
384
|
+
{children}
|
|
385
|
+
</h2>
|
|
386
|
+
);
|
|
387
|
+
```
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Category: Advanced Patterns
|
|
393
|
+
|
|
394
|
+
### AbortSignal Questions:
|
|
395
|
+
|
|
396
|
+
```markdown
|
|
397
|
+
## Modal Cancellation with AbortSignal
|
|
398
|
+
|
|
399
|
+
Programmatically cancel modals.
|
|
400
|
+
|
|
401
|
+
**Basic Usage:**
|
|
402
|
+
```tsx
|
|
403
|
+
const controller = new AbortController();
|
|
404
|
+
|
|
405
|
+
alert({
|
|
406
|
+
title: 'Cancelable Modal',
|
|
407
|
+
content: 'Auto-closes in 3 seconds.',
|
|
408
|
+
signal: controller.signal,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Cancel modal after 3 seconds
|
|
412
|
+
setTimeout(() => {
|
|
413
|
+
controller.abort();
|
|
414
|
+
}, 3000);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Manual Abort Control:**
|
|
418
|
+
```tsx
|
|
419
|
+
function ManualAbortControl() {
|
|
420
|
+
const [controller, setController] = useState<AbortController | null>(null);
|
|
421
|
+
|
|
422
|
+
const handleOpen = () => {
|
|
423
|
+
const newController = new AbortController();
|
|
424
|
+
setController(newController);
|
|
425
|
+
|
|
426
|
+
alert({
|
|
427
|
+
title: 'Manual Cancel',
|
|
428
|
+
content: 'Click "Cancel" button to close modal.',
|
|
429
|
+
signal: newController.signal,
|
|
430
|
+
closeOnBackdropClick: false,
|
|
431
|
+
}).then(() => {
|
|
432
|
+
setController(null);
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const handleAbort = () => {
|
|
437
|
+
if (controller) {
|
|
438
|
+
controller.abort();
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<div>
|
|
444
|
+
<button onClick={handleOpen} disabled={!!controller}>Open Modal</button>
|
|
445
|
+
<button onClick={handleAbort} disabled={!controller}>Cancel Modal</button>
|
|
446
|
+
</div>
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Batch Cancel Multiple Modals:**
|
|
452
|
+
```tsx
|
|
453
|
+
const controllers: AbortController[] = [];
|
|
454
|
+
|
|
455
|
+
for (let i = 0; i < 3; i++) {
|
|
456
|
+
const controller = new AbortController();
|
|
457
|
+
controllers.push(controller);
|
|
458
|
+
|
|
459
|
+
alert({
|
|
460
|
+
title: `Modal ${i + 1}`,
|
|
461
|
+
signal: controller.signal,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Cancel all modals
|
|
466
|
+
controllers.forEach((c) => c.abort());
|
|
467
|
+
```
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Toast Implementation Questions:
|
|
471
|
+
|
|
472
|
+
```markdown
|
|
473
|
+
## Toast Implementation
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
import { alert, useModalAnimation, useDestroyAfter, useModalDuration } from '@lerx/promise-modal';
|
|
477
|
+
|
|
478
|
+
const ToastForeground = ({ id, visible, children, onClose, onDestroy }) => {
|
|
479
|
+
const ref = useRef(null);
|
|
480
|
+
const { duration } = useModalDuration();
|
|
481
|
+
|
|
482
|
+
useEffect(() => {
|
|
483
|
+
const timer = setTimeout(onClose, 3000);
|
|
484
|
+
return () => clearTimeout(timer);
|
|
485
|
+
}, [onClose]);
|
|
486
|
+
|
|
487
|
+
useModalAnimation(visible, {
|
|
488
|
+
onVisible: () => ref.current?.classList.add('visible'),
|
|
489
|
+
onHidden: () => ref.current?.classList.remove('visible'),
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
useDestroyAfter(id, duration);
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<div
|
|
496
|
+
ref={ref}
|
|
497
|
+
style={{
|
|
498
|
+
position: 'fixed',
|
|
499
|
+
bottom: 20,
|
|
500
|
+
left: '50%',
|
|
501
|
+
transform: 'translateX(-50%)',
|
|
502
|
+
background: '#333',
|
|
503
|
+
color: 'white',
|
|
504
|
+
padding: '12px 24px',
|
|
505
|
+
borderRadius: 8,
|
|
506
|
+
opacity: 0,
|
|
507
|
+
transition: 'opacity 300ms',
|
|
508
|
+
}}
|
|
509
|
+
className={visible ? 'visible' : ''}
|
|
510
|
+
>
|
|
511
|
+
{children}
|
|
512
|
+
</div>
|
|
513
|
+
);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// Pattern for removing previous toast
|
|
517
|
+
let onDestroyPrevToast: () => void;
|
|
518
|
+
|
|
519
|
+
export const toast = (message: ReactNode, duration = 1250) => {
|
|
520
|
+
onDestroyPrevToast?.(); // Remove previous toast
|
|
521
|
+
|
|
522
|
+
return alert({
|
|
523
|
+
content: message,
|
|
524
|
+
ForegroundComponent: (props) => {
|
|
525
|
+
onDestroyPrevToast = props.onDestroy;
|
|
526
|
+
return <ToastForeground {...props} />;
|
|
527
|
+
},
|
|
528
|
+
footer: false,
|
|
529
|
+
dimmed: false,
|
|
530
|
+
closeOnBackdropClick: false,
|
|
531
|
+
});
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// Usage
|
|
535
|
+
toast('Task completed successfully!');
|
|
536
|
+
```
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Nested Modal Questions:
|
|
540
|
+
|
|
541
|
+
```markdown
|
|
542
|
+
## Nested Modals
|
|
543
|
+
|
|
544
|
+
```tsx
|
|
545
|
+
async function multiStepWizard() {
|
|
546
|
+
// Step 1: Confirm
|
|
547
|
+
const proceed = await confirm({
|
|
548
|
+
title: 'Start Wizard',
|
|
549
|
+
content: 'We will guide you through the setup.',
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
if (!proceed) return;
|
|
553
|
+
|
|
554
|
+
// Step 2: User input
|
|
555
|
+
const username = await prompt<string>({
|
|
556
|
+
title: 'Step 1: Username',
|
|
557
|
+
defaultValue: '',
|
|
558
|
+
Input: ({ value, onChange }) => (
|
|
559
|
+
<input value={value} onChange={(e) => onChange(e.target.value)} />
|
|
560
|
+
),
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
if (!username) return;
|
|
564
|
+
|
|
565
|
+
// Step 3: Confirm and complete
|
|
566
|
+
const confirmed = await confirm({
|
|
567
|
+
title: 'Step 2: Confirm',
|
|
568
|
+
content: `Create account "${username}"?`,
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
if (confirmed) {
|
|
572
|
+
await alert({
|
|
573
|
+
title: 'Complete!',
|
|
574
|
+
content: `Welcome, ${username}!`,
|
|
575
|
+
subtype: 'success',
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Custom Anchor Questions:
|
|
583
|
+
|
|
584
|
+
```markdown
|
|
585
|
+
## Custom Modal Anchor
|
|
586
|
+
|
|
587
|
+
```tsx
|
|
588
|
+
import { ModalProvider, useInitializeModal, alert } from '@lerx/promise-modal';
|
|
589
|
+
|
|
590
|
+
function CustomAnchorExample() {
|
|
591
|
+
const { initialize } = useInitializeModal({ mode: 'manual' });
|
|
592
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
593
|
+
|
|
594
|
+
useEffect(() => {
|
|
595
|
+
if (containerRef.current) {
|
|
596
|
+
initialize(containerRef.current);
|
|
597
|
+
}
|
|
598
|
+
}, [initialize]);
|
|
599
|
+
|
|
600
|
+
return (
|
|
601
|
+
<div>
|
|
602
|
+
{/* Modals render inside this container */}
|
|
603
|
+
<div
|
|
604
|
+
ref={containerRef}
|
|
605
|
+
style={{
|
|
606
|
+
position: 'relative',
|
|
607
|
+
width: '100%',
|
|
608
|
+
height: 500,
|
|
609
|
+
overflow: 'hidden',
|
|
610
|
+
}}
|
|
611
|
+
/>
|
|
612
|
+
<button onClick={() => alert({ content: 'Inside custom container!' })}>
|
|
613
|
+
Show Modal
|
|
614
|
+
</button>
|
|
615
|
+
</div>
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Category: Troubleshooting
|
|
624
|
+
|
|
625
|
+
### Common Issues:
|
|
626
|
+
|
|
627
|
+
```markdown
|
|
628
|
+
## Troubleshooting Guide
|
|
629
|
+
|
|
630
|
+
### Modal Not Appearing
|
|
631
|
+
|
|
632
|
+
**Cause 1:** Missing ModalProvider
|
|
633
|
+
```tsx
|
|
634
|
+
// ❌ Wrong
|
|
635
|
+
function App() {
|
|
636
|
+
return <MyApp />;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// ✅ Correct
|
|
640
|
+
function App() {
|
|
641
|
+
return (
|
|
642
|
+
<ModalProvider>
|
|
643
|
+
<MyApp />
|
|
644
|
+
</ModalProvider>
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**Cause 2:** z-index conflict
|
|
650
|
+
```css
|
|
651
|
+
/* Ensure modal has highest z-index */
|
|
652
|
+
.modal-container {
|
|
653
|
+
z-index: 9999;
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### Modal Not Closing
|
|
658
|
+
|
|
659
|
+
**Cause 1:** manualDestroy is true
|
|
660
|
+
```tsx
|
|
661
|
+
// Modal won't auto-close
|
|
662
|
+
alert({ content: 'Hello', manualDestroy: true });
|
|
663
|
+
|
|
664
|
+
// Must call onDestroy manually
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
**Cause 2:** closeOnBackdropClick is false
|
|
668
|
+
```tsx
|
|
669
|
+
// Won't close on backdrop click
|
|
670
|
+
alert({ content: 'Hello', closeOnBackdropClick: false });
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### prompt TypeScript Errors
|
|
674
|
+
|
|
675
|
+
**Cause:** Missing generic type
|
|
676
|
+
```tsx
|
|
677
|
+
// ❌ Wrong - TypeScript can't infer type
|
|
678
|
+
const value = await prompt({ ... });
|
|
679
|
+
|
|
680
|
+
// ✅ Correct - Specify type
|
|
681
|
+
const value = await prompt<string>({ ... });
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### Animation Not Working
|
|
685
|
+
|
|
686
|
+
**Cause:** Not using useModalAnimation
|
|
687
|
+
```tsx
|
|
688
|
+
// ❌ Wrong - Checking visible directly
|
|
689
|
+
if (visible) element.classList.add('show');
|
|
690
|
+
|
|
691
|
+
// ✅ Correct - Use hook
|
|
692
|
+
useModalAnimation(visible, {
|
|
693
|
+
onVisible: () => element.classList.add('show'),
|
|
694
|
+
onHidden: () => element.classList.remove('show'),
|
|
695
|
+
});
|
|
696
|
+
```
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Response Format
|
|
702
|
+
|
|
703
|
+
When answering questions, follow this structure:
|
|
704
|
+
|
|
705
|
+
1. **Brief Answer**: One-line summary
|
|
706
|
+
2. **Code Example**: Working code snippet
|
|
707
|
+
3. **Explanation**: Key points to understand
|
|
708
|
+
4. **Related API**: Links to related features
|
|
709
|
+
5. **Common Pitfalls**: What to avoid
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
## Example Interaction
|
|
714
|
+
|
|
715
|
+
**User**: "How do I disable the confirm button until a required field is filled?"
|
|
716
|
+
|
|
717
|
+
**Response**:
|
|
718
|
+
```markdown
|
|
719
|
+
Use the `disabled` option in `prompt()`:
|
|
720
|
+
|
|
721
|
+
```tsx
|
|
722
|
+
const email = await prompt<string>({
|
|
723
|
+
title: 'Enter Email',
|
|
724
|
+
defaultValue: '',
|
|
725
|
+
Input: ({ value, onChange }) => (
|
|
726
|
+
<input
|
|
727
|
+
type="email"
|
|
728
|
+
value={value}
|
|
729
|
+
onChange={(e) => onChange(e.target.value)}
|
|
730
|
+
placeholder="your@email.com"
|
|
731
|
+
/>
|
|
732
|
+
),
|
|
733
|
+
// Disable confirm button until valid email
|
|
734
|
+
disabled: (value) => !value.includes('@') || value.length < 5,
|
|
735
|
+
});
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
**Key Points:**
|
|
739
|
+
- `disabled` receives the current value
|
|
740
|
+
- Returns `true` to disable confirm button
|
|
741
|
+
- Updates reactively as user types
|
|
742
|
+
|
|
743
|
+
**Related:** `PromptInputProps`, `FooterComponentProps.disabled`
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
---
|
|
747
|
+
|
|
748
|
+
## Knowledge Sources
|
|
749
|
+
|
|
750
|
+
For more detailed information, refer to the related skill's in-depth knowledge:
|
|
751
|
+
|
|
752
|
+
| Topic | Knowledge File |
|
|
753
|
+
|------|-----------|
|
|
754
|
+
| Comprehensive Guide | `promise-modal-expert` (directory skill) |
|
|
755
|
+
| API Reference | `knowledge/api-reference.md` |
|
|
756
|
+
| Hooks Reference | `knowledge/hooks-reference.md` |
|
|
757
|
+
| Advanced Patterns | `knowledge/advanced-patterns.md` |
|
|
758
|
+
| Type Definitions | `knowledge/type-definitions.md` |
|
|
759
|
+
|
|
760
|
+
See SPECIFICATION documentation for full API specs.
|