@a13y/react 0.1.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,539 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Dialog in the stack
6
+ */
7
+ interface StackedDialog {
8
+ id: string;
9
+ title: string;
10
+ content: ReactNode;
11
+ description?: string;
12
+ onClose: () => void;
13
+ zIndex: number;
14
+ }
15
+ /**
16
+ * Dialog stack context
17
+ */
18
+ interface DialogStackContextValue {
19
+ /**
20
+ * Push a new dialog onto the stack
21
+ */
22
+ push: (dialog: Omit<StackedDialog, 'zIndex'>) => void;
23
+ /**
24
+ * Pop the topmost dialog
25
+ */
26
+ pop: () => void;
27
+ /**
28
+ * Close a specific dialog by ID
29
+ */
30
+ close: (id: string) => void;
31
+ /**
32
+ * Close all dialogs
33
+ */
34
+ closeAll: () => void;
35
+ /**
36
+ * Current stack depth
37
+ */
38
+ depth: number;
39
+ }
40
+ /**
41
+ * Hook to access dialog stack
42
+ */
43
+ declare const useDialogStack: () => DialogStackContextValue;
44
+ /**
45
+ * Props for DialogStackProvider
46
+ */
47
+ interface DialogStackProviderProps {
48
+ children: ReactNode;
49
+ /**
50
+ * Base z-index for dialogs
51
+ * @default 1000
52
+ */
53
+ baseZIndex?: number;
54
+ /**
55
+ * Z-index increment between layers
56
+ * @default 10
57
+ */
58
+ zIndexIncrement?: number;
59
+ }
60
+ /**
61
+ * Dialog Stack Provider
62
+ *
63
+ * Manages a stack of nested dialogs with:
64
+ * - Automatic z-index management
65
+ * - Focus restoration chain
66
+ * - Escape key closes topmost dialog only
67
+ * - Backdrop isolation per layer
68
+ *
69
+ * Pattern Explanation:
70
+ * - Each dialog gets a unique z-index: base + (depth * increment)
71
+ * - Focus is trapped in the topmost dialog
72
+ * - When a dialog closes, focus returns to the previous dialog
73
+ * - Escape key only affects the topmost dialog
74
+ * - Body scroll is locked when any dialog is open
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * function App() {
79
+ * return (
80
+ * <DialogStackProvider>
81
+ * <MyComponent />
82
+ * </DialogStackProvider>
83
+ * );
84
+ * }
85
+ *
86
+ * function MyComponent() {
87
+ * const { push, pop } = useDialogStack();
88
+ *
89
+ * return (
90
+ * <button onClick={() => push({
91
+ * id: 'dialog1',
92
+ * title: 'First Dialog',
93
+ * content: <SecondLevelDialog />,
94
+ * onClose: () => pop(),
95
+ * })}>
96
+ * Open Dialog
97
+ * </button>
98
+ * );
99
+ * }
100
+ *
101
+ * function SecondLevelDialog() {
102
+ * const { push, pop } = useDialogStack();
103
+ *
104
+ * return (
105
+ * <button onClick={() => push({
106
+ * id: 'dialog2',
107
+ * title: 'Nested Dialog',
108
+ * content: <p>This is nested!</p>,
109
+ * onClose: () => pop(),
110
+ * })}>
111
+ * Open Nested Dialog
112
+ * </button>
113
+ * );
114
+ * }
115
+ * ```
116
+ */
117
+ declare const DialogStackProvider: (props: DialogStackProviderProps) => react_jsx_runtime.JSX.Element;
118
+
119
+ /**
120
+ * Props for InfiniteList
121
+ */
122
+ interface InfiniteListProps<T> {
123
+ /**
124
+ * Current items in the list
125
+ */
126
+ items: T[];
127
+ /**
128
+ * Function to load more items
129
+ * Should return a promise that resolves to new items
130
+ */
131
+ loadMore: () => Promise<T[]>;
132
+ /**
133
+ * Check if there are more items to load
134
+ */
135
+ hasMore: boolean;
136
+ /**
137
+ * Check if currently loading
138
+ */
139
+ isLoading: boolean;
140
+ /**
141
+ * Render function for each item
142
+ */
143
+ renderItem: (item: T, index: number) => ReactNode;
144
+ /**
145
+ * Unique key extractor
146
+ */
147
+ getItemKey: (item: T, index: number) => string;
148
+ /**
149
+ * Loading indicator
150
+ */
151
+ loadingIndicator?: ReactNode;
152
+ /**
153
+ * Empty state
154
+ */
155
+ emptyState?: ReactNode;
156
+ /**
157
+ * Accessible label for the list
158
+ */
159
+ 'aria-label': string;
160
+ /**
161
+ * Distance from bottom to trigger load (px)
162
+ * @default 200
163
+ */
164
+ threshold?: number;
165
+ /**
166
+ * Custom className
167
+ */
168
+ className?: string;
169
+ }
170
+ /**
171
+ * Infinite List Component
172
+ *
173
+ * Accessible infinite scroll with:
174
+ * - Intersection Observer for lazy loading
175
+ * - Screen reader announcements for new items
176
+ * - Keyboard navigation (Tab through items)
177
+ * - Loading states announced
178
+ * - Total count announcements
179
+ *
180
+ * Pattern Explanation:
181
+ * - Uses Intersection Observer to detect when user scrolls near bottom
182
+ * - Announces new items loaded to screen readers
183
+ * - Maintains focus position when new items are added
184
+ * - Works with keyboard navigation (no mouse required)
185
+ * - Provides loading and empty states
186
+ *
187
+ * @example
188
+ * ```tsx
189
+ * const [items, setItems] = useState<Item[]>([]);
190
+ * const [hasMore, setHasMore] = useState(true);
191
+ * const [isLoading, setIsLoading] = useState(false);
192
+ *
193
+ * const loadMore = async () => {
194
+ * setIsLoading(true);
195
+ * const newItems = await fetchItems(items.length, 20);
196
+ * setItems([...items, ...newItems]);
197
+ * setHasMore(newItems.length === 20);
198
+ * setIsLoading(false);
199
+ * return newItems;
200
+ * };
201
+ *
202
+ * return (
203
+ * <InfiniteList
204
+ * items={items}
205
+ * loadMore={loadMore}
206
+ * hasMore={hasMore}
207
+ * isLoading={isLoading}
208
+ * renderItem={(item) => <ItemCard item={item} />}
209
+ * getItemKey={(item) => item.id}
210
+ * aria-label="Products list"
211
+ * />
212
+ * );
213
+ * ```
214
+ */
215
+ declare const InfiniteList: <T>(props: InfiniteListProps<T>) => react_jsx_runtime.JSX.Element;
216
+
217
+ /**
218
+ * Menu item with optional submenu
219
+ */
220
+ interface NestedMenuItem {
221
+ id: string;
222
+ label: string;
223
+ icon?: ReactNode;
224
+ disabled?: boolean;
225
+ /**
226
+ * Action when item is selected
227
+ * Omit for items with submenu
228
+ */
229
+ onPress?: () => void;
230
+ /**
231
+ * Submenu items
232
+ */
233
+ submenu?: NestedMenuItem[];
234
+ }
235
+ /**
236
+ * Props for NestedMenu
237
+ */
238
+ interface NestedMenuProps {
239
+ /**
240
+ * Menu trigger label for screen readers
241
+ */
242
+ label: string;
243
+ /**
244
+ * Trigger content
245
+ */
246
+ trigger: ReactNode;
247
+ /**
248
+ * Menu items (can have nested submenus)
249
+ */
250
+ items: NestedMenuItem[];
251
+ /**
252
+ * Custom className for trigger
253
+ */
254
+ className?: string;
255
+ }
256
+ /**
257
+ * Nested Menu Component
258
+ *
259
+ * Multi-level dropdown menu with full keyboard navigation:
260
+ * - Arrow Up/Down: Navigate items
261
+ * - Arrow Right: Open submenu
262
+ * - Arrow Left: Close submenu and return to parent
263
+ * - Enter/Space: Select item or open submenu
264
+ * - Escape: Close current menu level
265
+ *
266
+ * Pattern Explanation:
267
+ * - Each submenu level maintains its own navigation state
268
+ * - Arrow Right on item with submenu opens it
269
+ * - Arrow Left closes submenu and returns focus to parent item
270
+ * - Screen readers announce submenu availability via aria-haspopup
271
+ * - Submenus are positioned relative to parent items
272
+ * - Focus is trapped within the active menu level
273
+ *
274
+ * @example
275
+ * ```tsx
276
+ * <NestedMenu
277
+ * label="File menu"
278
+ * trigger="File ▼"
279
+ * items={[
280
+ * {
281
+ * id: 'new',
282
+ * label: 'New',
283
+ * submenu: [
284
+ * { id: 'file', label: 'File', onPress: () => console.log('New File') },
285
+ * { id: 'folder', label: 'Folder', onPress: () => console.log('New Folder') },
286
+ * ],
287
+ * },
288
+ * { id: 'open', label: 'Open', onPress: () => console.log('Open') },
289
+ * {
290
+ * id: 'recent',
291
+ * label: 'Open Recent',
292
+ * submenu: [
293
+ * { id: 'file1', label: 'document.txt', onPress: () => {} },
294
+ * { id: 'file2', label: 'project.json', onPress: () => {} },
295
+ * ],
296
+ * },
297
+ * ]}
298
+ * />
299
+ * ```
300
+ */
301
+ declare const NestedMenu: (props: NestedMenuProps) => react_jsx_runtime.JSX.Element;
302
+
303
+ /**
304
+ * Props for VirtualizedList
305
+ */
306
+ interface VirtualizedListProps<T> {
307
+ /**
308
+ * All items in the list
309
+ */
310
+ items: T[];
311
+ /**
312
+ * Height of each item (fixed)
313
+ */
314
+ itemHeight: number;
315
+ /**
316
+ * Height of the visible container
317
+ */
318
+ height: number;
319
+ /**
320
+ * Render function for each item
321
+ */
322
+ renderItem: (item: T, index: number) => ReactNode;
323
+ /**
324
+ * Unique key extractor
325
+ */
326
+ getItemKey: (item: T, index: number) => string;
327
+ /**
328
+ * Accessible label for the list
329
+ */
330
+ 'aria-label': string;
331
+ /**
332
+ * Number of items to render outside viewport (overscan)
333
+ * @default 3
334
+ */
335
+ overscan?: number;
336
+ /**
337
+ * Custom className
338
+ */
339
+ className?: string;
340
+ /**
341
+ * Empty state
342
+ */
343
+ emptyState?: ReactNode;
344
+ }
345
+ /**
346
+ * Virtualized List Component
347
+ *
348
+ * Accessible virtualized list that works with screen readers:
349
+ * - Only renders visible items + overscan
350
+ * - Maintains total height for scrollbar
351
+ * - Announces visible range to screen readers
352
+ * - Keyboard navigation works correctly
353
+ * - Focus management when scrolling
354
+ *
355
+ * Pattern Explanation:
356
+ * - Calculates visible range based on scroll position
357
+ * - Renders only visible items + overscan buffer
358
+ * - Uses absolute positioning to maintain item positions
359
+ * - Announces visible range changes to screen readers
360
+ * - Total height maintained for accurate scrollbar
361
+ * - Works with keyboard navigation (Page Up/Down, Home/End)
362
+ *
363
+ * Screen Reader Strategy:
364
+ * - aria-setsize and aria-posinset tell screen readers total count
365
+ * - Visible range announced when user scrolls
366
+ * - Focus is maintained when items are virtualized
367
+ *
368
+ * @example
369
+ * ```tsx
370
+ * const items = Array.from({ length: 10000 }, (_, i) => ({
371
+ * id: i,
372
+ * name: `Item ${i}`,
373
+ * }));
374
+ *
375
+ * return (
376
+ * <VirtualizedList
377
+ * items={items}
378
+ * itemHeight={48}
379
+ * height={600}
380
+ * renderItem={(item) => (
381
+ * <div style={{ padding: '0.75rem' }}>
382
+ * {item.name}
383
+ * </div>
384
+ * )}
385
+ * getItemKey={(item) => item.id.toString()}
386
+ * aria-label="Large list of items"
387
+ * />
388
+ * );
389
+ * ```
390
+ */
391
+ declare const VirtualizedList: <T>(props: VirtualizedListProps<T>) => react_jsx_runtime.JSX.Element;
392
+
393
+ /**
394
+ * Wizard step definition
395
+ */
396
+ interface WizardStep {
397
+ /**
398
+ * Unique step ID
399
+ */
400
+ id: string;
401
+ /**
402
+ * Step label (shown in progress indicator)
403
+ */
404
+ label: string;
405
+ /**
406
+ * Step content
407
+ */
408
+ content: ReactNode;
409
+ /**
410
+ * Optional validation before proceeding
411
+ * Return true if valid, or error message if invalid
412
+ */
413
+ validate?: () => true | string;
414
+ /**
415
+ * Whether this step can be skipped
416
+ */
417
+ optional?: boolean;
418
+ }
419
+ /**
420
+ * Wizard context value
421
+ */
422
+ interface WizardContextValue {
423
+ /**
424
+ * Current step index
425
+ */
426
+ currentStep: number;
427
+ /**
428
+ * Total number of steps
429
+ */
430
+ totalSteps: number;
431
+ /**
432
+ * Current step data
433
+ */
434
+ step: WizardStep;
435
+ /**
436
+ * Navigate to next step
437
+ */
438
+ next: () => void;
439
+ /**
440
+ * Navigate to previous step
441
+ */
442
+ previous: () => void;
443
+ /**
444
+ * Navigate to specific step (if valid)
445
+ */
446
+ goToStep: (index: number) => void;
447
+ /**
448
+ * Check if can go to next step
449
+ */
450
+ canGoNext: boolean;
451
+ /**
452
+ * Check if can go to previous step
453
+ */
454
+ canGoPrevious: boolean;
455
+ /**
456
+ * Check if on last step
457
+ */
458
+ isLastStep: boolean;
459
+ /**
460
+ * Validation error (if any)
461
+ */
462
+ validationError: string | null;
463
+ }
464
+ /**
465
+ * Hook to access wizard context
466
+ */
467
+ declare const useWizard: () => WizardContextValue;
468
+ /**
469
+ * Props for Wizard
470
+ */
471
+ interface WizardProps {
472
+ /**
473
+ * Wizard steps (must have at least one)
474
+ */
475
+ steps: [WizardStep, ...WizardStep[]];
476
+ /**
477
+ * Called when wizard is completed
478
+ */
479
+ onComplete: () => void;
480
+ /**
481
+ * Called when wizard is cancelled
482
+ */
483
+ onCancel?: () => void;
484
+ /**
485
+ * Initial step index
486
+ */
487
+ initialStep?: number;
488
+ /**
489
+ * Custom className
490
+ */
491
+ className?: string;
492
+ }
493
+ /**
494
+ * Wizard Component
495
+ *
496
+ * Multi-step form with:
497
+ * - Progress indicator
498
+ * - Keyboard navigation (arrows, Home, End)
499
+ * - Step validation
500
+ * - Screen reader announcements
501
+ * - Focus management between steps
502
+ *
503
+ * Pattern Explanation:
504
+ * - Each step can have validation before proceeding
505
+ * - Arrow keys navigate between steps (if valid)
506
+ * - Progress indicator shows current position
507
+ * - Screen readers announce step changes
508
+ * - Optional steps can be skipped
509
+ * - Validation errors are announced to screen readers
510
+ *
511
+ * @example
512
+ * ```tsx
513
+ * <Wizard
514
+ * steps={[
515
+ * {
516
+ * id: 'account',
517
+ * label: 'Account Info',
518
+ * content: <AccountForm />,
519
+ * validate: () => isValidEmail(email) || 'Invalid email',
520
+ * },
521
+ * {
522
+ * id: 'preferences',
523
+ * label: 'Preferences',
524
+ * content: <PreferencesForm />,
525
+ * optional: true,
526
+ * },
527
+ * {
528
+ * id: 'review',
529
+ * label: 'Review',
530
+ * content: <ReviewScreen />,
531
+ * },
532
+ * ]}
533
+ * onComplete={() => console.log('Wizard completed!')}
534
+ * />
535
+ * ```
536
+ */
537
+ declare const Wizard: (props: WizardProps) => react_jsx_runtime.JSX.Element;
538
+
539
+ export { DialogStackProvider, type DialogStackProviderProps, InfiniteList, type InfiniteListProps, NestedMenu, type NestedMenuItem, type NestedMenuProps, VirtualizedList, type VirtualizedListProps, Wizard, type WizardProps, type WizardStep, useDialogStack, useWizard };