1
1
import "./Tabs.css" ;
2
- import React , { useImperativeHandle , useReducer } from "react" ;
2
+ import React , { useEffect , useImperativeHandle , useReducer } from "react" ;
3
3
import { noop } from "src/utils/noop.ts" ;
4
- import { removeItem } from "src/utils/array-utils.ts" ;
5
- import {
6
- closestItem ,
7
- TabModel ,
8
- } from "src/tabbed-navigation.tsx" ;
4
+ import { removeItem } from "src/utils/array-utils.ts" ;
5
+ import { closestItem , TabModel } from "src/tabbed-navigation.tsx" ;
9
6
import { Tab } from "src/components/Tabs/Tab.tsx" ;
10
7
11
8
export type TabsApi = {
12
- setTabs : ( tabs : TabModel [ ] | { ( prevTabs : TabModel [ ] ) :TabModel [ ] } ) => void ;
13
- setActiveTabId : ( id : string ) => void ;
14
- }
9
+ setTabs : ( tabs : TabModel [ ] | { ( prevTabs : TabModel [ ] ) : TabModel [ ] } ) => void ;
10
+ setActiveTabId : ( id : string ) => void ;
11
+ getTabs : ( ) => TabModel [ ] ;
12
+ } ;
15
13
16
14
type TabsProps = {
15
+ hasControlledActiveTabId ?: boolean ;
16
+ activeTabId ?: string ;
17
+ tabs ?: TabModel [ ] ;
17
18
storeKey : string ;
18
19
onActiveTabChange ?: ( tab : TabModel | undefined ) => void ;
20
+ onTabsChange ?: ( tabs : TabModel [ ] ) => void ;
19
21
apiRef ?: React . Ref < TabsApi | undefined > ;
20
22
} ;
21
23
22
24
type State = {
23
25
readonly tabs : TabModel [ ] ;
24
26
readonly activeTabId : string | undefined ;
25
- }
27
+ } ;
26
28
27
29
type Action =
28
30
| {
29
- type : 'close-tab'
30
- tab : TabModel
31
- } | {
32
- type : 'set-tabs'
33
- tabs : TabModel [ ]
34
- } | {
35
- type : 'set-active-tab-id'
36
- id : string ;
37
- } ;
31
+ type : "close-tab" ;
32
+ tab : TabModel ;
33
+ onActiveTabChange ?: ( tab : TabModel | undefined ) => void ;
34
+ onTabsChange ?: ( tab : TabModel [ ] ) => void ;
35
+ }
36
+ | {
37
+ type : "set-tabs" ;
38
+ tabs : TabModel [ ] ;
39
+ onTabsChange ?: ( tab : TabModel [ ] ) => void ;
40
+ }
41
+ | {
42
+ type : "set-active-tab-id" ;
43
+ id : string ;
44
+ onActiveTabChange ?: ( tab : TabModel | undefined ) => void ;
45
+ } ;
38
46
39
47
function reducer ( state : State , action : Action ) : State {
40
48
switch ( action . type ) {
41
- case 'close-tab' : {
42
- const { tab} = action ;
43
- const { tabs, activeTabId : prevActiveId , ...rest } = state ;
49
+ case "close-tab" : {
50
+ const { tab, onTabsChange, onActiveTabChange } = action ;
51
+ const { tabs, activeTabId : prevActiveId , ...rest } = state ;
52
+
53
+ const prevActiveTab = tabs . find ( ( tab ) => tab . id === prevActiveId ) ;
54
+ const activeTab =
55
+ prevActiveId === tab . id ? closestItem ( tabs , tab ) : prevActiveTab ;
56
+
57
+ const updatedTabs = removeItem ( tabs , tab ) ;
58
+
59
+ onTabsChange ?.( updatedTabs ) ;
60
+ onActiveTabChange ?.( activeTab ) ;
44
61
45
- const activeTabId = prevActiveId === tab . id ? closestItem ( tabs , tab ) ?. id : prevActiveId ;
46
62
return {
47
- tabs : removeItem ( tabs , tab ) ,
48
- activeTabId : activeTabId === tab . id ? closestItem ( tabs , tab ) ? .id : activeTabId ,
49
- ...rest
50
- }
63
+ tabs : updatedTabs ,
64
+ activeTabId : activeTab ? .id ,
65
+ ...rest ,
66
+ } ;
51
67
}
52
- case ' set-tabs' : {
53
- const { tabs} = action ;
54
-
68
+ case " set-tabs" : {
69
+ const { tabs, onTabsChange } = action ;
70
+ onTabsChange ?. ( tabs ) ;
55
71
return {
56
72
...state ,
57
73
tabs,
58
- }
74
+ } ;
59
75
}
60
- case 'set-active-tab-id' : {
61
- const { id} = action ;
62
-
76
+ case "set-active-tab-id" : {
77
+ const { id, onActiveTabChange } = action ;
78
+ const activeTab = state . tabs . find ( ( tab ) => tab . id === id ) ;
79
+ onActiveTabChange ?.( activeTab ) ;
63
80
return {
64
81
...state ,
65
82
activeTabId : id ,
66
- }
83
+ } ;
67
84
}
68
85
}
69
86
}
70
87
71
88
export function Tabs ( props : TabsProps ) {
72
- const { onActiveTabChange = noop , apiRef } = props ;
89
+ const {
90
+ onActiveTabChange = noop ,
91
+ apiRef,
92
+ activeTabId : activeTabIdProp ,
93
+ hasControlledActiveTabId,
94
+ tabs : tabsProp ,
95
+ onTabsChange,
96
+ } = props ;
73
97
74
98
const [ state , dispatch ] = useReducer ( reducer , {
75
99
tabs : [ ] ,
76
100
activeTabId : undefined ,
77
101
} ) ;
78
102
79
- useImperativeHandle ( apiRef , ( ) => ( {
103
+ useImperativeHandle < TabsApi > ( apiRef , ( ) => ( {
80
104
setTabs : ( tabsArg ) => {
81
- const tabs = typeof tabsArg === 'function' ? tabsArg ( state . tabs ) : tabsArg ;
105
+ const tabs =
106
+ typeof tabsArg === "function" ? tabsArg ( state . tabs ) : tabsArg ;
82
107
dispatch ( {
83
- type : 'set-tabs' ,
84
- tabs
85
- } )
108
+ type : "set-tabs" ,
109
+ tabs,
110
+ onTabsChange,
111
+ } ) ;
86
112
} ,
87
113
setActiveTabId : ( id : string ) => {
88
114
dispatch ( {
89
- type : 'set-active-tab-id' ,
90
- id
91
- } )
92
- }
93
- } ) )
115
+ type : "set-active-tab-id" ,
116
+ id,
117
+ onActiveTabChange,
118
+ } ) ;
119
+ } ,
120
+ getTabs : ( ) => state . tabs ,
121
+ } ) ) ;
94
122
95
- const { tabs, activeTabId} = state ;
123
+ const { tabs, activeTabId } = state ;
96
124
97
- const activeTab = tabs . find ( tab => tab . id === activeTabId ) ;
125
+ const activeTab = tabs . find ( ( tab ) => tab . id === activeTabId ) ;
98
126
99
127
const closeTab = ( tab : TabModel ) => {
100
128
dispatch ( {
101
- type : 'close-tab' ,
102
- tab
103
- } )
129
+ type : "close-tab" ,
130
+ tab,
131
+ onTabsChange,
132
+ onActiveTabChange,
133
+ } ) ;
104
134
} ;
105
135
136
+ useEffect ( ( ) => {
137
+ if ( hasControlledActiveTabId ) {
138
+ dispatch ( {
139
+ type : "set-active-tab-id" ,
140
+ id : activeTabIdProp ,
141
+ } ) ;
142
+ }
143
+ } , [ hasControlledActiveTabId , activeTabIdProp ] ) ;
144
+
145
+ useEffect ( ( ) => {
146
+ if ( tabsProp ) {
147
+ dispatch ( {
148
+ type : "set-tabs" ,
149
+ tabs : tabsProp ,
150
+ } ) ;
151
+ }
152
+ } , [ tabsProp ] ) ;
153
+
106
154
if ( tabs . length < 1 ) {
107
155
return null ;
108
156
}
@@ -121,4 +169,3 @@ export function Tabs(props: TabsProps) {
121
169
</ div >
122
170
) ;
123
171
}
124
-
0 commit comments