@@ -22,6 +22,7 @@ import {
22
22
Tab as RACTab ,
23
23
TabList as RACTabList ,
24
24
Tabs as RACTabs ,
25
+ SelectionIndicator ,
25
26
TabListStateContext ,
26
27
TabRenderProps
27
28
} from 'react-aria-components' ;
@@ -36,7 +37,7 @@ import {inertValue, useEffectEvent, useId, useLabels, useLayoutEffect, useResize
36
37
import { Picker , PickerItem } from './TabsPicker' ;
37
38
import { Text , TextContext } from './Content' ;
38
39
import { useControlledState } from '@react-stately/utils' ;
39
- import { useDOMRef , useMediaQuery } from '@react-spectrum/utils' ;
40
+ import { useDOMRef } from '@react-spectrum/utils' ;
40
41
import { useHasTabbableChild } from '@react-aria/focus' ;
41
42
import { useLocale } from '@react-aria/i18n' ;
42
43
import { useSpectrumContextProps } from './useSpectrumContextProps' ;
@@ -79,7 +80,6 @@ export interface TabPanelProps extends Omit<AriaTabPanelProps, 'children' | 'sty
79
80
export const TabsContext = createContext < ContextValue < Partial < TabsProps > , DOMRefValue < HTMLDivElement > > > ( null ) ;
80
81
const InternalTabsContext = createContext < Partial < TabsProps > & {
81
82
tablistRef ?: RefObject < HTMLDivElement | null > ,
82
- prevRef ?: RefObject < DOMRect | null > ,
83
83
selectedKey ?: Key | null
84
84
} > ( { } ) ;
85
85
@@ -133,14 +133,6 @@ export const Tabs = forwardRef(function Tabs(props: TabsProps, ref: DOMRef<HTMLD
133
133
}
134
134
135
135
let tablistRef = useRef < HTMLDivElement | null > ( null ) ;
136
- let prevRef = useRef < DOMRect | null > ( null ) ;
137
-
138
- let onChange = useEffectEvent ( ( val : Key ) => {
139
- if ( tablistRef . current ) {
140
- prevRef . current = tablistRef . current . querySelector ( '[role=tab][data-selected=true]' ) ?. getBoundingClientRect ( ) ?? null ;
141
- }
142
- setValue ( val ) ;
143
- } ) ;
144
136
145
137
return (
146
138
< Provider
@@ -152,8 +144,7 @@ export const Tabs = forwardRef(function Tabs(props: TabsProps, ref: DOMRef<HTMLD
152
144
disabledKeys,
153
145
selectedKey : value ,
154
146
tablistRef,
155
- prevRef,
156
- onSelectionChange : onChange ,
147
+ onSelectionChange : setValue ,
157
148
labelBehavior,
158
149
'aria-label' : props [ 'aria-label' ] ,
159
150
'aria-labelledby' : props [ 'aria-labelledby' ]
@@ -164,7 +155,7 @@ export const Tabs = forwardRef(function Tabs(props: TabsProps, ref: DOMRef<HTMLD
164
155
< CollapsingTabs
165
156
{ ...props }
166
157
selectedKey = { value }
167
- onSelectionChange = { onChange }
158
+ onSelectionChange = { setValue }
168
159
collection = { collection }
169
160
containerRef = { domRef } />
170
161
) }
@@ -294,6 +285,13 @@ const selectedIndicator = style<{isDisabled: boolean, orientation?: Orientation}
294
285
vertical : '[2px]'
295
286
}
296
287
} ,
288
+ contain : 'strict' ,
289
+ transition : {
290
+ default : '[translate,width,height]' ,
291
+ '@media (prefers-reduced-motion: reduce)' : 'none'
292
+ } ,
293
+ transitionDuration : 200 ,
294
+ transitionTimingFunction : 'out' ,
297
295
bottom : {
298
296
default : 0
299
297
} ,
@@ -374,7 +372,7 @@ const icon = style({
374
372
} ) ;
375
373
376
374
export function Tab ( props : TabProps ) : ReactNode {
377
- let { density, orientation, labelBehavior, prevRef } = useContext ( InternalTabsContext ) ?? { } ;
375
+ let { density, orientation, labelBehavior} = useContext ( InternalTabsContext ) ?? { } ;
378
376
379
377
let contentId = useId ( ) ;
380
378
let ariaLabelledBy = props [ 'aria-labelledby' ] || '' ;
@@ -390,7 +388,6 @@ export function Tab(props: TabProps): ReactNode {
390
388
{ ( {
391
389
// @ts -ignore
392
390
isMenu,
393
- isSelected,
394
391
isDisabled
395
392
} ) => {
396
393
if ( isMenu ) {
@@ -417,10 +414,8 @@ export function Tab(props: TabProps): ReactNode {
417
414
} ]
418
415
] } >
419
416
< TabInner
420
- isSelected = { isSelected }
421
417
orientation = { orientation ! }
422
- isDisabled = { isDisabled }
423
- prevRef = { prevRef } >
418
+ isDisabled = { isDisabled } >
424
419
{ typeof props . children === 'string' ? < Text > { props . children } </ Text > : props . children }
425
420
</ TabInner >
426
421
</ Provider >
@@ -431,53 +426,17 @@ export function Tab(props: TabProps): ReactNode {
431
426
) ;
432
427
}
433
428
434
- function TabInner ( { isSelected, isDisabled, orientation, children, prevRef} : {
435
- isSelected : boolean ,
429
+ function TabInner ( { isDisabled, orientation, children} : {
436
430
isDisabled : boolean ,
437
431
orientation : Orientation ,
438
- children : ReactNode ,
439
- prevRef ?: RefObject < DOMRect | null >
432
+ children : ReactNode
440
433
} ) {
441
- let reduceMotion = useMediaQuery ( '(prefers-reduced-motion: reduce)' ) ;
442
434
let ref = useRef < HTMLDivElement | null > ( null ) ;
443
-
444
- useLayoutEffect ( ( ) => {
445
- if ( isSelected && prevRef ?. current && ref ?. current && ! reduceMotion ) {
446
- let currentItem = ref ?. current . getBoundingClientRect ( ) ;
447
-
448
- if ( orientation === 'horizontal' ) {
449
- let deltaX = prevRef . current . left - currentItem . left ;
450
- ref . current . animate (
451
- [
452
- { transform : `translateX(${ deltaX } px)` , width : `${ prevRef . current . width } px` } ,
453
- { transform : 'translateX(0px)' , width : '100%' }
454
- ] ,
455
- {
456
- duration : 200 ,
457
- easing : 'ease-out'
458
- }
459
- ) ;
460
- } else {
461
- let deltaY = prevRef . current . top - currentItem . top ;
462
- ref . current . animate (
463
- [
464
- { transform : `translateY(${ deltaY } px)` , height : `${ prevRef . current . height } px` } ,
465
- { transform : 'translateY(0px)' , height : '100%' }
466
- ] ,
467
- {
468
- duration : 200 ,
469
- easing : 'ease-out'
470
- }
471
- ) ;
472
- }
473
-
474
- prevRef . current = null ;
475
- }
476
- } , [ isSelected , reduceMotion , prevRef , orientation ] ) ;
435
+ let isHidden = useContext ( HiddenTabsContext ) ;
477
436
478
437
return (
479
438
< >
480
- { isSelected && < div ref = { ref } className = { selectedIndicator ( { isDisabled, orientation} ) } /> }
439
+ { ! isHidden && < SelectionIndicator ref = { ref } className = { selectedIndicator ( { isDisabled, orientation} ) } /> }
481
440
{ children }
482
441
</ >
483
442
) ;
@@ -549,6 +508,8 @@ function isEveryTabDisabled<T>(collection: Collection<Node<T>> | undefined, disa
549
508
return false ;
550
509
}
551
510
511
+ const HiddenTabsContext = createContext ( false ) ;
512
+
552
513
let HiddenTabs = function ( props : {
553
514
listRef : RefObject < HTMLDivElement | null > ,
554
515
items : Array < Node < any > > ,
@@ -573,18 +534,20 @@ let HiddenTabs = function (props: {
573
534
overflow : 'hidden' ,
574
535
opacity : 0
575
536
} ) } >
576
- { items . map ( ( item ) => {
577
- // pull off individual props as an allow list, don't want refs or other props getting through
578
- return (
579
- < div
580
- data-hidden-tab
581
- style = { item . props . UNSAFE_style }
582
- key = { item . key }
583
- className = { item . props . className ( { size, density} ) } >
584
- { item . props . children ( { size, density} ) }
585
- </ div >
586
- ) ;
587
- } ) }
537
+ < HiddenTabsContext . Provider value >
538
+ { items . map ( ( item ) => {
539
+ // pull off individual props as an allow list, don't want refs or other props getting through
540
+ return (
541
+ < div
542
+ data-hidden-tab
543
+ style = { item . props . UNSAFE_style }
544
+ key = { item . key }
545
+ className = { item . props . className ( { size, density} ) } >
546
+ { item . props . children ( { size, density} ) }
547
+ </ div >
548
+ ) ;
549
+ } ) }
550
+ </ HiddenTabsContext . Provider >
588
551
</ div >
589
552
) ;
590
553
} ;
0 commit comments