@ceed/ads 1.20.0 → 1.20.1-next.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/components/ProfileMenu/ProfileMenu.d.ts +1 -1
- package/dist/components/data-display/Markdown.md +832 -0
- package/dist/components/feedback/Dialog.md +605 -3
- package/dist/components/feedback/Modal.md +656 -24
- package/dist/components/feedback/llms.txt +1 -1
- package/dist/components/inputs/Autocomplete.md +734 -2
- package/dist/components/inputs/Calendar.md +655 -1
- package/dist/components/inputs/DatePicker.md +699 -3
- package/dist/components/inputs/DateRangePicker.md +815 -1
- package/dist/components/inputs/MonthPicker.md +626 -4
- package/dist/components/inputs/MonthRangePicker.md +682 -4
- package/dist/components/inputs/Select.md +600 -0
- package/dist/components/layout/Container.md +507 -0
- package/dist/components/navigation/Breadcrumbs.md +582 -0
- package/dist/components/navigation/IconMenuButton.md +693 -0
- package/dist/components/navigation/InsetDrawer.md +1150 -3
- package/dist/components/navigation/Link.md +526 -0
- package/dist/components/navigation/MenuButton.md +632 -0
- package/dist/components/navigation/NavigationGroup.md +401 -1
- package/dist/components/navigation/NavigationItem.md +311 -0
- package/dist/components/navigation/Navigator.md +373 -0
- package/dist/components/navigation/Pagination.md +521 -0
- package/dist/components/navigation/ProfileMenu.md +605 -0
- package/dist/components/navigation/Tabs.md +609 -7
- package/dist/components/surfaces/Accordions.md +947 -3
- package/dist/index.cjs +3 -1
- package/dist/index.js +3 -1
- package/dist/llms.txt +1 -1
- package/framer/index.js +1 -1
- package/package.json +3 -2
|
@@ -2,38 +2,670 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
+
The Modal component is a dialog overlay that appears on top of the main content, demanding user attention and interaction. Built on Joy UI's Modal, it is used for displaying critical information, capturing user input, or requiring confirmation before proceeding. Modals block interaction with the underlying page until dismissed, making them ideal for important actions that require focused attention.
|
|
6
|
+
|
|
5
7
|
```tsx
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</
|
|
23
|
-
|
|
24
|
-
Make sure to use <code>aria-labelledby</code> on the modal dialog with an optional{' '}
|
|
25
|
-
<code>aria-describedby</code> attribute.
|
|
26
|
-
</Typography>
|
|
27
|
-
</Sheet>
|
|
28
|
-
</Modal>
|
|
8
|
+
<>
|
|
9
|
+
<Button onClick={() => setOpen(true)}>Open Modal</Button>
|
|
10
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
11
|
+
<ModalDialog>
|
|
12
|
+
<ModalClose />
|
|
13
|
+
<DialogTitle>Modal Title</DialogTitle>
|
|
14
|
+
<DialogContent>This is the modal content. You can place any content here.</DialogContent>
|
|
15
|
+
<DialogActions>
|
|
16
|
+
<Button variant="solid" onClick={() => setOpen(false)}>
|
|
17
|
+
Confirm
|
|
18
|
+
</Button>
|
|
19
|
+
<Button variant="plain" color="neutral" onClick={() => setOpen(false)}>
|
|
20
|
+
Cancel
|
|
21
|
+
</Button>
|
|
22
|
+
</DialogActions>
|
|
23
|
+
</ModalDialog>
|
|
24
|
+
</Modal>
|
|
25
|
+
</>
|
|
29
26
|
```
|
|
30
27
|
|
|
31
28
|
| Field | Description | Default |
|
|
32
29
|
| ---------------------------- | ----------- | ------- |
|
|
33
30
|
| Controls resolved at runtime | — | — |
|
|
34
31
|
|
|
32
|
+
> ⚠️ **Usage Warning** ⚠️
|
|
33
|
+
>
|
|
34
|
+
> Modals interrupt the user's current workflow and should be used sparingly.
|
|
35
|
+
>
|
|
36
|
+
> - Use only when user confirmation or required input is absolutely necessary
|
|
37
|
+
> - For simple information display, consider using Alert or Toast instead
|
|
38
|
+
> - Complex multi-step forms should be placed on separate pages
|
|
39
|
+
> - Avoid using modals for content that users need to reference while interacting with the page
|
|
40
|
+
|
|
35
41
|
## Usage
|
|
36
42
|
|
|
37
43
|
```tsx
|
|
38
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
Modal,
|
|
46
|
+
ModalDialog,
|
|
47
|
+
ModalClose,
|
|
48
|
+
DialogTitle,
|
|
49
|
+
DialogContent,
|
|
50
|
+
DialogActions,
|
|
51
|
+
Button,
|
|
52
|
+
} from '@ceed/ads';
|
|
53
|
+
|
|
54
|
+
function MyComponent() {
|
|
55
|
+
const [open, setOpen] = useState(false);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
<Button onClick={() => setOpen(true)}>Open Modal</Button>
|
|
60
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
61
|
+
<ModalDialog>
|
|
62
|
+
<ModalClose />
|
|
63
|
+
<DialogTitle>Modal Title</DialogTitle>
|
|
64
|
+
<DialogContent>
|
|
65
|
+
Place your content here.
|
|
66
|
+
</DialogContent>
|
|
67
|
+
<DialogActions>
|
|
68
|
+
<Button onClick={() => setOpen(false)}>Close</Button>
|
|
69
|
+
</DialogActions>
|
|
70
|
+
</ModalDialog>
|
|
71
|
+
</Modal>
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Examples
|
|
78
|
+
|
|
79
|
+
### Basic Modal
|
|
80
|
+
|
|
81
|
+
The basic modal with a simple Sheet for custom layouts.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<>
|
|
85
|
+
<Button onClick={() => setOpen(true)}>Open Basic Modal</Button>
|
|
86
|
+
<Modal aria-labelledby="modal-title" aria-describedby="modal-desc" open={open} onClose={() => setOpen(false)} sx={{
|
|
87
|
+
display: 'flex',
|
|
88
|
+
justifyContent: 'center',
|
|
89
|
+
alignItems: 'center'
|
|
90
|
+
}}>
|
|
91
|
+
<Sheet variant="outlined" sx={{
|
|
92
|
+
maxWidth: 500,
|
|
93
|
+
borderRadius: 'md',
|
|
94
|
+
p: 3,
|
|
95
|
+
boxShadow: 'lg'
|
|
96
|
+
}}>
|
|
97
|
+
<ModalClose variant="plain" sx={{
|
|
98
|
+
m: 1
|
|
99
|
+
}} />
|
|
100
|
+
<Typography component="h2" id="modal-title" level="h4" textColor="inherit" fontWeight="lg" mb={2}>
|
|
101
|
+
This is the modal title
|
|
102
|
+
</Typography>
|
|
103
|
+
<Typography id="modal-desc" textColor="text.tertiary">
|
|
104
|
+
Make sure to use <code>aria-labelledby</code> on the modal dialog with an optional{' '}
|
|
105
|
+
<code>aria-describedby</code> attribute.
|
|
106
|
+
</Typography>
|
|
107
|
+
</Sheet>
|
|
108
|
+
</Modal>
|
|
109
|
+
</>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Modal Dialog
|
|
113
|
+
|
|
114
|
+
Use ModalDialog for structured dialogs with title, content, and actions.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<>
|
|
118
|
+
<Button onClick={() => setOpen(true)}>Open Form Modal</Button>
|
|
119
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
120
|
+
<ModalDialog>
|
|
121
|
+
<ModalClose />
|
|
122
|
+
<DialogTitle>Create new project</DialogTitle>
|
|
123
|
+
<DialogContent>
|
|
124
|
+
Fill in the information of the project.
|
|
125
|
+
<form onSubmit={(event: React.FormEvent<HTMLFormElement>) => {
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
setOpen(false);
|
|
128
|
+
}}>
|
|
129
|
+
<Stack spacing={4}>
|
|
130
|
+
<FormControl>
|
|
131
|
+
<FormLabel>Name</FormLabel>
|
|
132
|
+
<Input required />
|
|
133
|
+
</FormControl>
|
|
134
|
+
<FormControl>
|
|
135
|
+
<FormLabel>Description</FormLabel>
|
|
136
|
+
<Input required />
|
|
137
|
+
</FormControl>
|
|
138
|
+
<Button type="submit">Submit</Button>
|
|
139
|
+
</Stack>
|
|
140
|
+
</form>
|
|
141
|
+
</DialogContent>
|
|
142
|
+
</ModalDialog>
|
|
143
|
+
</Modal>
|
|
144
|
+
</>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Variants
|
|
148
|
+
|
|
149
|
+
Modal dialogs support different visual variants.
|
|
150
|
+
|
|
151
|
+
#### Plain Variant
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<>
|
|
155
|
+
<Button onClick={() => setOpen(true)}>Plain Variant</Button>
|
|
156
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
157
|
+
<ModalDialog variant="plain">
|
|
158
|
+
<ModalClose />
|
|
159
|
+
<DialogTitle>Modal Dialog</DialogTitle>
|
|
160
|
+
<DialogContent>This is a `plain` modal dialog.</DialogContent>
|
|
161
|
+
</ModalDialog>
|
|
162
|
+
</Modal>
|
|
163
|
+
</>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Outlined Variant
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<>
|
|
170
|
+
<Button onClick={() => setOpen(true)}>Outlined Variant</Button>
|
|
171
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
172
|
+
<ModalDialog variant="outlined">
|
|
173
|
+
<ModalClose />
|
|
174
|
+
<DialogTitle>Modal Dialog</DialogTitle>
|
|
175
|
+
<DialogContent>This is an `outlined` modal dialog.</DialogContent>
|
|
176
|
+
</ModalDialog>
|
|
177
|
+
</Modal>
|
|
178
|
+
</>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Soft Variant
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<>
|
|
185
|
+
<Button onClick={() => setOpen(true)}>Soft Variant</Button>
|
|
186
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
187
|
+
<ModalDialog variant="soft">
|
|
188
|
+
<ModalClose />
|
|
189
|
+
<DialogTitle>Modal Dialog</DialogTitle>
|
|
190
|
+
<DialogContent>This is a `soft` modal dialog.</DialogContent>
|
|
191
|
+
</ModalDialog>
|
|
192
|
+
</Modal>
|
|
193
|
+
</>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Solid Variant
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
<>
|
|
200
|
+
<Button onClick={() => setOpen(true)}>Solid Variant</Button>
|
|
201
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
202
|
+
<ModalDialog variant="solid">
|
|
203
|
+
<ModalClose />
|
|
204
|
+
<DialogTitle>Modal Dialog</DialogTitle>
|
|
205
|
+
<DialogContent>This is a `solid` modal dialog.</DialogContent>
|
|
206
|
+
</ModalDialog>
|
|
207
|
+
</Modal>
|
|
208
|
+
</>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Alert Dialog
|
|
212
|
+
|
|
213
|
+
For critical confirmations that require explicit user decision.
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<>
|
|
217
|
+
<Button color="danger" onClick={() => setOpen(true)}>
|
|
218
|
+
Delete Item
|
|
219
|
+
</Button>
|
|
220
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
221
|
+
<ModalDialog variant="outlined" role="alertdialog">
|
|
222
|
+
<DialogTitle>
|
|
223
|
+
<WarningRoundedIcon />
|
|
224
|
+
Confirmation
|
|
225
|
+
</DialogTitle>
|
|
226
|
+
<Divider />
|
|
227
|
+
<DialogContent>Are you sure you want to discard all of your notes?</DialogContent>
|
|
228
|
+
<DialogActions>
|
|
229
|
+
<Button variant="solid" color="danger" onClick={() => setOpen(false)}>
|
|
230
|
+
Discard notes
|
|
231
|
+
</Button>
|
|
232
|
+
<Button variant="plain" color="neutral" onClick={() => setOpen(false)}>
|
|
233
|
+
Cancel
|
|
234
|
+
</Button>
|
|
235
|
+
</DialogActions>
|
|
236
|
+
</ModalDialog>
|
|
237
|
+
</Modal>
|
|
238
|
+
</>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Layouts
|
|
242
|
+
|
|
243
|
+
#### Fullscreen Layout
|
|
244
|
+
|
|
245
|
+
For complex content that requires maximum screen space.
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
<>
|
|
249
|
+
<Button onClick={() => setOpen(true)}>Open Fullscreen Modal</Button>
|
|
250
|
+
<Modal open={open} onClose={() => setOpen(false)}>
|
|
251
|
+
<ModalDialog layout="fullscreen">
|
|
252
|
+
<ModalClose />
|
|
253
|
+
<DialogTitle>Fullscreen Modal</DialogTitle>
|
|
254
|
+
<DialogContent>This modal takes up the entire screen.</DialogContent>
|
|
255
|
+
<DialogActions>
|
|
256
|
+
<Button variant="solid" onClick={() => setOpen(false)}>
|
|
257
|
+
Save
|
|
258
|
+
</Button>
|
|
259
|
+
</DialogActions>
|
|
260
|
+
</ModalDialog>
|
|
261
|
+
</Modal>
|
|
262
|
+
</>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Nested Modals
|
|
266
|
+
|
|
267
|
+
Modals can be stacked on top of each other when necessary.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
<>
|
|
271
|
+
<Button onClick={() => setFirstOpen(true)}>Open First Modal</Button>
|
|
272
|
+
<Modal open={firstOpen} onClose={() => setFirstOpen(false)}>
|
|
273
|
+
<ModalDialog>
|
|
274
|
+
<ModalClose />
|
|
275
|
+
<DialogTitle>First Modal</DialogTitle>
|
|
276
|
+
<DialogContent>This is the first modal. Click the button below to open a nested modal.</DialogContent>
|
|
277
|
+
<DialogActions>
|
|
278
|
+
<Button onClick={() => setSecondOpen(true)}>Open Nested Modal</Button>
|
|
279
|
+
</DialogActions>
|
|
280
|
+
</ModalDialog>
|
|
281
|
+
</Modal>
|
|
282
|
+
<Modal open={secondOpen} onClose={() => setSecondOpen(false)}>
|
|
283
|
+
<ModalDialog>
|
|
284
|
+
<ModalClose />
|
|
285
|
+
<DialogTitle>Nested Modal</DialogTitle>
|
|
286
|
+
<DialogContent>This is a nested modal on top of the first one.</DialogContent>
|
|
287
|
+
<DialogActions>
|
|
288
|
+
<Button onClick={() => setSecondOpen(false)}>Close</Button>
|
|
289
|
+
</DialogActions>
|
|
290
|
+
</ModalDialog>
|
|
291
|
+
</Modal>
|
|
292
|
+
</>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## When to Use
|
|
296
|
+
|
|
297
|
+
### ✅ Good Use Cases
|
|
298
|
+
|
|
299
|
+
- **Destructive actions**: Confirming deletion, discarding changes, or other irreversible operations
|
|
300
|
+
- **Critical decisions**: Actions that have significant consequences requiring explicit consent
|
|
301
|
+
- **Focused input**: Short forms where distraction-free input is beneficial (login, quick edit)
|
|
302
|
+
- **Blocking notifications**: System errors or warnings that require acknowledgment
|
|
303
|
+
- **Media preview**: Viewing images, videos, or documents in a focused overlay
|
|
304
|
+
|
|
305
|
+
### ❌ When Not to Use
|
|
306
|
+
|
|
307
|
+
- **Long forms**: Multi-step wizards or complex forms belong on dedicated pages
|
|
308
|
+
- **Reference content**: Information users need while working on the page
|
|
309
|
+
- **Frequent interactions**: Actions performed repeatedly shouldn't require modal confirmation
|
|
310
|
+
- **Non-critical information**: Success messages or tips should use Toast or inline feedback
|
|
311
|
+
- **Mobile navigation**: Consider using full-page transitions instead on mobile devices
|
|
312
|
+
|
|
313
|
+
## Common Use Cases
|
|
314
|
+
|
|
315
|
+
### Confirmation Dialog
|
|
316
|
+
|
|
317
|
+
Confirm destructive or significant actions before executing them.
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
function DeleteConfirmation({ item, onDelete, onCancel }) {
|
|
321
|
+
return (
|
|
322
|
+
<Modal open onClose={onCancel}>
|
|
323
|
+
<ModalDialog variant="outlined" role="alertdialog">
|
|
324
|
+
<DialogTitle>
|
|
325
|
+
<WarningRoundedIcon />
|
|
326
|
+
Delete {item.name}?
|
|
327
|
+
</DialogTitle>
|
|
328
|
+
<Divider />
|
|
329
|
+
<DialogContent>
|
|
330
|
+
This action cannot be undone. All data associated with this item
|
|
331
|
+
will be permanently removed.
|
|
332
|
+
</DialogContent>
|
|
333
|
+
<DialogActions>
|
|
334
|
+
<Button variant="solid" color="danger" onClick={onDelete}>
|
|
335
|
+
Delete
|
|
336
|
+
</Button>
|
|
337
|
+
<Button variant="plain" color="neutral" onClick={onCancel}>
|
|
338
|
+
Cancel
|
|
339
|
+
</Button>
|
|
340
|
+
</DialogActions>
|
|
341
|
+
</ModalDialog>
|
|
342
|
+
</Modal>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Form Modal
|
|
348
|
+
|
|
349
|
+
Capture user input in a focused dialog.
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
function CreateProjectModal({ open, onClose, onCreate }) {
|
|
353
|
+
const handleSubmit = (event) => {
|
|
354
|
+
event.preventDefault();
|
|
355
|
+
const formData = new FormData(event.target);
|
|
356
|
+
onCreate({
|
|
357
|
+
name: formData.get('name'),
|
|
358
|
+
description: formData.get('description'),
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<Modal open={open} onClose={onClose}>
|
|
364
|
+
<ModalDialog>
|
|
365
|
+
<ModalClose />
|
|
366
|
+
<DialogTitle>Create New Project</DialogTitle>
|
|
367
|
+
<form onSubmit={handleSubmit}>
|
|
368
|
+
<DialogContent>
|
|
369
|
+
<Stack spacing={2}>
|
|
370
|
+
<FormControl required>
|
|
371
|
+
<FormLabel>Project Name</FormLabel>
|
|
372
|
+
<Input name="name" placeholder="Enter project name" />
|
|
373
|
+
</FormControl>
|
|
374
|
+
<FormControl>
|
|
375
|
+
<FormLabel>Description</FormLabel>
|
|
376
|
+
<Textarea name="description" minRows={3} placeholder="Enter description" />
|
|
377
|
+
</FormControl>
|
|
378
|
+
</Stack>
|
|
379
|
+
</DialogContent>
|
|
380
|
+
<DialogActions>
|
|
381
|
+
<Button type="submit" variant="solid">
|
|
382
|
+
Create
|
|
383
|
+
</Button>
|
|
384
|
+
<Button variant="plain" color="neutral" onClick={onClose}>
|
|
385
|
+
Cancel
|
|
386
|
+
</Button>
|
|
387
|
+
</DialogActions>
|
|
388
|
+
</form>
|
|
389
|
+
</ModalDialog>
|
|
390
|
+
</Modal>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Information Modal
|
|
396
|
+
|
|
397
|
+
Display detailed information that requires user acknowledgment.
|
|
398
|
+
|
|
399
|
+
```tsx
|
|
400
|
+
function TermsModal({ open, onAccept, onDecline }) {
|
|
401
|
+
return (
|
|
402
|
+
<Modal open={open}>
|
|
403
|
+
<ModalDialog sx={{ maxWidth: 600, maxHeight: '80vh', overflow: 'auto' }}>
|
|
404
|
+
<DialogTitle>Terms of Service</DialogTitle>
|
|
405
|
+
<DialogContent>
|
|
406
|
+
<Typography level="body-sm">
|
|
407
|
+
Please read and accept the following terms and conditions before
|
|
408
|
+
proceeding...
|
|
409
|
+
</Typography>
|
|
410
|
+
{/* Terms content */}
|
|
411
|
+
</DialogContent>
|
|
412
|
+
<DialogActions>
|
|
413
|
+
<Button variant="solid" onClick={onAccept}>
|
|
414
|
+
Accept
|
|
415
|
+
</Button>
|
|
416
|
+
<Button variant="plain" color="neutral" onClick={onDecline}>
|
|
417
|
+
Decline
|
|
418
|
+
</Button>
|
|
419
|
+
</DialogActions>
|
|
420
|
+
</ModalDialog>
|
|
421
|
+
</Modal>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Image Preview Modal
|
|
427
|
+
|
|
428
|
+
Display images or media in a focused overlay.
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
function ImagePreviewModal({ image, open, onClose }) {
|
|
432
|
+
return (
|
|
433
|
+
<Modal open={open} onClose={onClose}>
|
|
434
|
+
<ModalDialog layout="center" sx={{ p: 0, overflow: 'hidden' }}>
|
|
435
|
+
<ModalClose sx={{ top: 8, right: 8, zIndex: 1 }} />
|
|
436
|
+
<img
|
|
437
|
+
src={image.src}
|
|
438
|
+
alt={image.alt}
|
|
439
|
+
style={{ maxWidth: '90vw', maxHeight: '90vh', objectFit: 'contain' }}
|
|
440
|
+
/>
|
|
441
|
+
</ModalDialog>
|
|
442
|
+
</Modal>
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Loading Modal
|
|
448
|
+
|
|
449
|
+
Block user interaction during async operations.
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
function LoadingModal({ open, message }) {
|
|
453
|
+
return (
|
|
454
|
+
<Modal open={open}>
|
|
455
|
+
<ModalDialog>
|
|
456
|
+
<Stack spacing={2} alignItems="center" sx={{ py: 2 }}>
|
|
457
|
+
<CircularProgress />
|
|
458
|
+
<Typography>{message || 'Processing...'}</Typography>
|
|
459
|
+
</Stack>
|
|
460
|
+
</ModalDialog>
|
|
461
|
+
</Modal>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Component Structure
|
|
467
|
+
|
|
468
|
+
Modal uses a composition pattern with multiple sub-components:
|
|
469
|
+
|
|
470
|
+
```tsx
|
|
471
|
+
<Modal> {/* Overlay and backdrop */}
|
|
472
|
+
<ModalDialog> {/* Dialog container */}
|
|
473
|
+
<ModalClose /> {/* Close button (optional) */}
|
|
474
|
+
<DialogTitle> {/* Header */}
|
|
475
|
+
Title
|
|
476
|
+
</DialogTitle>
|
|
477
|
+
<DialogContent> {/* Body */}
|
|
478
|
+
Content goes here
|
|
479
|
+
</DialogContent>
|
|
480
|
+
<DialogActions> {/* Footer */}
|
|
481
|
+
<Button>Action</Button>
|
|
482
|
+
</DialogActions>
|
|
483
|
+
</ModalDialog>
|
|
484
|
+
</Modal>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Props and Customization
|
|
488
|
+
|
|
489
|
+
### Modal Props
|
|
490
|
+
|
|
491
|
+
| Prop | Type | Default | Description |
|
|
492
|
+
| ---------------------- | ------------- | ------- | -------------------------------- |
|
|
493
|
+
| `open` | `boolean` | `false` | Controls modal visibility |
|
|
494
|
+
| `onClose` | `function` | - | Callback when modal should close |
|
|
495
|
+
| `disableEscapeKeyDown` | `boolean` | `false` | Disable closing on Escape key |
|
|
496
|
+
| `keepMounted` | `boolean` | `false` | Keep modal in DOM when closed |
|
|
497
|
+
| `container` | `HTMLElement` | - | Portal container element |
|
|
498
|
+
|
|
499
|
+
### ModalDialog Props
|
|
500
|
+
|
|
501
|
+
| Prop | Type | Default | Description |
|
|
502
|
+
| --------- | -------------------------------------------------------------- | ------------ | ------------ |
|
|
503
|
+
| `variant` | `'plain' \| 'outlined' \| 'soft' \| 'solid'` | `'outlined'` | Visual style |
|
|
504
|
+
| `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'neutral'` | Color scheme |
|
|
505
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Dialog size |
|
|
506
|
+
| `layout` | `'center' \| 'fullscreen'` | `'center'` | Layout mode |
|
|
507
|
+
|
|
508
|
+
### Custom Styling
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
511
|
+
<Modal
|
|
512
|
+
open={open}
|
|
513
|
+
onClose={onClose}
|
|
514
|
+
sx={{
|
|
515
|
+
// Customize backdrop
|
|
516
|
+
'& .MuiModal-backdrop': {
|
|
517
|
+
backdropFilter: 'blur(4px)',
|
|
518
|
+
},
|
|
519
|
+
}}
|
|
520
|
+
>
|
|
521
|
+
<ModalDialog
|
|
522
|
+
sx={{
|
|
523
|
+
minWidth: 400,
|
|
524
|
+
maxWidth: 600,
|
|
525
|
+
borderRadius: 'xl',
|
|
526
|
+
boxShadow: 'xl',
|
|
527
|
+
}}
|
|
528
|
+
>
|
|
529
|
+
{/* Content */}
|
|
530
|
+
</ModalDialog>
|
|
531
|
+
</Modal>
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Accessibility
|
|
535
|
+
|
|
536
|
+
Modal components include comprehensive accessibility features:
|
|
537
|
+
|
|
538
|
+
### ARIA Attributes
|
|
539
|
+
|
|
540
|
+
- `role="dialog"` is automatically applied to ModalDialog
|
|
541
|
+
- Use `role="alertdialog"` for confirmation dialogs requiring user response
|
|
542
|
+
- `aria-labelledby` connects to DialogTitle
|
|
543
|
+
- `aria-describedby` connects to DialogContent
|
|
544
|
+
|
|
545
|
+
```tsx
|
|
546
|
+
<Modal
|
|
547
|
+
open={open}
|
|
548
|
+
onClose={onClose}
|
|
549
|
+
aria-labelledby="modal-title"
|
|
550
|
+
aria-describedby="modal-description"
|
|
551
|
+
>
|
|
552
|
+
<ModalDialog>
|
|
553
|
+
<DialogTitle id="modal-title">Accessible Title</DialogTitle>
|
|
554
|
+
<DialogContent id="modal-description">
|
|
555
|
+
This content is read by screen readers.
|
|
556
|
+
</DialogContent>
|
|
557
|
+
</ModalDialog>
|
|
558
|
+
</Modal>
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Keyboard Navigation
|
|
562
|
+
|
|
563
|
+
- **Escape**: Closes the modal (unless `disableEscapeKeyDown` is true)
|
|
564
|
+
- **Tab**: Cycles through focusable elements within the modal
|
|
565
|
+
- **Focus trapping**: Focus is automatically trapped within the modal
|
|
566
|
+
|
|
567
|
+
### Screen Reader Support
|
|
568
|
+
|
|
569
|
+
- Modal announces its presence when opened
|
|
570
|
+
- ModalClose should include `aria-label` for screen readers
|
|
571
|
+
|
|
572
|
+
```tsx
|
|
573
|
+
<ModalClose aria-label="Close dialog" />
|
|
39
574
|
```
|
|
575
|
+
|
|
576
|
+
## Best Practices
|
|
577
|
+
|
|
578
|
+
### ✅ Do
|
|
579
|
+
|
|
580
|
+
1. **Provide clear actions**: Always include explicit action buttons for user decisions
|
|
581
|
+
|
|
582
|
+
```tsx
|
|
583
|
+
// ✅ Good: Clear action buttons
|
|
584
|
+
<DialogActions>
|
|
585
|
+
<Button variant="solid" color="danger">Delete</Button>
|
|
586
|
+
<Button variant="plain">Cancel</Button>
|
|
587
|
+
</DialogActions>
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
2. **Use appropriate variants**: Match the visual style to the action severity
|
|
591
|
+
|
|
592
|
+
```tsx
|
|
593
|
+
// ✅ Good: Danger color for destructive actions
|
|
594
|
+
<ModalDialog color="danger" variant="outlined">
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
3. **Keep content focused**: Modals should contain only essential information
|
|
598
|
+
|
|
599
|
+
4. **Handle close events**: Always provide multiple ways to close (X button, backdrop click, Escape key)
|
|
600
|
+
|
|
601
|
+
### ❌ Don't
|
|
602
|
+
|
|
603
|
+
1. **Don't nest complex navigation**: Avoid wizards or multi-step flows in modals
|
|
604
|
+
|
|
605
|
+
```tsx
|
|
606
|
+
// ❌ Bad: Complex multi-step wizard in modal
|
|
607
|
+
<ModalDialog>
|
|
608
|
+
<Stepper activeStep={step}>
|
|
609
|
+
<Step>Step 1</Step>
|
|
610
|
+
<Step>Step 2</Step>
|
|
611
|
+
<Step>Step 3</Step>
|
|
612
|
+
</Stepper>
|
|
613
|
+
</ModalDialog>
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
2. **Don't overuse modals**: Frequent interruptions frustrate users
|
|
617
|
+
|
|
618
|
+
3. **Don't hide important errors in modals**: Use inline validation for form errors
|
|
619
|
+
|
|
620
|
+
4. **Don't make modals too large**: If content requires scrolling extensively, use a dedicated page
|
|
621
|
+
|
|
622
|
+
## Performance Considerations
|
|
623
|
+
|
|
624
|
+
### Lazy Loading
|
|
625
|
+
|
|
626
|
+
For modals with heavy content, consider lazy loading:
|
|
627
|
+
|
|
628
|
+
```tsx
|
|
629
|
+
const HeavyContent = lazy(() => import('./HeavyContent'));
|
|
630
|
+
|
|
631
|
+
function OptimizedModal({ open, onClose }) {
|
|
632
|
+
return (
|
|
633
|
+
<Modal open={open} onClose={onClose}>
|
|
634
|
+
<ModalDialog>
|
|
635
|
+
<Suspense fallback={<CircularProgress />}>
|
|
636
|
+
<HeavyContent />
|
|
637
|
+
</Suspense>
|
|
638
|
+
</ModalDialog>
|
|
639
|
+
</Modal>
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Keep Mounted
|
|
645
|
+
|
|
646
|
+
Use `keepMounted` only when the modal needs to preserve state between openings:
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
// Only use when necessary
|
|
650
|
+
<Modal keepMounted open={open} onClose={onClose}>
|
|
651
|
+
{/* Content that needs to maintain state */}
|
|
652
|
+
</Modal>
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Avoid Unnecessary Re-renders
|
|
656
|
+
|
|
657
|
+
Memoize modal content when it depends on complex data:
|
|
658
|
+
|
|
659
|
+
```tsx
|
|
660
|
+
const modalContent = useMemo(() => (
|
|
661
|
+
<ComplexContent data={data} />
|
|
662
|
+
), [data]);
|
|
663
|
+
|
|
664
|
+
<Modal open={open} onClose={onClose}>
|
|
665
|
+
<ModalDialog>
|
|
666
|
+
{modalContent}
|
|
667
|
+
</ModalDialog>
|
|
668
|
+
</Modal>
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Modal is a powerful component for focused user interactions. Use it thoughtfully to maintain a smooth user experience while capturing important decisions and inputs.
|