15
15
16
16
'use client' ;
17
17
18
- import {
19
- createRef ,
20
- Fragment ,
21
- useState ,
22
- type KeyboardEvent ,
23
- type ReactNode ,
24
- } from 'react' ;
18
+ import { Fragment , useState , type KeyboardEvent , type ReactNode } from 'react' ;
25
19
26
20
import {
27
21
isArrowLeft ,
@@ -35,9 +29,11 @@ import { TabPanel } from './components/TabPanel/index.js';
35
29
36
30
export interface TabsProps extends TabListProps {
37
31
/**
38
- * The index of the initially selected tab.
32
+ * The id of the initially selected tab.
33
+ *
34
+ * @default items[0].id
39
35
*/
40
- initialSelectedIndex ?: number ;
36
+ initialSelectedId ?: string ;
41
37
/**
42
38
* A collection of tabs with an id, the tab label, and panel content.
43
39
*/
@@ -48,34 +44,43 @@ export interface TabsProps extends TabListProps {
48
44
} [ ] ;
49
45
}
50
46
51
- export function Tabs ( { initialSelectedIndex = 0 , items, ...props } : TabsProps ) {
52
- const [ selectedIndex , setSelectedIndex ] = useState ( initialSelectedIndex ) ;
53
-
54
- const tabPanelsRefs = createRefs ( items . length ) ;
47
+ export function Tabs ( {
48
+ items,
49
+ initialSelectedId = items [ 0 ] . id ,
50
+ ...props
51
+ } : TabsProps ) {
52
+ const [ selectedId , setSelectedId ] = useState ( initialSelectedId ) ;
55
53
56
54
const handleTabKeyDown = ( event : KeyboardEvent ) => {
55
+ const selectedIndex = items . findIndex ( ( item ) => item . id === selectedId ) ;
56
+
57
57
if ( isArrowLeft ( event ) ) {
58
- const previousTab = Math . max ( 0 , selectedIndex - 1 ) ;
59
- setSelectedIndex ( previousTab ) ;
58
+ const previousIndex = selectedIndex - 1 ;
59
+ if ( previousIndex >= 0 ) {
60
+ const previousId = items [ previousIndex ] . id ;
61
+ setSelectedId ( previousId ) ;
62
+ document . getElementById ( `tab-${ previousId } ` ) ?. focus ( ) ;
63
+ }
60
64
} else if ( isArrowRight ( event ) ) {
61
- const nextTab = Math . min ( items . length - 1 , selectedIndex + 1 ) ;
62
- setSelectedIndex ( nextTab ) ;
63
- } else if ( isArrowDown ( event ) ) {
64
- const panelRef = tabPanelsRefs [ selectedIndex ] . current ;
65
- if ( panelRef ) {
66
- panelRef . focus ( ) ;
65
+ const nextIndex = selectedIndex + 1 ;
66
+ if ( nextIndex <= items . length - 1 ) {
67
+ const nextId = items [ nextIndex ] . id ;
68
+ setSelectedId ( nextId ) ;
69
+ document . getElementById ( `tab-${ nextId } ` ) ?. focus ( ) ;
67
70
}
71
+ } else if ( isArrowDown ( event ) ) {
72
+ document . getElementById ( `panel-${ selectedId } ` ) ?. focus ( ) ;
68
73
}
69
74
} ;
70
75
71
76
return (
72
77
< Fragment >
73
78
< TabList { ...props } >
74
- { items . map ( ( { id, tab } , index ) => (
79
+ { items . map ( ( { id, tab } ) => (
75
80
< Tab
76
81
key = { id }
77
- selected = { selectedIndex === index }
78
- onClick = { ( ) => setSelectedIndex ( index ) }
82
+ selected = { selectedId === id }
83
+ onClick = { ( ) => setSelectedId ( id ) }
79
84
id = { `tab-${ id } ` }
80
85
aria-controls = { `panel-${ id } ` }
81
86
onKeyDown = { handleTabKeyDown }
@@ -84,23 +89,16 @@ export function Tabs({ initialSelectedIndex = 0, items, ...props }: TabsProps) {
84
89
</ Tab >
85
90
) ) }
86
91
</ TabList >
87
- { items . map ( ( { id, panel } , index ) => (
92
+ { items . map ( ( { id, panel } ) => (
88
93
< TabPanel
89
94
key = { id }
90
95
id = { `panel-${ id } ` }
91
96
aria-labelledby = { `tab-${ id } ` }
92
- hidden = { selectedIndex !== index }
93
- ref = { tabPanelsRefs [ index ] }
97
+ hidden = { selectedId !== id }
94
98
>
95
99
{ panel }
96
100
</ TabPanel >
97
101
) ) }
98
102
</ Fragment >
99
103
) ;
100
104
}
101
-
102
- function createRefs ( length : number ) {
103
- return Array . from ( Array ( length ) . keys ( ) ) . map ( ( ) =>
104
- createRef < HTMLDivElement > ( ) ,
105
- ) ;
106
- }
0 commit comments