1
1
'use client' ;
2
2
import * as React from 'react' ;
3
- import { useComponentRenderer } from '../../utils/useComponentRenderer' ;
4
3
import type { BaseUIComponentProps } from '../../utils/types' ;
4
+ import { useControlled } from '../../utils/useControlled' ;
5
+ import { useEventCallback } from '../../utils/useEventCallback' ;
6
+ import { useRenderElement } from '../../utils/useRenderElement' ;
5
7
import { CompositeList } from '../../composite/list/CompositeList' ;
8
+ import type { CompositeMetadata } from '../../composite/list/CompositeList' ;
6
9
import { useDirection } from '../../direction-provider/DirectionContext' ;
7
- import { useTabsRoot } from './useTabsRoot' ;
8
10
import { TabsRootContext } from './TabsRootContext' ;
9
11
import { tabsStyleHookMapping } from './styleHooks' ;
12
+ import type { TabMetadata } from '../tab/TabsTab' ;
10
13
import type { TabPanelMetadata } from '../panel/TabsPanel' ;
11
14
12
15
/**
@@ -16,7 +19,7 @@ import type { TabPanelMetadata } from '../panel/TabsPanel';
16
19
* Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs)
17
20
*/
18
21
export const TabsRoot = React . forwardRef ( function TabsRoot (
19
- props : TabsRoot . Props ,
22
+ componentProps : TabsRoot . Props ,
20
23
forwardedRef : React . ForwardedRef < HTMLDivElement > ,
21
24
) {
22
25
const {
@@ -26,27 +29,116 @@ export const TabsRoot = React.forwardRef(function TabsRoot(
26
29
orientation = 'horizontal' ,
27
30
render,
28
31
value : valueProp ,
29
- ...other
30
- } = props ;
32
+ ...elementProps
33
+ } = componentProps ;
31
34
32
35
const direction = useDirection ( ) ;
33
36
34
- const {
35
- getTabElementBySelectedValue,
36
- getTabIdByPanelValueOrIndex,
37
- getTabPanelIdByTabValueOrIndex,
38
- onValueChange,
39
- setTabMap,
40
- setTabPanelMap,
41
- tabActivationDirection,
42
- tabPanelRefs,
43
- value,
44
- } = useTabsRoot ( {
45
- value : valueProp ,
46
- defaultValue,
47
- onValueChange : onValueChangeProp ,
37
+ const tabPanelRefs = React . useRef < ( HTMLElement | null ) [ ] > ( [ ] ) ;
38
+
39
+ const [ value , setValue ] = useControlled ( {
40
+ controlled : valueProp ,
41
+ default : defaultValue ,
42
+ name : 'Tabs' ,
43
+ state : 'value' ,
48
44
} ) ;
49
45
46
+ const [ tabPanelMap , setTabPanelMap ] = React . useState (
47
+ ( ) => new Map < Node , CompositeMetadata < TabPanelMetadata > | null > ( ) ,
48
+ ) ;
49
+ const [ tabMap , setTabMap ] = React . useState (
50
+ ( ) => new Map < Node , CompositeMetadata < TabMetadata > | null > ( ) ,
51
+ ) ;
52
+
53
+ const [ tabActivationDirection , setTabActivationDirection ] =
54
+ React . useState < TabActivationDirection > ( 'none' ) ;
55
+
56
+ const onValueChange = useEventCallback (
57
+ ( newValue : TabValue , activationDirection : TabActivationDirection , event : Event | undefined ) => {
58
+ setValue ( newValue ) ;
59
+ setTabActivationDirection ( activationDirection ) ;
60
+ onValueChangeProp ?.( newValue , event ) ;
61
+ } ,
62
+ ) ;
63
+
64
+ // get the `id` attribute of <Tabs.Panel> to set as the value of `aria-controls` on <Tabs.Tab>
65
+ const getTabPanelIdByTabValueOrIndex = React . useCallback (
66
+ ( tabValue : TabValue | undefined , index : number ) => {
67
+ if ( tabValue === undefined && index < 0 ) {
68
+ return undefined ;
69
+ }
70
+
71
+ for ( const tabPanelMetadata of tabPanelMap . values ( ) ) {
72
+ // find by tabValue
73
+ if ( tabValue !== undefined && tabPanelMetadata && tabValue === tabPanelMetadata ?. value ) {
74
+ return tabPanelMetadata . id ;
75
+ }
76
+
77
+ // find by index
78
+ if (
79
+ tabValue === undefined &&
80
+ tabPanelMetadata ?. index &&
81
+ tabPanelMetadata ?. index === index
82
+ ) {
83
+ return tabPanelMetadata . id ;
84
+ }
85
+ }
86
+
87
+ return undefined ;
88
+ } ,
89
+ [ tabPanelMap ] ,
90
+ ) ;
91
+
92
+ // get the `id` attribute of <Tabs.Tab> to set as the value of `aria-labelledby` on <Tabs.Panel>
93
+ const getTabIdByPanelValueOrIndex = React . useCallback (
94
+ ( tabPanelValue : TabValue | undefined , index : number ) => {
95
+ if ( tabPanelValue === undefined && index < 0 ) {
96
+ return undefined ;
97
+ }
98
+
99
+ for ( const tabMetadata of tabMap . values ( ) ) {
100
+ // find by tabPanelValue
101
+ if (
102
+ tabPanelValue !== undefined &&
103
+ index > - 1 &&
104
+ tabPanelValue === ( tabMetadata ?. value ?? tabMetadata ?. index ?? undefined )
105
+ ) {
106
+ return tabMetadata ?. id ;
107
+ }
108
+
109
+ // find by index
110
+ if (
111
+ tabPanelValue === undefined &&
112
+ index > - 1 &&
113
+ index === ( tabMetadata ?. value ?? tabMetadata ?. index ?? undefined )
114
+ ) {
115
+ return tabMetadata ?. id ;
116
+ }
117
+ }
118
+
119
+ return undefined ;
120
+ } ,
121
+ [ tabMap ] ,
122
+ ) ;
123
+
124
+ // used in `useActivationDirectionDetector` for setting data-activation-direction
125
+ const getTabElementBySelectedValue = React . useCallback (
126
+ ( selectedValue : TabValue | undefined ) : HTMLElement | null => {
127
+ if ( selectedValue === undefined ) {
128
+ return null ;
129
+ }
130
+
131
+ for ( const [ tabElement , tabMetadata ] of tabMap . entries ( ) ) {
132
+ if ( tabMetadata != null && selectedValue === ( tabMetadata . value ?? tabMetadata . index ) ) {
133
+ return tabElement as HTMLElement ;
134
+ }
135
+ }
136
+
137
+ return null ;
138
+ } ,
139
+ [ tabMap ] ,
140
+ ) ;
141
+
50
142
const tabsContextValue : TabsRootContext = React . useMemo (
51
143
( ) => ( {
52
144
direction,
@@ -77,12 +169,10 @@ export const TabsRoot = React.forwardRef(function TabsRoot(
77
169
tabActivationDirection,
78
170
} ;
79
171
80
- const { renderElement } = useComponentRenderer ( {
81
- render : render ?? 'div' ,
82
- className,
172
+ const renderElement = useRenderElement ( 'div' , componentProps , {
83
173
state,
84
- extraProps : other ,
85
174
ref : forwardedRef ,
175
+ props : elementProps ,
86
176
customStyleHookMapping : tabsStyleHookMapping ,
87
177
} ) ;
88
178
0 commit comments