1- import React , { useEffect , useRef , useState } from 'react' ;
2- import { has , is , isNil } from 'ramda' ;
1+ import React , { useCallback , useEffect , useRef , useState } from 'react' ;
2+ import { has , isNil } from 'ramda' ;
33
44import LoadingElement from '../utils/_LoadingElement' ;
5- import { DashComponent } from '@dash-renderer /types/component ' ;
5+ import { PersistedProps , PersistenceTypes , TabProps , TabsProps } from '.. /types' ;
66import './css/tabs.css' ;
7+ import { DashComponent } from '@dash-renderer/types/component' ;
78
8- interface EnhancedTabProps {
9- id ?: string ;
10- label ?: string | DashComponent [ ] ;
9+ interface EnhancedTabProps extends TabProps {
1110 selected : boolean ;
12- className ?: string ;
13- style ?: React . CSSProperties ;
14- selectedClassName ?: string ;
15- selected_style ?: React . CSSProperties ;
16- selectHandler : ( value : string ) => void ;
17- value : string ;
18- disabled ?: boolean ;
19- disabled_style ?: React . CSSProperties ;
20- disabled_className ?: string ;
21- componentPath : string [ ] ;
11+ componentPath ?: ( string | number ) [ ] ;
2212}
23- import { PersistedProps , PersistenceTypes , TabsProps } from '../types' ;
2413
25- // EnhancedTab is defined here instead of in Tab.react.js because if exported there,
14+ // EnhancedTab is defined here instead of in Tab.tsx because if exported there,
2615// it will mess up the Python imports and metadata.json
2716const EnhancedTab = ( {
2817 id,
2918 label,
3019 selected,
3120 className,
3221 style,
33- selectedClassName ,
22+ selected_className ,
3423 selected_style,
35- selectHandler,
24+ setProps : selectHandler ,
3625 value,
3726 disabled = false ,
38- disabled_style = { color : '#d6d6d6 ' } ,
27+ disabled_style = { color : 'var(--Dash-Text-Disabled) ' } ,
3928 disabled_className,
4029 componentPath,
4130} : EnhancedTabProps ) => {
31+ const ExternalWrapper = window . dash_component_api . ExternalWrapper ;
4232 const ctx = window . dash_component_api . useDashContext ( ) ;
33+ componentPath = componentPath ?? ctx . componentPath ;
4334 // We use the raw path here since it's up one level from
4435 // the tabs child.
45- const isLoading = ctx . useLoading ( { rawPath : ! ! componentPath } ) ;
36+ const isLoading = ctx . useLoading ( { rawPath : componentPath } ) ;
37+ const tabStyle = {
38+ ...style ,
39+ ...( disabled ? disabled_style : { } ) ,
40+ ...( selected ? selected_style : { } ) ,
41+ } ;
42+
43+ const tabClassNames = [
44+ 'tab' ,
45+ className ,
46+ disabled ? 'tab--disabled' : null ,
47+ disabled ? disabled_className : null ,
48+ selected ? 'tab--selected' : null ,
49+ selected ? selected_className : null ,
50+ ] . filter ( Boolean ) ;
4651
47- let tabStyle = style ;
48- if ( disabled ) {
49- tabStyle = { ...tabStyle , ...disabled_style } ;
50- }
51- if ( selected ) {
52- tabStyle = { ...tabStyle , ...selected_style } ;
53- }
54- let tabClassName = `tab ${ className || '' } ` ;
55- if ( disabled ) {
56- tabClassName += ` tab--disabled ${ disabled_className || '' } ` ;
57- }
58- if ( selected ) {
59- tabClassName += ` tab--selected ${ selectedClassName || '' } ` ;
60- }
6152 let labelDisplay ;
62- if ( is ( Array , label ) ) {
63- // label is an array, so it has children that we want to render
64- labelDisplay = label [ 0 ] . props . children ;
53+ if ( typeof label === 'object' ) {
54+ labelDisplay = (
55+ < ExternalWrapper
56+ component = { label }
57+ componentPath = { [ ...componentPath , 0 ] }
58+ />
59+ ) ;
6560 } else {
66- // else it is a string, so we just want to render that
67- labelDisplay = label ;
61+ labelDisplay = < span > { label } </ span > ;
6862 }
63+
6964 return (
7065 < div
7166 data-dash-is-loading = { isLoading }
72- className = { tabClassName }
67+ className = { tabClassNames . join ( ' ' ) }
7368 id = { id }
7469 style = { tabStyle }
7570 onClick = { ( ) => {
7671 if ( ! disabled ) {
77- selectHandler ( value ) ;
72+ selectHandler ( { value} ) ;
7873 }
7974 } }
8075 >
81- < span > { labelDisplay } </ span >
76+ { labelDisplay }
8277 </ div >
8378 ) ;
8479} ;
@@ -101,26 +96,28 @@ function Tabs({
10196 persisted_props = [ PersistedProps . value ] ,
10297 // eslint-disable-next-line @typescript-eslint/no-unused-vars
10398 persistence_type = PersistenceTypes . local ,
99+ children,
104100 ...props
105101} : TabsProps ) {
106102 const initializedRef = useRef ( false ) ;
107103 const [ isAboveBreakpoint , setIsAboveBreakpoint ] = useState ( false ) ;
108104
109- const parseChildrenToArray = ( ) => {
110- if ( props . children && ! is ( Array , props . children ) ) {
111- // if dcc.Tabs.children contains just one single element, it gets passed as an object
112- // instead of an array - so we put it in an array ourselves!
113- return [ props . children ] ;
105+ const parseChildrenToArray = useCallback ( ( ) : DashComponent [ ] => {
106+ if ( ! children ) {
107+ return [ ] ;
114108 }
115- return props . children ?? [ ] ;
116- } ;
109+ if ( children instanceof Array ) {
110+ return children ;
111+ }
112+ return [ children ] ;
113+ } , [ children ] ) ;
117114
118115 const valueOrDefault = ( ) => {
119116 if ( has ( 'value' , props ) ) {
120117 return props . value ;
121118 }
122119 const children = parseChildrenToArray ( ) ;
123- if ( children && children . length ) {
120+ if ( children && children . length && children [ 0 ] . props . componentPath ) {
124121 const firstChildren = window . dash_component_api . getLayout ( [
125122 ...children [ 0 ] . props . componentPath ,
126123 'props' ,
@@ -131,10 +128,6 @@ function Tabs({
131128 return 'tab-1' ;
132129 } ;
133130
134- const selectHandler = ( value : string ) => {
135- props . setProps ( { value : value } ) ;
136- } ;
137-
138131 // Initialize value on mount if not set
139132 useEffect ( ( ) => {
140133 if ( ! initializedRef . current && ! has ( 'value' , props ) ) {
@@ -167,15 +160,15 @@ function Tabs({
167160
168161 const value = valueOrDefault ( ) ;
169162
170- if ( props . children ) {
163+ if ( children ) {
171164 const children = parseChildrenToArray ( ) ;
172165
173166 EnhancedTabs = children . map ( ( child , index ) => {
174167 // TODO: handle components that are not dcc.Tab components (throw error)
175168 // enhance Tab components coming from Dash (as dcc.Tab) with methods needed for handling logic
176- let childProps ;
169+ let childProps : Omit < TabProps , 'setProps' > ;
177170
178- if ( React . isValidElement ( child ) ) {
171+ if ( React . isValidElement ( child ) && child . props . componentPath ) {
179172 childProps = window . dash_component_api . getLayout ( [
180173 ...child . props . componentPath ,
181174 'props' ,
@@ -194,22 +187,31 @@ function Tabs({
194187 selectedTab = child ;
195188 }
196189
190+ const style = childProps . style ?? { } ;
191+ if ( typeof childProps . width === 'number' ) {
192+ style . width = `${ childProps . width } px` ;
193+ style . flex = 'none' ;
194+ } else if ( typeof childProps . width === 'string' ) {
195+ style . width = childProps . width ;
196+ style . flex = 'none' ;
197+ }
198+
197199 return (
198200 < EnhancedTab
199201 key = { index }
200202 id = { childProps . id }
201203 label = { childProps . label }
202204 selected = { value === childProps . value }
203- selectHandler = { selectHandler }
205+ setProps = { props . setProps }
204206 className = { childProps . className }
205- style = { childProps . style }
206- selectedClassName = { childProps . selected_className }
207+ style = { style }
208+ selected_className = { childProps . selected_className }
207209 selected_style = { childProps . selected_style }
208210 value = { childProps . value }
209211 disabled = { childProps . disabled }
210212 disabled_style = { childProps . disabled_style }
211213 disabled_className = { childProps . disabled_className }
212- componentPath = { child . componentPath }
214+ componentPath = { child . props . componentPath }
213215 />
214216 ) ;
215217 } ) ;
@@ -241,7 +243,6 @@ function Tabs({
241243 '--tabs-border' : colors . border ,
242244 '--tabs-primary' : colors . primary ,
243245 '--tabs-background' : colors . background ,
244- '--tabs-width' : `calc(100% / ${ parseChildrenToArray ( ) . length } )` ,
245246 } as const ;
246247
247248 return (
0 commit comments