1212
1313import { classNames , SlotProvider , unwrapDOMRef , useDOMRef , useStyleProps } from '@react-spectrum/utils' ;
1414import { DOMProps , DOMRef , Node , Orientation } from '@react-types/shared' ;
15- import { filterDOMProps , useValueEffect } from '@react-aria/utils' ;
15+ import { filterDOMProps } from '@react-aria/utils' ;
1616import { FocusRing } from '@react-aria/focus' ;
1717import { Item , Picker } from '@react-spectrum/picker' ;
1818import { ListCollection , SingleSelectListState } from '@react-stately/list' ;
@@ -36,7 +36,7 @@ interface TabsContext<T> {
3636 tabListState : TabListState < T > ,
3737 setTabListState : ( state : TabListState < T > ) => void ,
3838 selectedTab : HTMLElement ,
39- collapse : boolean
39+ collapsed : boolean
4040 } ,
4141 refs : {
4242 wrapperRef : MutableRefObject < HTMLDivElement > ,
@@ -64,7 +64,7 @@ function Tabs<T extends object>(props: SpectrumTabsProps<T>, ref: DOMRef<HTMLDiv
6464
6565 let { direction} = useLocale ( ) ;
6666 let { styleProps} = useStyleProps ( otherProps ) ;
67- let [ collapse , setCollapse ] = useValueEffect ( false ) ;
67+ let [ collapsed , setCollapsed ] = useState ( false ) ;
6868 let [ selectedTab , setSelectedTab ] = useState < HTMLElement > ( ) ;
6969 const [ tabListState , setTabListState ] = useState < TabListState < T > > ( null ) ;
7070
@@ -77,34 +77,21 @@ function Tabs<T extends object>(props: SpectrumTabsProps<T>, ref: DOMRef<HTMLDiv
7777 }
7878 }
7979 // collapse is in the dep array so selectedTab can be updated for TabLine positioning
80- } , [ children , tabListState ?. selectedKey , collapse , tablistRef ] ) ;
80+ } , [ children , tabListState ?. selectedKey , collapsed , tablistRef ] ) ;
8181
8282 let checkShouldCollapse = useCallback ( ( ) => {
83- let computeShouldCollapse = ( ) => {
84- if ( wrapperRef . current ) {
85- let tabsComponent = wrapperRef . current ;
86- let tabs = tablistRef . current . querySelectorAll ( '[role="tab"]' ) ;
87- let lastTab = tabs [ tabs . length - 1 ] ;
88-
89- let end = direction === 'rtl' ? 'left' : 'right' ;
90- let farEdgeTabList = tabsComponent . getBoundingClientRect ( ) [ end ] ;
91- let farEdgeLastTab = lastTab ?. getBoundingClientRect ( ) [ end ] ;
92- let shouldCollapse = direction === 'rtl' ? farEdgeLastTab < farEdgeTabList : farEdgeTabList < farEdgeLastTab ;
93-
94- return shouldCollapse ;
95- }
96- } ;
97-
98- if ( orientation !== 'vertical' ) {
99- setCollapse ( function * ( ) {
100- // Make Tabs render in non-collapsed state
101- yield false ;
102-
103- // Compute if Tabs should collapse and update
104- yield computeShouldCollapse ( ) ;
105- } ) ;
83+ if ( wrapperRef . current && orientation !== 'vertical' ) {
84+ let tabsComponent = wrapperRef . current ;
85+ let tabs = tablistRef . current . querySelectorAll ( '[role="tab"]' ) ;
86+ let lastTab = tabs [ tabs . length - 1 ] ;
87+
88+ let end = direction === 'rtl' ? 'left' : 'right' ;
89+ let farEdgeTabList = tabsComponent . getBoundingClientRect ( ) [ end ] ;
90+ let farEdgeLastTab = lastTab ?. getBoundingClientRect ( ) [ end ] ;
91+ let shouldCollapse = direction === 'rtl' ? farEdgeLastTab < farEdgeTabList : farEdgeTabList < farEdgeLastTab ;
92+ setCollapsed ( shouldCollapse ) ;
10693 }
107- } , [ tablistRef , wrapperRef , direction , orientation , setCollapse ] ) ;
94+ } , [ tablistRef , wrapperRef , direction , orientation , setCollapsed ] ) ;
10895
10996 useEffect ( ( ) => {
11097 checkShouldCollapse ( ) ;
@@ -118,14 +105,14 @@ function Tabs<T extends object>(props: SpectrumTabsProps<T>, ref: DOMRef<HTMLDiv
118105
119106 // When the tabs are collapsed, the tabPanel should be labelled by the Picker button element.
120107 let collapsibleTabListId = useId ( ) ;
121- if ( collapse && orientation !== 'vertical' ) {
108+ if ( collapsed && orientation !== 'vertical' ) {
122109 tabPanelProps [ 'aria-labelledby' ] = collapsibleTabListId ;
123110 }
124111 return (
125112 < TabContext . Provider
126113 value = { {
127114 tabProps : { ...props , orientation, density} ,
128- tabState : { tabListState, setTabListState, selectedTab, collapse } ,
115+ tabState : { tabListState, setTabListState, selectedTab, collapsed } ,
129116 refs : { tablistRef, wrapperRef} ,
130117 tabPanelProps
131118 } } >
@@ -255,7 +242,7 @@ export function TabList<T>(props: SpectrumTabListProps<T>) {
255242 const tabContext = useContext ( TabContext ) ;
256243 const { refs, tabState, tabProps, tabPanelProps} = tabContext ;
257244 const { isQuiet, density, isDisabled, isEmphasized, orientation} = tabProps ;
258- const { selectedTab, collapse , setTabListState} = tabState ;
245+ const { selectedTab, collapsed , setTabListState} = tabState ;
259246 const { tablistRef, wrapperRef} = refs ;
260247 // Pass original Tab props but override children to create the collection.
261248 const state = useTabListState ( { ...tabProps , children : props . children } ) ;
@@ -268,12 +255,18 @@ export function TabList<T>(props: SpectrumTabListProps<T>) {
268255 setTabListState ( state ) ;
269256 // eslint-disable-next-line react-hooks/exhaustive-deps
270257 } , [ state . disabledKeys , state . selectedItem , state . selectedKey , props . children ] ) ;
271- let stylePropsForVertical = orientation === 'vertical' ? styleProps : { } ;
258+
259+ let collapseStyle : React . CSSProperties = collapsed && orientation !== 'vertical' ? { maxWidth : 'calc(100% + 1px)' , overflow : 'hidden' , visibility : 'hidden' , position : 'absolute' } : { maxWidth : 'calc(100% + 1px)' } ;
260+ let stylePropsFinal = orientation === 'vertical' ? styleProps : { style : collapseStyle } ;
261+
262+ if ( collapsed && orientation !== 'vertical' ) {
263+ tabListProps [ 'aria-hidden' ] = true ;
264+ }
272265
273266 let tabListclassName = classNames ( styles , 'spectrum-TabsPanel-tabs' ) ;
274267 const tabContent = (
275268 < div
276- { ...stylePropsForVertical }
269+ { ...stylePropsFinal }
277270 { ...tabListProps }
278271 ref = { tablistRef }
279272 className = { classNames (
@@ -309,7 +302,8 @@ export function TabList<T>(props: SpectrumTabListProps<T>) {
309302 'spectrum-TabsPanel-collapseWrapper' ,
310303 styleProps . className
311304 ) } >
312- { collapse ? < TabPicker { ...props } { ...tabProps } id = { tabPanelProps [ 'aria-labelledby' ] } state = { state } className = { tabListclassName } /> : tabContent }
305+ < TabPicker { ...props } { ...tabProps } visible = { collapsed } id = { tabPanelProps [ 'aria-labelledby' ] } state = { state } className = { tabListclassName } />
306+ { tabContent }
313307 </ div >
314308 ) ;
315309 }
@@ -359,7 +353,8 @@ interface TabPickerProps<T> extends Omit<SpectrumPickerProps<T>, 'children'> {
359353 density ?: 'compact' | 'regular' ,
360354 isEmphasized ?: boolean ,
361355 state : SingleSelectListState < T > ,
362- className ?: string
356+ className ?: string ,
357+ visible : boolean
363358}
364359
365360function TabPicker < T > ( props : TabPickerProps < T > ) {
@@ -372,7 +367,8 @@ function TabPicker<T>(props: TabPickerProps<T>) {
372367 'aria-label' : ariaLabel ,
373368 density,
374369 className,
375- id
370+ id,
371+ visible
376372 } = props ;
377373
378374 let ref = useRef ( ) ;
@@ -394,6 +390,8 @@ function TabPicker<T>(props: TabPickerProps<T>) {
394390 'aria-label' : ariaLabel
395391 } ;
396392
393+ const style : React . CSSProperties = visible ? { } : { visibility : 'hidden' , position : 'absolute' } ;
394+
397395 // TODO: Figure out if tabListProps should go onto the div here, v2 doesn't do it
398396 return (
399397 < div
@@ -408,7 +406,9 @@ function TabPicker<T>(props: TabPickerProps<T>) {
408406 'spectrum-Tabs--emphasized' : isEmphasized
409407 } ,
410408 className
411- ) } >
409+ ) }
410+ style = { style }
411+ aria-hidden = { visible ? undefined : true } >
412412 < SlotProvider
413413 slots = { {
414414 icon : {
@@ -425,7 +425,7 @@ function TabPicker<T>(props: TabPickerProps<T>) {
425425 items = { items }
426426 ref = { ref }
427427 isQuiet
428- isDisabled = { isDisabled }
428+ isDisabled = { ! visible || isDisabled }
429429 selectedKey = { state . selectedKey }
430430 disabledKeys = { state . disabledKeys }
431431 onSelectionChange = { state . setSelectedKey } >
0 commit comments