1
1
'use client' ;
2
- // TODO@ben : refactor to use HeadlessUI tabs
3
2
import cx from 'classnames' ;
4
- import {
3
+ import React , {
5
4
createContext ,
6
5
ReactNode ,
7
6
useContext ,
8
7
useEffect ,
9
8
useState ,
9
+ cloneElement ,
10
10
} from 'react' ;
11
11
12
12
export const TabContext = createContext ( '' ) ;
@@ -20,51 +20,58 @@ export function Tabs({
20
20
labels : string [ ] ;
21
21
children : ReactNode ;
22
22
} ) {
23
- const [ currentTab , setCurrentTab ] = useState ( labels [ 0 ] ) ;
23
+ const [ currentTab , setCurrentTab ] = useState < string > ( labels [ 0 ] ) ;
24
+
24
25
useEffect ( ( ) => {
25
26
const handleTabSelectedEvent = ( ) => {
26
27
const selectedTab = localStorage . getItem ( SELECTED_TAB_KEY ) ;
27
28
if ( selectedTab && labels . includes ( selectedTab ) ) {
28
29
setCurrentTab ( selectedTab ) ;
29
30
}
30
31
} ;
32
+
31
33
handleTabSelectedEvent ( ) ;
32
34
window . addEventListener ( TAB_SELECTED_EVENT , handleTabSelectedEvent ) ;
33
35
return ( ) =>
34
36
window . removeEventListener ( TAB_SELECTED_EVENT , handleTabSelectedEvent ) ;
35
37
} , [ labels ] ) ;
36
38
39
+ const handleTabClick = ( label : string ) => {
40
+ localStorage . setItem ( SELECTED_TAB_KEY , label ) ;
41
+ window . dispatchEvent ( new Event ( TAB_SELECTED_EVENT ) ) ;
42
+ setCurrentTab ( label ) ;
43
+ } ;
44
+
37
45
return (
38
46
< TabContext . Provider value = { currentTab } >
39
- < section >
40
- < div className = "not-prose " >
41
- < div className = "border-b border-slate-200 dark:border-slate-800" >
42
- < nav className = "-mb-px flex space-x-8" aria-label = "Tabs" >
43
- { labels . map ( ( label : string ) => (
44
- < button
45
- key = { label }
46
- role = "tab"
47
- aria-selected = { label === currentTab }
48
- onClick = { ( ) => {
49
- localStorage . setItem ( SELECTED_TAB_KEY , label ) ;
50
- window . dispatchEvent ( new Event ( TAB_SELECTED_EVENT ) ) ;
51
- setCurrentTab ( label ) ;
52
- } }
53
- className = { cx (
54
- 'whitespace-nowrap border-b-2 border-transparent p-2 text-sm font-medium' ,
55
- label === currentTab
56
- ? 'border-blue-500 text-slate-800 dark:border-sky-500 dark:text-slate-300'
57
- : 'text-slate-500 hover:border-blue-500 hover:text-slate-800 dark:text-slate-400 dark:hover:border-sky-500 dark:hover:text-slate-300'
58
- ) }
59
- >
60
- { label }
61
- </ button >
62
- ) ) }
63
- </ nav >
64
- </ div >
65
- </ div >
47
+ < nav className = "not-prose -mb-px flex space-x-8" aria-label = "Tabs" >
48
+ { labels . map ( ( label , index ) => (
49
+ < button
50
+ key = { label }
51
+ role = "tab"
52
+ aria-selected = { label === currentTab }
53
+ onClick = { ( ) => handleTabClick ( label ) }
54
+ className = { cx (
55
+ 'whitespace-nowrap border-b-2 p-2 text-sm font-medium' ,
56
+ label === currentTab
57
+ ? 'border-blue-500 text-slate-800 dark:border-sky-500 dark:text-slate-300'
58
+ : 'border-transparent text-slate-500 hover:border-blue-500 hover:text-slate-800 dark:text-slate-400 dark:hover:border-sky-500 dark:hover:text-slate-300'
59
+ ) }
60
+ >
61
+ { label }
62
+ </ button >
63
+ ) ) }
64
+ </ nav >
65
+ < div
66
+ className = { cx (
67
+ 'border border-slate-200 pb-2 pl-4 pr-4 pt-2 dark:border-slate-700' ,
68
+ currentTab === labels [ 0 ]
69
+ ? 'rounded-b-md rounded-tr-md'
70
+ : 'rounded-b-md rounded-t-md'
71
+ ) }
72
+ >
66
73
{ children }
67
- </ section >
74
+ </ div >
68
75
</ TabContext . Provider >
69
76
) ;
70
77
}
@@ -77,14 +84,23 @@ export function Tab({
77
84
children : ReactNode ;
78
85
} ) {
79
86
const currentTab = useContext ( TabContext ) ;
87
+ const isActive = label === currentTab ;
80
88
81
- if ( label !== currentTab ) {
82
- return null ;
83
- }
89
+ const passPropsToChildren = ( children : ReactNode ) => {
90
+ return React . Children . map ( children , ( child ) => {
91
+ if ( React . isValidElement ( child ) && typeof child . type !== 'string' ) {
92
+ return cloneElement ( child , { isWithinTab : true } ) ;
93
+ }
94
+ return child ;
95
+ } ) ;
96
+ } ;
84
97
85
98
return (
86
- < div className = "prose prose-slate dark:prose-invert mt-4 max-w-none" >
87
- { children }
99
+ < div
100
+ className = "prose prose-slate dark:prose-invert mt-2 max-w-none"
101
+ hidden = { ! isActive }
102
+ >
103
+ { isActive && passPropsToChildren ( children ) }
88
104
</ div >
89
105
) ;
90
106
}
0 commit comments