@discourser/design-system 0.3.0 → 0.4.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/guidelines/Guidelines.md +195 -0
- package/guidelines/components/accordion.md +639 -0
- package/guidelines/components/avatar.md +945 -0
- package/guidelines/components/badge.md +667 -0
- package/guidelines/components/button.md +314 -0
- package/guidelines/components/card.md +353 -0
- package/guidelines/components/checkbox.md +583 -0
- package/guidelines/components/dialog.md +465 -0
- package/guidelines/components/drawer.md +961 -0
- package/guidelines/components/heading.md +505 -0
- package/guidelines/components/icon-button.md +417 -0
- package/guidelines/components/input.md +499 -0
- package/guidelines/components/popover.md +1200 -0
- package/guidelines/components/progress.md +773 -0
- package/guidelines/components/radio-group.md +757 -0
- package/guidelines/components/select.md +1155 -0
- package/guidelines/components/skeleton.md +726 -0
- package/guidelines/components/switch.md +457 -0
- package/guidelines/components/tabs.md +834 -0
- package/guidelines/components/textarea.md +425 -0
- package/guidelines/components/toast.md +707 -0
- package/guidelines/components/tooltip.md +832 -0
- package/guidelines/design-tokens/colors.md +187 -0
- package/guidelines/design-tokens/elevation.md +274 -0
- package/guidelines/design-tokens/spacing.md +289 -0
- package/guidelines/design-tokens/typography.md +226 -0
- package/guidelines/overview-components.md +204 -0
- package/package.json +3 -2
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# Dialog
|
|
2
|
+
|
|
3
|
+
**Purpose:** Modal overlay component for focused tasks, confirmations, and important information following Material Design 3 patterns.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Dialog } from '@discourser/design-system';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
The Dialog component creates modal overlays that:
|
|
14
|
+
- Block interaction with the background content
|
|
15
|
+
- Display a semi-transparent backdrop (scrim)
|
|
16
|
+
- Center content on screen
|
|
17
|
+
- Support keyboard navigation (Escape to close)
|
|
18
|
+
- Manage focus automatically
|
|
19
|
+
- Prevent scroll on background content
|
|
20
|
+
|
|
21
|
+
## Sizes
|
|
22
|
+
|
|
23
|
+
| Size | Width | Min Height | Usage |
|
|
24
|
+
|------|-------|-----------|-------|
|
|
25
|
+
| `sm` | 280px | 140px | Simple confirmations, alerts |
|
|
26
|
+
| `md` | 560px | 200px | Default, most dialogs |
|
|
27
|
+
| `lg` | 800px | 300px | Forms, detailed content |
|
|
28
|
+
| `fullscreen` | 100vw × 100vh | - | Mobile-optimized, complex flows |
|
|
29
|
+
|
|
30
|
+
## Props
|
|
31
|
+
|
|
32
|
+
| Prop | Type | Default | Description |
|
|
33
|
+
|------|------|---------|-------------|
|
|
34
|
+
| `open` | `boolean` | - | Whether the dialog is open (controlled) |
|
|
35
|
+
| `onOpenChange` | `(details: { open: boolean }) => void` | - | Callback when open state changes |
|
|
36
|
+
| `title` | `string` | - | Dialog title (headlineSmall) |
|
|
37
|
+
| `description` | `string` | - | Dialog description/content (bodyMedium) |
|
|
38
|
+
| `children` | `ReactNode` | - | Custom dialog content (alternative to description) |
|
|
39
|
+
| `size` | `'sm' \| 'md' \| 'lg' \| 'fullscreen'` | `'md'` | Dialog size |
|
|
40
|
+
| `showCloseButton` | `boolean` | `true` | Whether to show the close button |
|
|
41
|
+
| `closeLabel` | `string` | `'Close'` | Accessible label for close button |
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
### Basic Usage
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const [open, setOpen] = useState(false);
|
|
49
|
+
|
|
50
|
+
// Simple confirmation dialog
|
|
51
|
+
<>
|
|
52
|
+
<Button onClick={() => setOpen(true)}>Open Dialog</Button>
|
|
53
|
+
|
|
54
|
+
<Dialog
|
|
55
|
+
open={open}
|
|
56
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
57
|
+
title="Confirm Action"
|
|
58
|
+
description="Are you sure you want to proceed? This action cannot be undone."
|
|
59
|
+
/>
|
|
60
|
+
</>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### With Custom Content
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const [open, setOpen] = useState(false);
|
|
67
|
+
|
|
68
|
+
<Dialog
|
|
69
|
+
open={open}
|
|
70
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
71
|
+
title="Create New Project"
|
|
72
|
+
size="md"
|
|
73
|
+
>
|
|
74
|
+
<div className={css({ p: 'lg', display: 'flex', flexDirection: 'column', gap: 'md' })}>
|
|
75
|
+
<Input label="Project Name" />
|
|
76
|
+
<Input label="Description" />
|
|
77
|
+
<div className={css({ display: 'flex', gap: 'sm', justifyContent: 'flex-end', mt: 'md' })}>
|
|
78
|
+
<Button variant="text" onClick={() => setOpen(false)}>
|
|
79
|
+
Cancel
|
|
80
|
+
</Button>
|
|
81
|
+
<Button variant="filled" onClick={handleCreate}>
|
|
82
|
+
Create
|
|
83
|
+
</Button>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</Dialog>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Different Sizes
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// Small (alerts, confirmations)
|
|
93
|
+
<Dialog
|
|
94
|
+
open={open}
|
|
95
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
96
|
+
size="sm"
|
|
97
|
+
title="Delete Item?"
|
|
98
|
+
description="This action cannot be undone."
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
// Medium (default)
|
|
102
|
+
<Dialog
|
|
103
|
+
open={open}
|
|
104
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
105
|
+
size="md"
|
|
106
|
+
title="Edit Profile"
|
|
107
|
+
>
|
|
108
|
+
{/* Form content */}
|
|
109
|
+
</Dialog>
|
|
110
|
+
|
|
111
|
+
// Large (forms, detailed content)
|
|
112
|
+
<Dialog
|
|
113
|
+
open={open}
|
|
114
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
115
|
+
size="lg"
|
|
116
|
+
title="Settings"
|
|
117
|
+
>
|
|
118
|
+
{/* Complex settings UI */}
|
|
119
|
+
</Dialog>
|
|
120
|
+
|
|
121
|
+
// Fullscreen (mobile-optimized)
|
|
122
|
+
<Dialog
|
|
123
|
+
open={open}
|
|
124
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
125
|
+
size="fullscreen"
|
|
126
|
+
title="Full Editor"
|
|
127
|
+
>
|
|
128
|
+
{/* Full-page editor */}
|
|
129
|
+
</Dialog>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Without Close Button
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
<Dialog
|
|
136
|
+
open={open}
|
|
137
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
138
|
+
title="Please Wait"
|
|
139
|
+
description="Processing your request..."
|
|
140
|
+
showCloseButton={false}
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Common Patterns
|
|
145
|
+
|
|
146
|
+
### Confirmation Dialog
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
150
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
151
|
+
|
|
152
|
+
const handleDelete = async () => {
|
|
153
|
+
setIsDeleting(true);
|
|
154
|
+
await deleteItem();
|
|
155
|
+
setIsDeleting(false);
|
|
156
|
+
setDeleteDialogOpen(false);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
<Dialog
|
|
160
|
+
open={deleteDialogOpen}
|
|
161
|
+
onOpenChange={({ open }) => setDeleteDialogOpen(open)}
|
|
162
|
+
title="Delete Item?"
|
|
163
|
+
size="sm"
|
|
164
|
+
>
|
|
165
|
+
<div className={css({ p: 'lg' })}>
|
|
166
|
+
<p className={css({ textStyle: 'bodyMedium', color: 'onSurfaceVariant', mb: 'lg' })}>
|
|
167
|
+
This action cannot be undone. The item will be permanently deleted.
|
|
168
|
+
</p>
|
|
169
|
+
<div className={css({ display: 'flex', gap: 'sm', justifyContent: 'flex-end' })}>
|
|
170
|
+
<Button variant="text" onClick={() => setDeleteDialogOpen(false)}>
|
|
171
|
+
Cancel
|
|
172
|
+
</Button>
|
|
173
|
+
<Button variant="filled" onClick={handleDelete} disabled={isDeleting}>
|
|
174
|
+
{isDeleting ? 'Deleting...' : 'Delete'}
|
|
175
|
+
</Button>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</Dialog>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Form Dialog
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const [open, setOpen] = useState(false);
|
|
185
|
+
const [formData, setFormData] = useState({ name: '', email: '' });
|
|
186
|
+
|
|
187
|
+
const handleSubmit = (e: FormEvent) => {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
// Process form
|
|
190
|
+
setOpen(false);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
<Dialog
|
|
194
|
+
open={open}
|
|
195
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
196
|
+
title="Contact Information"
|
|
197
|
+
size="md"
|
|
198
|
+
>
|
|
199
|
+
<form onSubmit={handleSubmit}>
|
|
200
|
+
<div className={css({ p: 'lg', display: 'flex', flexDirection: 'column', gap: 'md' })}>
|
|
201
|
+
<Input
|
|
202
|
+
label="Name"
|
|
203
|
+
value={formData.name}
|
|
204
|
+
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
205
|
+
required
|
|
206
|
+
/>
|
|
207
|
+
<Input
|
|
208
|
+
label="Email"
|
|
209
|
+
type="email"
|
|
210
|
+
value={formData.email}
|
|
211
|
+
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
|
212
|
+
required
|
|
213
|
+
/>
|
|
214
|
+
<div className={css({ display: 'flex', gap: 'sm', justifyContent: 'flex-end', mt: 'md' })}>
|
|
215
|
+
<Button type="button" variant="text" onClick={() => setOpen(false)}>
|
|
216
|
+
Cancel
|
|
217
|
+
</Button>
|
|
218
|
+
<Button type="submit" variant="filled">
|
|
219
|
+
Save
|
|
220
|
+
</Button>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</form>
|
|
224
|
+
</Dialog>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Alert Dialog
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
<Dialog
|
|
231
|
+
open={alertOpen}
|
|
232
|
+
onOpenChange={({ open }) => setAlertOpen(open)}
|
|
233
|
+
title="Success!"
|
|
234
|
+
size="sm"
|
|
235
|
+
>
|
|
236
|
+
<div className={css({ p: 'lg' })}>
|
|
237
|
+
<p className={css({ textStyle: 'bodyMedium', color: 'onSurfaceVariant', mb: 'lg' })}>
|
|
238
|
+
Your changes have been saved successfully.
|
|
239
|
+
</p>
|
|
240
|
+
<div className={css({ display: 'flex', justifyContent: 'flex-end' })}>
|
|
241
|
+
<Button variant="filled" onClick={() => setAlertOpen(false)}>
|
|
242
|
+
OK
|
|
243
|
+
</Button>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</Dialog>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Loading Dialog
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
<Dialog
|
|
253
|
+
open={loading}
|
|
254
|
+
onOpenChange={() => {}} // Cannot close while loading
|
|
255
|
+
title="Processing"
|
|
256
|
+
showCloseButton={false}
|
|
257
|
+
size="sm"
|
|
258
|
+
>
|
|
259
|
+
<div className={css({ p: 'lg', textAlign: 'center' })}>
|
|
260
|
+
<Spinner className={css({ mb: 'md' })} />
|
|
261
|
+
<p className={css({ textStyle: 'bodyMedium', color: 'onSurfaceVariant' })}>
|
|
262
|
+
Please wait while we process your request...
|
|
263
|
+
</p>
|
|
264
|
+
</div>
|
|
265
|
+
</Dialog>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## DO NOT
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// ❌ Don't use div or custom modal (use Dialog component)
|
|
272
|
+
<div className="modal-overlay">
|
|
273
|
+
<div className="modal-content">...</div>
|
|
274
|
+
</div> // Use <Dialog> instead
|
|
275
|
+
|
|
276
|
+
// ❌ Don't forget controlled state
|
|
277
|
+
<Dialog title="Test">...</Dialog> // Missing open/onOpenChange
|
|
278
|
+
|
|
279
|
+
// ✅ Always control the dialog state
|
|
280
|
+
<Dialog
|
|
281
|
+
open={open}
|
|
282
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
283
|
+
title="Test"
|
|
284
|
+
>
|
|
285
|
+
...
|
|
286
|
+
</Dialog>
|
|
287
|
+
|
|
288
|
+
// ❌ Don't nest dialogs
|
|
289
|
+
<Dialog open={open1}>
|
|
290
|
+
<Dialog open={open2}>...</Dialog> // Avoid nested dialogs
|
|
291
|
+
</Dialog>
|
|
292
|
+
|
|
293
|
+
// ❌ Don't override backdrop/scrim styles heavily
|
|
294
|
+
<Dialog
|
|
295
|
+
style={{ backgroundColor: 'red' }} // Don't do this
|
|
296
|
+
title="Test"
|
|
297
|
+
/>
|
|
298
|
+
|
|
299
|
+
// ❌ Don't use fullscreen for simple confirmations
|
|
300
|
+
<Dialog size="fullscreen" title="Delete item?"> // Overkill
|
|
301
|
+
...
|
|
302
|
+
</Dialog>
|
|
303
|
+
|
|
304
|
+
// ✅ Use appropriate sizes
|
|
305
|
+
<Dialog size="sm" title="Delete item?">
|
|
306
|
+
...
|
|
307
|
+
</Dialog>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Accessibility
|
|
311
|
+
|
|
312
|
+
The Dialog component follows WCAG 2.1 Level AA standards:
|
|
313
|
+
|
|
314
|
+
- **Focus Management**: Automatically traps focus within dialog
|
|
315
|
+
- **Keyboard Navigation**: Escape key closes dialog, Tab cycles through elements
|
|
316
|
+
- **ARIA Attributes**: Proper role, aria-modal, aria-labelledby applied
|
|
317
|
+
- **Focus Restoration**: Returns focus to trigger element on close
|
|
318
|
+
- **Screen Reader Support**: Title and description properly associated
|
|
319
|
+
|
|
320
|
+
### Accessibility Best Practices
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// ✅ Always provide a title
|
|
324
|
+
<Dialog
|
|
325
|
+
open={open}
|
|
326
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
327
|
+
title="Confirm Deletion" // Required for accessibility
|
|
328
|
+
>
|
|
329
|
+
...
|
|
330
|
+
</Dialog>
|
|
331
|
+
|
|
332
|
+
// ✅ Provide descriptive close button label
|
|
333
|
+
<Dialog
|
|
334
|
+
open={open}
|
|
335
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
336
|
+
title="Settings"
|
|
337
|
+
closeLabel="Close settings dialog"
|
|
338
|
+
>
|
|
339
|
+
...
|
|
340
|
+
</Dialog>
|
|
341
|
+
|
|
342
|
+
// ✅ Use semantic button elements for actions
|
|
343
|
+
<Dialog open={open} onOpenChange={({ open }) => setOpen(open)} title="Confirm">
|
|
344
|
+
<div className={css({ p: 'lg' })}>
|
|
345
|
+
<p>Are you sure?</p>
|
|
346
|
+
<Button variant="text">Cancel</Button> {/* Semantic button */}
|
|
347
|
+
<Button variant="filled">Confirm</Button>
|
|
348
|
+
</div>
|
|
349
|
+
</Dialog>
|
|
350
|
+
|
|
351
|
+
// ✅ Ensure proper reading order
|
|
352
|
+
// Dialog content should follow logical reading order (title → description → actions)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Size Selection Guide
|
|
356
|
+
|
|
357
|
+
| Scenario | Recommended Size | Reasoning |
|
|
358
|
+
|----------|-----------------|-----------|
|
|
359
|
+
| Simple confirmation | `sm` | Minimal content, quick decision |
|
|
360
|
+
| Alerts | `sm` | Brief message, single action |
|
|
361
|
+
| Forms (2-3 fields) | `md` | Standard forms, most common |
|
|
362
|
+
| Settings/Preferences | `lg` | Multiple sections, complex UI |
|
|
363
|
+
| Mobile editor/viewer | `fullscreen` | Maximize screen space |
|
|
364
|
+
| Multi-step wizard | `lg` or `fullscreen` | Complex flow needs space |
|
|
365
|
+
|
|
366
|
+
## State Behaviors
|
|
367
|
+
|
|
368
|
+
| State | Visual Change | Behavior |
|
|
369
|
+
|-------|---------------|----------|
|
|
370
|
+
| **Opening** | Fade in + scale animation | Backdrop fades, content scales up |
|
|
371
|
+
| **Open** | Fully visible | Modal state, focus trapped |
|
|
372
|
+
| **Closing** | Fade out + scale animation | Backdrop fades, content scales down |
|
|
373
|
+
| **Backdrop Click** | Closes dialog | Click outside closes (default Ark UI behavior) |
|
|
374
|
+
| **Escape Key** | Closes dialog | Keyboard shortcut to close |
|
|
375
|
+
|
|
376
|
+
## Backdrop Behavior
|
|
377
|
+
|
|
378
|
+
The backdrop (scrim) behind the dialog:
|
|
379
|
+
- Uses `scrim` color token (#000000)
|
|
380
|
+
- 40% opacity
|
|
381
|
+
- Blocks all background interactions
|
|
382
|
+
- Clicking backdrop closes dialog (can be disabled via Ark UI props)
|
|
383
|
+
|
|
384
|
+
## Focus Management
|
|
385
|
+
|
|
386
|
+
The Dialog component automatically:
|
|
387
|
+
1. **Traps focus** within the dialog when open
|
|
388
|
+
2. **Focuses first focusable element** when opened (typically close button or first input)
|
|
389
|
+
3. **Restores focus** to the trigger element when closed
|
|
390
|
+
4. **Prevents Tab** from leaving the dialog
|
|
391
|
+
|
|
392
|
+
## Responsive Considerations
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// Mobile: Use fullscreen for complex dialogs
|
|
396
|
+
<Dialog
|
|
397
|
+
size={{ base: 'fullscreen', md: 'md' }}
|
|
398
|
+
open={open}
|
|
399
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
400
|
+
title="Settings"
|
|
401
|
+
>
|
|
402
|
+
{/* Complex settings */}
|
|
403
|
+
</Dialog>
|
|
404
|
+
|
|
405
|
+
// Mobile: Reduce to sm for simple dialogs
|
|
406
|
+
<Dialog
|
|
407
|
+
size={{ base: 'sm', md: 'md' }}
|
|
408
|
+
open={open}
|
|
409
|
+
onOpenChange={({ open }) => setOpen(open)}
|
|
410
|
+
title="Confirm"
|
|
411
|
+
>
|
|
412
|
+
{/* Simple confirmation */}
|
|
413
|
+
</Dialog>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Testing
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import { render, screen } from '@testing-library/react';
|
|
420
|
+
import userEvent from '@testing-library/user-event';
|
|
421
|
+
|
|
422
|
+
test('dialog opens and closes', async () => {
|
|
423
|
+
const { rerender } = render(
|
|
424
|
+
<Dialog open={false} onOpenChange={() => {}} title="Test">
|
|
425
|
+
Dialog content
|
|
426
|
+
</Dialog>
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
expect(screen.queryByText('Dialog content')).not.toBeInTheDocument();
|
|
430
|
+
|
|
431
|
+
rerender(
|
|
432
|
+
<Dialog open={true} onOpenChange={() => {}} title="Test">
|
|
433
|
+
Dialog content
|
|
434
|
+
</Dialog>
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
expect(screen.getByText('Dialog content')).toBeInTheDocument();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('dialog close button triggers onOpenChange', async () => {
|
|
441
|
+
const handleOpenChange = vi.fn();
|
|
442
|
+
render(
|
|
443
|
+
<Dialog open={true} onOpenChange={handleOpenChange} title="Test">
|
|
444
|
+
Content
|
|
445
|
+
</Dialog>
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
const closeButton = screen.getByLabelText('Close');
|
|
449
|
+
await userEvent.click(closeButton);
|
|
450
|
+
|
|
451
|
+
expect(handleOpenChange).toHaveBeenCalledWith({ open: false });
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('dialog has proper ARIA attributes', () => {
|
|
455
|
+
render(
|
|
456
|
+
<Dialog open={true} onOpenChange={() => {}} title="Test Dialog">
|
|
457
|
+
Content
|
|
458
|
+
</Dialog>
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const dialog = screen.getByRole('dialog');
|
|
462
|
+
expect(dialog).toHaveAttribute('aria-modal', 'true');
|
|
463
|
+
expect(screen.getByText('Test Dialog')).toBeInTheDocument();
|
|
464
|
+
});
|
|
465
|
+
```
|