@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.
@@ -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.