@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,849 @@
1
+ import { useState, useRef, useEffect, useCallback, useId } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ var useAccessibleButton = (props) => {
5
+ const { label, onPress, isDisabled = false, role = "button", elementType = "button" } = props;
6
+ const buttonRef = useRef(null);
7
+ const isPressedRef = useRef(false);
8
+ useEffect(() => {
9
+ {
10
+ import('@a13y/devtools/runtime/invariants').then(
11
+ ({ assertHasAccessibleName, assertKeyboardAccessible }) => {
12
+ if (buttonRef.current) {
13
+ assertHasAccessibleName(buttonRef.current, "useAccessibleButton");
14
+ assertKeyboardAccessible(buttonRef.current, "useAccessibleButton");
15
+ }
16
+ }
17
+ );
18
+ }
19
+ }, []);
20
+ const handlePress = useCallback(
21
+ (event) => {
22
+ if (isDisabled) {
23
+ return;
24
+ }
25
+ onPress(event);
26
+ },
27
+ [onPress, isDisabled]
28
+ );
29
+ const handlePointerDown = useCallback(
30
+ (event) => {
31
+ if (isDisabled) {
32
+ event.preventDefault();
33
+ return;
34
+ }
35
+ isPressedRef.current = true;
36
+ handlePress({ type: "mouse" });
37
+ },
38
+ [handlePress, isDisabled]
39
+ );
40
+ const handleKeyDown = useCallback(
41
+ (event) => {
42
+ if (isDisabled) {
43
+ return;
44
+ }
45
+ if (event.key === "Enter" || event.key === " ") {
46
+ event.preventDefault();
47
+ handlePress({ type: "keyboard", key: event.key });
48
+ }
49
+ },
50
+ [handlePress, isDisabled]
51
+ );
52
+ const buttonProps = {
53
+ role,
54
+ tabIndex: isDisabled ? -1 : 0,
55
+ "aria-label": label,
56
+ "aria-disabled": isDisabled ? true : void 0,
57
+ disabled: elementType === "button" ? isDisabled : void 0,
58
+ onPointerDown: handlePointerDown,
59
+ onKeyDown: handleKeyDown
60
+ };
61
+ return {
62
+ buttonProps,
63
+ isPressed: isPressedRef.current
64
+ };
65
+ };
66
+ var AccessibleButton = (props) => {
67
+ const {
68
+ children,
69
+ label,
70
+ onPress,
71
+ disabled = false,
72
+ variant = "primary",
73
+ className = "",
74
+ type = "button"
75
+ } = props;
76
+ const { buttonProps } = useAccessibleButton({
77
+ label,
78
+ onPress,
79
+ isDisabled: disabled
80
+ });
81
+ const baseStyles = "inline-flex items-center justify-center font-medium transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:opacity-50 disabled:cursor-not-allowed";
82
+ const variantStyles = {
83
+ primary: "bg-blue-600 text-white hover:bg-blue-700 focus-visible:outline-blue-600",
84
+ secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300 focus-visible:outline-gray-500",
85
+ danger: "bg-red-600 text-white hover:bg-red-700 focus-visible:outline-red-600",
86
+ ghost: "bg-transparent text-gray-700 hover:bg-gray-100 focus-visible:outline-gray-500"
87
+ };
88
+ const combinedClassName = `${baseStyles} ${variantStyles[variant]} ${className}`.trim();
89
+ return /* @__PURE__ */ jsx(
90
+ "button",
91
+ {
92
+ ...buttonProps,
93
+ type,
94
+ className: combinedClassName,
95
+ style: {
96
+ padding: "0.5rem 1rem",
97
+ borderRadius: "0.375rem",
98
+ border: "none",
99
+ cursor: disabled ? "not-allowed" : "pointer"
100
+ },
101
+ children
102
+ }
103
+ );
104
+ };
105
+ var useFocusTrap = (props) => {
106
+ const { isActive, onEscape, restoreFocus = true, autoFocus = true } = props;
107
+ const trapRef = useRef(null);
108
+ const focusTrapRef = useRef(null);
109
+ const previousFocusRef = useRef(null);
110
+ useEffect(() => {
111
+ if (!isActive || !trapRef.current) {
112
+ return;
113
+ }
114
+ if (restoreFocus) {
115
+ previousFocusRef.current = document.activeElement;
116
+ }
117
+ import('@a13y/core/runtime/focus').then(({ createFocusTrap }) => {
118
+ if (!trapRef.current) {
119
+ return;
120
+ }
121
+ const options = {
122
+ returnFocus: false,
123
+ onEscape
124
+ };
125
+ if (autoFocus) {
126
+ options.initialFocus = void 0;
127
+ }
128
+ const trap = createFocusTrap(trapRef.current, options);
129
+ trap.activate();
130
+ focusTrapRef.current = trap;
131
+ {
132
+ import('@a13y/devtools/runtime/validators').then(({ focusValidator }) => {
133
+ if (trapRef.current) {
134
+ focusValidator.validateFocusTrap(trapRef.current, true);
135
+ }
136
+ });
137
+ }
138
+ });
139
+ return () => {
140
+ if (focusTrapRef.current) {
141
+ focusTrapRef.current.deactivate();
142
+ focusTrapRef.current = null;
143
+ }
144
+ if (restoreFocus && previousFocusRef.current) {
145
+ previousFocusRef.current.focus();
146
+ {
147
+ import('@a13y/devtools/runtime/validators').then(({ focusValidator }) => {
148
+ if (previousFocusRef.current) {
149
+ focusValidator.expectFocusRestoration(
150
+ previousFocusRef.current,
151
+ "focus trap deactivation"
152
+ );
153
+ }
154
+ });
155
+ }
156
+ }
157
+ };
158
+ }, [isActive, onEscape, restoreFocus, autoFocus]);
159
+ return {
160
+ trapRef
161
+ };
162
+ };
163
+
164
+ // src/hooks/use-accessible-dialog.ts
165
+ var useAccessibleDialog = (props) => {
166
+ const {
167
+ isOpen,
168
+ onClose,
169
+ title,
170
+ description,
171
+ role = "dialog",
172
+ isModal = true,
173
+ closeOnBackdropClick = true
174
+ } = props;
175
+ {
176
+ if (!title || title.trim().length === 0) {
177
+ throw new Error(
178
+ '@a13y/react [useAccessibleDialog]: "title" prop is required for accessibility'
179
+ );
180
+ }
181
+ }
182
+ const dialogRef = useRef(null);
183
+ const titleId = useId();
184
+ const descriptionId = useId();
185
+ const { trapRef } = useFocusTrap({
186
+ isActive: isOpen,
187
+ onEscape: onClose,
188
+ restoreFocus: true,
189
+ autoFocus: true
190
+ });
191
+ useEffect(() => {
192
+ if (dialogRef.current && trapRef.current !== dialogRef.current) {
193
+ trapRef.current = dialogRef.current;
194
+ }
195
+ }, [trapRef]);
196
+ useEffect(() => {
197
+ if (!isOpen || !isModal) {
198
+ return;
199
+ }
200
+ const originalOverflow = document.body.style.overflow;
201
+ document.body.style.overflow = "hidden";
202
+ return () => {
203
+ document.body.style.overflow = originalOverflow;
204
+ };
205
+ }, [isOpen, isModal]);
206
+ useEffect(() => {
207
+ if (isOpen) {
208
+ import('@a13y/devtools/runtime/invariants').then(
209
+ ({ assertHasAccessibleName, assertValidAriaAttributes }) => {
210
+ if (dialogRef.current) {
211
+ assertHasAccessibleName(dialogRef.current, "useAccessibleDialog");
212
+ assertValidAriaAttributes(dialogRef.current);
213
+ }
214
+ }
215
+ );
216
+ }
217
+ }, [isOpen]);
218
+ const dialogProps = {
219
+ ref: dialogRef,
220
+ role,
221
+ "aria-labelledby": titleId,
222
+ "aria-describedby": description ? descriptionId : void 0,
223
+ "aria-modal": isModal,
224
+ tabIndex: -1
225
+ };
226
+ const titleProps = {
227
+ id: titleId
228
+ };
229
+ const descriptionProps = description ? { id: descriptionId } : null;
230
+ const backdropProps = closeOnBackdropClick && isModal ? {
231
+ onClick: onClose,
232
+ "aria-hidden": true
233
+ } : null;
234
+ return {
235
+ dialogProps,
236
+ titleProps,
237
+ descriptionProps,
238
+ backdropProps,
239
+ close: onClose
240
+ };
241
+ };
242
+ var AccessibleDialog = (props) => {
243
+ const {
244
+ isOpen,
245
+ onClose,
246
+ title,
247
+ children,
248
+ description,
249
+ role = "dialog",
250
+ showCloseButton = true,
251
+ className = "",
252
+ backdropClassName = ""
253
+ } = props;
254
+ const { dialogProps, titleProps, descriptionProps, backdropProps, close } = useAccessibleDialog({
255
+ isOpen,
256
+ onClose,
257
+ title,
258
+ description,
259
+ role,
260
+ isModal: true,
261
+ closeOnBackdropClick: true
262
+ });
263
+ if (!isOpen) {
264
+ return null;
265
+ }
266
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
267
+ backdropProps && /* @__PURE__ */ jsx(
268
+ "div",
269
+ {
270
+ ...backdropProps,
271
+ className: backdropClassName,
272
+ style: {
273
+ position: "fixed",
274
+ inset: 0,
275
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
276
+ display: "flex",
277
+ alignItems: "center",
278
+ justifyContent: "center",
279
+ zIndex: 50
280
+ }
281
+ }
282
+ ),
283
+ /* @__PURE__ */ jsxs(
284
+ "div",
285
+ {
286
+ ref: dialogProps.ref,
287
+ role: dialogProps.role,
288
+ "aria-labelledby": dialogProps["aria-labelledby"],
289
+ "aria-describedby": dialogProps["aria-describedby"],
290
+ "aria-modal": dialogProps["aria-modal"],
291
+ tabIndex: dialogProps.tabIndex,
292
+ className,
293
+ style: {
294
+ position: "fixed",
295
+ top: "50%",
296
+ left: "50%",
297
+ transform: "translate(-50%, -50%)",
298
+ backgroundColor: "white",
299
+ borderRadius: "0.5rem",
300
+ boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1)",
301
+ padding: "1.5rem",
302
+ maxWidth: "32rem",
303
+ width: "90vw",
304
+ maxHeight: "90vh",
305
+ overflow: "auto",
306
+ zIndex: 51
307
+ },
308
+ children: [
309
+ showCloseButton && /* @__PURE__ */ jsx(
310
+ "button",
311
+ {
312
+ type: "button",
313
+ onClick: close,
314
+ "aria-label": "Close dialog",
315
+ style: {
316
+ position: "absolute",
317
+ top: "1rem",
318
+ right: "1rem",
319
+ padding: "0.5rem",
320
+ border: "none",
321
+ background: "transparent",
322
+ cursor: "pointer",
323
+ fontSize: "1.25rem",
324
+ lineHeight: 1,
325
+ color: "#6b7280"
326
+ },
327
+ children: "\u2715"
328
+ }
329
+ ),
330
+ /* @__PURE__ */ jsx(
331
+ "h2",
332
+ {
333
+ ...titleProps,
334
+ style: {
335
+ fontSize: "1.25rem",
336
+ fontWeight: 600,
337
+ marginBottom: description ? "0.5rem" : "1rem",
338
+ paddingRight: showCloseButton ? "2rem" : 0
339
+ },
340
+ children: title
341
+ }
342
+ ),
343
+ descriptionProps && description && /* @__PURE__ */ jsx(
344
+ "p",
345
+ {
346
+ ...descriptionProps,
347
+ style: {
348
+ fontSize: "0.875rem",
349
+ color: "#6b7280",
350
+ marginBottom: "1rem"
351
+ },
352
+ children: description
353
+ }
354
+ ),
355
+ /* @__PURE__ */ jsx("div", { children })
356
+ ]
357
+ }
358
+ )
359
+ ] });
360
+ };
361
+ var useKeyboardNavigation = (props) => {
362
+ const {
363
+ orientation,
364
+ loop = false,
365
+ onNavigate,
366
+ defaultIndex = 0,
367
+ currentIndex: controlledIndex
368
+ } = props;
369
+ const isControlled = controlledIndex !== void 0;
370
+ const [uncontrolledIndex, setUncontrolledIndex] = useState(defaultIndex);
371
+ const currentIndex = isControlled ? controlledIndex : uncontrolledIndex;
372
+ const itemsRef = useRef(/* @__PURE__ */ new Map());
373
+ const containerRef = useRef(null);
374
+ const setCurrentIndex = useCallback(
375
+ (index) => {
376
+ if (!isControlled) {
377
+ setUncontrolledIndex(index);
378
+ }
379
+ onNavigate?.(index);
380
+ const element = itemsRef.current.get(index);
381
+ if (element) {
382
+ element.focus();
383
+ }
384
+ },
385
+ [isControlled, onNavigate]
386
+ );
387
+ const navigate = useCallback(
388
+ (direction) => {
389
+ const itemCount = itemsRef.current.size;
390
+ if (itemCount === 0) {
391
+ return;
392
+ }
393
+ let nextIndex = currentIndex;
394
+ switch (direction) {
395
+ case "forward":
396
+ nextIndex = currentIndex + 1;
397
+ if (nextIndex >= itemCount) {
398
+ nextIndex = loop ? 0 : itemCount - 1;
399
+ }
400
+ break;
401
+ case "backward":
402
+ nextIndex = currentIndex - 1;
403
+ if (nextIndex < 0) {
404
+ nextIndex = loop ? itemCount - 1 : 0;
405
+ }
406
+ break;
407
+ case "first":
408
+ nextIndex = 0;
409
+ break;
410
+ case "last":
411
+ nextIndex = itemCount - 1;
412
+ break;
413
+ }
414
+ if (nextIndex !== currentIndex) {
415
+ setCurrentIndex(nextIndex);
416
+ }
417
+ },
418
+ [currentIndex, loop, setCurrentIndex]
419
+ );
420
+ const handleKeyDown = useCallback(
421
+ (event) => {
422
+ const { key } = event;
423
+ let direction = null;
424
+ if (key === "ArrowRight") {
425
+ if (orientation === "horizontal" || orientation === "both") {
426
+ direction = "forward";
427
+ }
428
+ } else if (key === "ArrowLeft") {
429
+ if (orientation === "horizontal" || orientation === "both") {
430
+ direction = "backward";
431
+ }
432
+ } else if (key === "ArrowDown") {
433
+ if (orientation === "vertical" || orientation === "both") {
434
+ direction = "forward";
435
+ }
436
+ } else if (key === "ArrowUp") {
437
+ if (orientation === "vertical" || orientation === "both") {
438
+ direction = "backward";
439
+ }
440
+ } else if (key === "Home") {
441
+ direction = "first";
442
+ } else if (key === "End") {
443
+ direction = "last";
444
+ }
445
+ if (direction) {
446
+ event.preventDefault();
447
+ navigate(direction);
448
+ }
449
+ },
450
+ [orientation, navigate]
451
+ );
452
+ const getItemProps = useCallback(
453
+ (index) => {
454
+ return {
455
+ ref: (element) => {
456
+ if (element) {
457
+ itemsRef.current.set(index, element);
458
+ } else {
459
+ itemsRef.current.delete(index);
460
+ }
461
+ },
462
+ tabIndex: index === currentIndex ? 0 : -1,
463
+ onKeyDown: handleKeyDown,
464
+ "data-index": index
465
+ };
466
+ },
467
+ [currentIndex, handleKeyDown]
468
+ );
469
+ useEffect(() => {
470
+ {
471
+ import('@a13y/devtools/runtime/validators').then(({ keyboardValidator }) => {
472
+ if (containerRef.current) {
473
+ keyboardValidator.validateContainer(containerRef.current);
474
+ }
475
+ const container = Array.from(itemsRef.current.values())[0]?.parentElement;
476
+ if (container) {
477
+ keyboardValidator.validateRovingTabindex(container);
478
+ }
479
+ });
480
+ }
481
+ }, []);
482
+ const containerProps = {
483
+ role: "toolbar",
484
+ "aria-orientation": orientation
485
+ };
486
+ return {
487
+ currentIndex,
488
+ setCurrentIndex,
489
+ getItemProps,
490
+ containerProps
491
+ };
492
+ };
493
+ var AccessibleMenu = (props) => {
494
+ const { label, trigger, items, className = "", menuClassName = "" } = props;
495
+ const [isOpen, setIsOpen] = useState(false);
496
+ const { buttonProps: triggerProps } = useAccessibleButton({
497
+ label,
498
+ onPress: () => setIsOpen(!isOpen)
499
+ });
500
+ const { trapRef } = useFocusTrap({
501
+ isActive: isOpen,
502
+ onEscape: () => setIsOpen(false),
503
+ restoreFocus: true
504
+ });
505
+ const { getItemProps } = useKeyboardNavigation({
506
+ orientation: "vertical",
507
+ loop: true
508
+ });
509
+ const handleItemPress = (item) => {
510
+ if (item.disabled) {
511
+ return;
512
+ }
513
+ item.onPress();
514
+ setIsOpen(false);
515
+ };
516
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [
517
+ /* @__PURE__ */ jsx(
518
+ "button",
519
+ {
520
+ ...triggerProps,
521
+ className,
522
+ "aria-expanded": isOpen,
523
+ "aria-haspopup": "true",
524
+ style: {
525
+ padding: "0.5rem 1rem",
526
+ border: "1px solid #d1d5db",
527
+ borderRadius: "0.375rem",
528
+ backgroundColor: "white",
529
+ cursor: "pointer",
530
+ fontSize: "0.875rem",
531
+ fontWeight: 500
532
+ },
533
+ children: trigger
534
+ }
535
+ ),
536
+ isOpen && /* @__PURE__ */ jsx(
537
+ "div",
538
+ {
539
+ ref: trapRef,
540
+ role: "menu",
541
+ "aria-orientation": "vertical",
542
+ className: menuClassName,
543
+ style: {
544
+ position: "absolute",
545
+ top: "calc(100% + 0.25rem)",
546
+ left: 0,
547
+ minWidth: "12rem",
548
+ backgroundColor: "white",
549
+ border: "1px solid #e5e7eb",
550
+ borderRadius: "0.375rem",
551
+ boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1)",
552
+ padding: "0.25rem",
553
+ zIndex: 50
554
+ },
555
+ children: items.map((item, index) => {
556
+ const itemProps = getItemProps(index);
557
+ return /* @__PURE__ */ jsxs(
558
+ "button",
559
+ {
560
+ ...itemProps,
561
+ role: "menuitem",
562
+ disabled: item.disabled,
563
+ onClick: () => handleItemPress(item),
564
+ style: {
565
+ display: "flex",
566
+ alignItems: "center",
567
+ gap: "0.75rem",
568
+ width: "100%",
569
+ padding: "0.5rem 0.75rem",
570
+ border: "none",
571
+ background: "transparent",
572
+ textAlign: "left",
573
+ fontSize: "0.875rem",
574
+ cursor: item.disabled ? "not-allowed" : "pointer",
575
+ borderRadius: "0.25rem",
576
+ color: item.disabled ? "#9ca3af" : "#111827",
577
+ opacity: item.disabled ? 0.5 : 1
578
+ },
579
+ onMouseEnter: (e) => {
580
+ if (!item.disabled) {
581
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
582
+ }
583
+ },
584
+ onMouseLeave: (e) => {
585
+ e.currentTarget.style.backgroundColor = "transparent";
586
+ },
587
+ children: [
588
+ item.icon && /* @__PURE__ */ jsx("span", { children: item.icon }),
589
+ /* @__PURE__ */ jsx("span", { children: item.label })
590
+ ]
591
+ },
592
+ item.id
593
+ );
594
+ })
595
+ }
596
+ )
597
+ ] });
598
+ };
599
+ var AccessibleModal = (props) => {
600
+ const {
601
+ isOpen,
602
+ onClose,
603
+ title,
604
+ children,
605
+ footer,
606
+ size = "md",
607
+ closeOnBackdropClick = false,
608
+ className = ""
609
+ } = props;
610
+ const { dialogProps, titleProps, backdropProps, close } = useAccessibleDialog({
611
+ isOpen,
612
+ onClose,
613
+ title,
614
+ role: "dialog",
615
+ isModal: true,
616
+ closeOnBackdropClick
617
+ });
618
+ if (!isOpen) {
619
+ return null;
620
+ }
621
+ const sizeStyles = {
622
+ sm: { maxWidth: "24rem" },
623
+ md: { maxWidth: "32rem" },
624
+ lg: { maxWidth: "48rem" },
625
+ xl: { maxWidth: "64rem" },
626
+ full: { maxWidth: "95vw", maxHeight: "95vh" }
627
+ };
628
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
629
+ backdropProps && /* @__PURE__ */ jsx(
630
+ "div",
631
+ {
632
+ ...backdropProps,
633
+ style: {
634
+ position: "fixed",
635
+ inset: 0,
636
+ backgroundColor: "rgba(0, 0, 0, 0.6)",
637
+ display: "flex",
638
+ alignItems: "center",
639
+ justifyContent: "center",
640
+ zIndex: 50
641
+ }
642
+ }
643
+ ),
644
+ /* @__PURE__ */ jsxs(
645
+ "div",
646
+ {
647
+ ref: dialogProps.ref,
648
+ role: dialogProps.role,
649
+ "aria-labelledby": dialogProps["aria-labelledby"],
650
+ "aria-describedby": dialogProps["aria-describedby"],
651
+ "aria-modal": dialogProps["aria-modal"],
652
+ tabIndex: dialogProps.tabIndex,
653
+ className,
654
+ style: {
655
+ position: "fixed",
656
+ top: "50%",
657
+ left: "50%",
658
+ transform: "translate(-50%, -50%)",
659
+ backgroundColor: "white",
660
+ borderRadius: "0.5rem",
661
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
662
+ width: "90vw",
663
+ maxHeight: "90vh",
664
+ display: "flex",
665
+ flexDirection: "column",
666
+ zIndex: 51,
667
+ ...sizeStyles[size]
668
+ },
669
+ children: [
670
+ /* @__PURE__ */ jsxs(
671
+ "div",
672
+ {
673
+ style: {
674
+ padding: "1.5rem",
675
+ borderBottom: "1px solid #e5e7eb",
676
+ display: "flex",
677
+ alignItems: "center",
678
+ justifyContent: "space-between"
679
+ },
680
+ children: [
681
+ /* @__PURE__ */ jsx(
682
+ "h2",
683
+ {
684
+ ...titleProps,
685
+ style: {
686
+ fontSize: "1.25rem",
687
+ fontWeight: 600,
688
+ margin: 0
689
+ },
690
+ children: title
691
+ }
692
+ ),
693
+ /* @__PURE__ */ jsx(
694
+ "button",
695
+ {
696
+ type: "button",
697
+ onClick: close,
698
+ "aria-label": "Close modal",
699
+ style: {
700
+ padding: "0.5rem",
701
+ border: "none",
702
+ background: "transparent",
703
+ cursor: "pointer",
704
+ fontSize: "1.25rem",
705
+ lineHeight: 1,
706
+ color: "#6b7280",
707
+ borderRadius: "0.25rem"
708
+ },
709
+ children: "\u2715"
710
+ }
711
+ )
712
+ ]
713
+ }
714
+ ),
715
+ /* @__PURE__ */ jsx(
716
+ "div",
717
+ {
718
+ style: {
719
+ flex: 1,
720
+ overflowY: "auto",
721
+ padding: "1.5rem"
722
+ },
723
+ children
724
+ }
725
+ ),
726
+ footer && /* @__PURE__ */ jsx(
727
+ "div",
728
+ {
729
+ style: {
730
+ padding: "1.5rem",
731
+ borderTop: "1px solid #e5e7eb",
732
+ display: "flex",
733
+ gap: "0.75rem",
734
+ justifyContent: "flex-end"
735
+ },
736
+ children: footer
737
+ }
738
+ )
739
+ ]
740
+ }
741
+ )
742
+ ] });
743
+ };
744
+ var AccessibleTabs = (props) => {
745
+ const {
746
+ tabs,
747
+ defaultTab = 0,
748
+ selectedTab: controlledTab,
749
+ onTabChange,
750
+ className = "",
751
+ panelClassName = ""
752
+ } = props;
753
+ const isControlled = controlledTab !== void 0;
754
+ const [uncontrolledTab, setUncontrolledTab] = useState(defaultTab);
755
+ const selectedIndex = isControlled ? controlledTab : uncontrolledTab;
756
+ const { getItemProps, setCurrentIndex } = useKeyboardNavigation({
757
+ orientation: "horizontal",
758
+ loop: false,
759
+ currentIndex: selectedIndex,
760
+ onNavigate: (index) => {
761
+ if (tabs[index]?.disabled) {
762
+ return;
763
+ }
764
+ if (!isControlled) {
765
+ setUncontrolledTab(index);
766
+ }
767
+ onTabChange?.(index);
768
+ }
769
+ });
770
+ const handleTabClick = (index) => {
771
+ if (tabs[index]?.disabled) {
772
+ return;
773
+ }
774
+ setCurrentIndex(index);
775
+ if (!isControlled) {
776
+ setUncontrolledTab(index);
777
+ }
778
+ onTabChange?.(index);
779
+ };
780
+ const selectedTab = tabs[selectedIndex];
781
+ return /* @__PURE__ */ jsxs("div", { className, children: [
782
+ /* @__PURE__ */ jsx(
783
+ "div",
784
+ {
785
+ role: "tablist",
786
+ "aria-orientation": "horizontal",
787
+ style: {
788
+ display: "flex",
789
+ borderBottom: "2px solid #e5e7eb",
790
+ gap: "0.25rem"
791
+ },
792
+ children: tabs.map((tab, index) => {
793
+ const itemProps = getItemProps(index);
794
+ const isSelected = index === selectedIndex;
795
+ return /* @__PURE__ */ jsxs(
796
+ "button",
797
+ {
798
+ ...itemProps,
799
+ id: `tab-${tab.id}`,
800
+ role: "tab",
801
+ "aria-selected": isSelected,
802
+ "aria-controls": `panel-${tab.id}`,
803
+ disabled: tab.disabled,
804
+ onClick: () => handleTabClick(index),
805
+ style: {
806
+ display: "flex",
807
+ alignItems: "center",
808
+ gap: "0.5rem",
809
+ padding: "0.75rem 1rem",
810
+ border: "none",
811
+ background: "transparent",
812
+ cursor: tab.disabled ? "not-allowed" : "pointer",
813
+ fontSize: "0.875rem",
814
+ fontWeight: isSelected ? 600 : 400,
815
+ color: tab.disabled ? "#9ca3af" : isSelected ? "#2563eb" : "#6b7280",
816
+ borderBottom: isSelected ? "2px solid #2563eb" : "none",
817
+ marginBottom: "-2px",
818
+ opacity: tab.disabled ? 0.5 : 1,
819
+ transition: "color 0.2s"
820
+ },
821
+ children: [
822
+ tab.icon && /* @__PURE__ */ jsx("span", { children: tab.icon }),
823
+ /* @__PURE__ */ jsx("span", { children: tab.label })
824
+ ]
825
+ },
826
+ tab.id
827
+ );
828
+ })
829
+ }
830
+ ),
831
+ selectedTab && /* @__PURE__ */ jsx(
832
+ "div",
833
+ {
834
+ id: `panel-${selectedTab.id}`,
835
+ role: "tabpanel",
836
+ "aria-labelledby": `tab-${selectedTab.id}`,
837
+ className: panelClassName,
838
+ style: {
839
+ padding: "1.5rem"
840
+ },
841
+ children: selectedTab.content
842
+ }
843
+ )
844
+ ] });
845
+ };
846
+
847
+ export { AccessibleButton, AccessibleDialog, AccessibleMenu, AccessibleModal, AccessibleTabs };
848
+ //# sourceMappingURL=index.js.map
849
+ //# sourceMappingURL=index.js.map