@lerx/promise-modal 0.11.1 → 0.12.1
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/dist/claude-hashes.json +18 -0
- package/docs/claude/skills/{expert → promise-modal-skill}/SKILL.md +32 -34
- package/docs/claude/skills/{expert → promise-modal-skill}/knowledge/advanced-patterns.md +8 -6
- package/docs/claude/skills/{expert → promise-modal-skill}/knowledge/hooks-reference.md +1 -7
- package/package.json +8 -5
- package/docs/claude/commands/guide.md +0 -760
- /package/docs/claude/skills/{expert → promise-modal-skill}/knowledge/api-reference.md +0 -0
- /package/docs/claude/skills/{expert → promise-modal-skill}/knowledge/type-definitions.md +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"package": {
|
|
4
|
+
"name": "@lerx/promise-modal",
|
|
5
|
+
"version": "0.12.1"
|
|
6
|
+
},
|
|
7
|
+
"generatedAt": "2026-04-24T14:52:39.411Z",
|
|
8
|
+
"algorithm": "sha256",
|
|
9
|
+
"assetRoot": "docs/claude",
|
|
10
|
+
"files": {
|
|
11
|
+
"skills/promise-modal-skill/SKILL.md": "b424c979b9e969e11ba8fa0a56afc3cbf1793c822f06509e6b730068f384679e",
|
|
12
|
+
"skills/promise-modal-skill/knowledge/advanced-patterns.md": "2e2c29449a46b1336727f8a1d5f1eede4e99639675dbe819edd2bf4243acfaab",
|
|
13
|
+
"skills/promise-modal-skill/knowledge/api-reference.md": "1df36fa5b832d2349c05dacaec913876f7c30fad6478fcae64c8a4bd65bc023a",
|
|
14
|
+
"skills/promise-modal-skill/knowledge/hooks-reference.md": "b0137caf257ad9712cfa7ab44c908041bbbc61bf355fa0180925a35479e47516",
|
|
15
|
+
"skills/promise-modal-skill/knowledge/type-definitions.md": "fde17b7f99d3e65b7e6c16abf4689af0a9e41a8a06dfeb9482b94a7611841d50"
|
|
16
|
+
},
|
|
17
|
+
"previousVersions": {}
|
|
18
|
+
}
|
|
@@ -1,37 +1,33 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: promise-modal-
|
|
2
|
+
name: promise-modal-skill
|
|
3
3
|
description: "@lerx/promise-modal library expert. Guide users on React-based Promise modal utilities (alert, confirm, prompt), architecture, and customization."
|
|
4
|
-
user-invocable: false
|
|
5
4
|
---
|
|
6
5
|
|
|
7
|
-
# Promise Modal Expert
|
|
6
|
+
# Promise Modal Expert
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Guide users on the `@lerx/promise-modal` library for React-based Promise modal utilities. Apply this skill when users ask how to implement, configure, customize, or troubleshoot modal dialogs built with `@lerx/promise-modal`.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## Core Knowledge
|
|
14
|
-
|
|
15
|
-
### Library Overview
|
|
10
|
+
## Library Overview
|
|
16
11
|
|
|
17
12
|
`@lerx/promise-modal` is a universal modal utility for React that provides:
|
|
13
|
+
|
|
18
14
|
- Promise-based modal interactions (alert, confirm, prompt)
|
|
19
15
|
- Usage both inside and outside React components
|
|
20
16
|
- High-level component customization
|
|
21
17
|
- Automatic lifecycle management
|
|
22
18
|
- Programmatic modal cancellation via AbortSignal
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
## Architecture
|
|
25
21
|
|
|
26
22
|
The library uses a layered architecture:
|
|
27
23
|
|
|
28
|
-
1. **Core Layer**
|
|
29
|
-
2. **Application Layer**
|
|
30
|
-
3. **Bootstrap Layer**
|
|
31
|
-
4. **Provider Layer**
|
|
32
|
-
5. **Component Layer**
|
|
24
|
+
1. **Core Layer** — Main API functions (alert, confirm, prompt)
|
|
25
|
+
2. **Application Layer** — ModalManager singleton for lifecycle and DOM management
|
|
26
|
+
3. **Bootstrap Layer** — ModalProvider component for initialization
|
|
27
|
+
4. **Provider Layer** — Context providers for configuration and state
|
|
28
|
+
5. **Component Layer** — Customizable UI components
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
## Configuration Priority
|
|
35
31
|
|
|
36
32
|
Configuration is applied hierarchically, with lower levels overriding higher levels:
|
|
37
33
|
|
|
@@ -49,9 +45,9 @@ Provider Config (Lowest) < Hook Config < Handler Config (Highest)
|
|
|
49
45
|
|
|
50
46
|
## Basic Usage Patterns
|
|
51
47
|
|
|
52
|
-
### Pattern
|
|
48
|
+
### Pattern A: Static API
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
Use static functions outside React components.
|
|
55
51
|
|
|
56
52
|
```typescript
|
|
57
53
|
import { alert, confirm, prompt } from '@lerx/promise-modal';
|
|
@@ -85,9 +81,9 @@ const name = await prompt({
|
|
|
85
81
|
});
|
|
86
82
|
```
|
|
87
83
|
|
|
88
|
-
### Pattern
|
|
84
|
+
### Pattern B: Component-Scoped with useModal
|
|
89
85
|
|
|
90
|
-
|
|
86
|
+
Tie modals to component lifecycle — automatically cleaned up on unmount.
|
|
91
87
|
|
|
92
88
|
```typescript
|
|
93
89
|
import { useModal } from '@lerx/promise-modal';
|
|
@@ -107,7 +103,7 @@ function MyComponent() {
|
|
|
107
103
|
}
|
|
108
104
|
```
|
|
109
105
|
|
|
110
|
-
**
|
|
106
|
+
**Static API vs useModal**:
|
|
111
107
|
|
|
112
108
|
| Feature | Static Handlers | useModal Hook |
|
|
113
109
|
|------|------------|-------------|
|
|
@@ -115,6 +111,8 @@ function MyComponent() {
|
|
|
115
111
|
| Cleanup | Manual | Auto on unmount |
|
|
116
112
|
| Usage Location | Anywhere | Inside React components |
|
|
117
113
|
|
|
114
|
+
For advanced patterns (AbortSignal cancellation, toasts, nested modals, custom anchors, complex forms), see `knowledge/advanced-patterns.md`.
|
|
115
|
+
|
|
118
116
|
---
|
|
119
117
|
|
|
120
118
|
## Troubleshooting
|
|
@@ -147,25 +145,25 @@ function MyComponent() {
|
|
|
147
145
|
|
|
148
146
|
## Best Practices
|
|
149
147
|
|
|
150
|
-
1. **Place ModalProvider at Root
|
|
151
|
-
2. **Use useModal in Components
|
|
152
|
-
3. **Use Static API in Utilities
|
|
153
|
-
4. **Customize at Provider Level
|
|
154
|
-
5. **Use subtype Semantically
|
|
155
|
-
6. **Handle Promises Properly
|
|
156
|
-
7. **Keep Modal Content Simple
|
|
157
|
-
8. **Test Accessibility
|
|
158
|
-
9. **Leverage AbortSignal
|
|
148
|
+
1. **Place ModalProvider at Root** — wrap the entire app
|
|
149
|
+
2. **Use useModal in Components** — for automatic cleanup
|
|
150
|
+
3. **Use Static API in Utilities** — for non-component code
|
|
151
|
+
4. **Customize at Provider Level** — set global styles once
|
|
152
|
+
5. **Use subtype Semantically** — info, success, warning, error
|
|
153
|
+
6. **Handle Promises Properly** — always await or handle rejection
|
|
154
|
+
7. **Keep Modal Content Simple** — avoid complex state in modals
|
|
155
|
+
8. **Test Accessibility** — verify keyboard navigation
|
|
156
|
+
9. **Leverage AbortSignal** — when programmatic modal closure is needed
|
|
159
157
|
|
|
160
158
|
---
|
|
161
159
|
|
|
162
|
-
##
|
|
160
|
+
## Resources
|
|
163
161
|
|
|
164
|
-
|
|
162
|
+
Load these files on demand for detailed API, patterns, and type definitions:
|
|
165
163
|
|
|
166
164
|
| File | Contents |
|
|
167
165
|
|------|------|
|
|
168
166
|
| `knowledge/api-reference.md` | Static functions (alert, confirm, prompt) and ModalProvider detailed API |
|
|
169
167
|
| `knowledge/hooks-reference.md` | Complete reference for 8 hooks (useModal, useActiveModalCount, etc.) |
|
|
170
|
-
| `knowledge/advanced-patterns.md` | Advanced patterns
|
|
171
|
-
| `knowledge/type-definitions.md` | TypeScript interface definitions (ModalFrameProps,
|
|
168
|
+
| `knowledge/advanced-patterns.md` | Advanced patterns: AbortSignal, toast, nested modals, custom anchors, complex forms |
|
|
169
|
+
| `knowledge/type-definitions.md` | TypeScript interface definitions (ModalFrameProps, FooterComponentProps, PromptInputProps) |
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Advanced Patterns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
These patterns extend the basic Static API and `useModal` patterns documented in `SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Pattern 1: Modal Cancellation with AbortSignal
|
|
4
6
|
|
|
5
7
|
Programmatically cancel modals.
|
|
6
8
|
|
|
@@ -47,7 +49,7 @@ function ManualAbortControl() {
|
|
|
47
49
|
|
|
48
50
|
---
|
|
49
51
|
|
|
50
|
-
## Pattern
|
|
52
|
+
## Pattern 2: Toast Implementation
|
|
51
53
|
|
|
52
54
|
Implement auto-dismissing toast notifications.
|
|
53
55
|
|
|
@@ -104,7 +106,7 @@ toast('An error occurred.', 2000);
|
|
|
104
106
|
|
|
105
107
|
---
|
|
106
108
|
|
|
107
|
-
## Pattern
|
|
109
|
+
## Pattern 3: Nested Modals
|
|
108
110
|
|
|
109
111
|
Display multiple modals sequentially in steps.
|
|
110
112
|
|
|
@@ -149,7 +151,7 @@ async function multiStepProcess() {
|
|
|
149
151
|
|
|
150
152
|
---
|
|
151
153
|
|
|
152
|
-
## Pattern
|
|
154
|
+
## Pattern 4: Custom Anchor
|
|
153
155
|
|
|
154
156
|
Render modals inside a specific DOM element.
|
|
155
157
|
|
|
@@ -191,7 +193,7 @@ function CustomAnchorExample() {
|
|
|
191
193
|
|
|
192
194
|
---
|
|
193
195
|
|
|
194
|
-
## Pattern
|
|
196
|
+
## Pattern 5: Complex Form Input
|
|
195
197
|
|
|
196
198
|
Collect complex form data using prompt.
|
|
197
199
|
|
|
@@ -258,7 +260,7 @@ async function collectUserInfo() {
|
|
|
258
260
|
|
|
259
261
|
---
|
|
260
262
|
|
|
261
|
-
## Pattern
|
|
263
|
+
## Pattern 6: Conditional Modal Styles
|
|
262
264
|
|
|
263
265
|
Display different styled modals based on situation.
|
|
264
266
|
|
|
@@ -20,13 +20,7 @@ function MyComponent() {
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
**Key Feature**: Modals are automatically cleaned up when component unmounts.
|
|
24
|
-
|
|
25
|
-
| Feature | Static Handlers | useModal Hook |
|
|
26
|
-
|------|------------|-------------|
|
|
27
|
-
| Lifecycle | Independent | Tied to component |
|
|
28
|
-
| Cleanup | Manual | Auto on unmount |
|
|
29
|
-
| Usage Location | Anywhere | Inside React components |
|
|
23
|
+
**Key Feature**: Modals are automatically cleaned up when component unmounts. For a comparison with the Static API, see `SKILL.md`.
|
|
30
24
|
|
|
31
25
|
---
|
|
32
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lerx/promise-modal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Universal React modal utility that can be used outside React components with promise-based results for alert, confirm, and prompt modals",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -48,13 +48,15 @@
|
|
|
48
48
|
"README.md"
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
|
-
"build": "rollup -c && yarn build:types",
|
|
51
|
+
"build": "rollup -c && yarn build:types && yarn build:hashes",
|
|
52
|
+
"build:hashes": "claude-build-hashes",
|
|
52
53
|
"build:publish:npm": "yarn build && yarn publish:npm",
|
|
53
54
|
"build:types": "node ../../aileron/script/build/buildTypes.mjs",
|
|
54
55
|
"build-storybook": "storybook build",
|
|
55
56
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
56
57
|
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
57
58
|
"make-dependency-graph": "npx depcruise src --config .dependency-cruiser.js --output-type dot > dependency-graph.dot && dot -Tpng dependency-graph.dot -o dependency-graph.png",
|
|
59
|
+
"prepublishOnly": "yarn build",
|
|
58
60
|
"publish:npm": "yarn npm publish --access public",
|
|
59
61
|
"size-limit": "size-limit",
|
|
60
62
|
"start": "yarn build && yarn storybook",
|
|
@@ -65,9 +67,10 @@
|
|
|
65
67
|
"version:patch": "yarn version patch"
|
|
66
68
|
},
|
|
67
69
|
"dependencies": {
|
|
68
|
-
"@
|
|
69
|
-
"@winglet/
|
|
70
|
-
"@winglet/
|
|
70
|
+
"@slats/claude-assets-sync": "^0.3.0",
|
|
71
|
+
"@winglet/common-utils": "^0.12.1",
|
|
72
|
+
"@winglet/react-utils": "^0.12.1",
|
|
73
|
+
"@winglet/style-utils": "^0.12.1"
|
|
71
74
|
},
|
|
72
75
|
"devDependencies": {
|
|
73
76
|
"@types/react": "^19.0.0",
|
|
@@ -1,760 +0,0 @@
|
|
|
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.
|
|
File without changes
|
|
File without changes
|