1- /* eslint-disable jsx-a11y/interactive-supports-focus */
2- import type { HTMLAttributes , Ref } from "react" ;
1+ import type { HTMLAttributes } from "react" ;
2+ import { forwardRef } from "react" ;
33import {
4- Children ,
5- cloneElement ,
6- forwardRef ,
7- isValidElement ,
8- useEffect ,
9- useRef ,
10- } from "react" ;
4+ bem ,
5+ useIsUserInteractionMode ,
6+ useKeyboardFocus ,
7+ } from "@react-md/utils" ;
118import cn from "classnames" ;
12- import { applyRef , bem , useIsUserInteractionMode } from "@react-md/utils" ;
139
1410import type { TabsConfig } from "./types" ;
15- import {
16- UpdateIndicatorStylesProvider ,
17- useTabIndicatorStyle ,
18- } from "./useTabIndicatorStyle" ;
19- import { useTabsMovement } from "./useTabsMovement" ;
11+ import { useTabIndicatorStyles } from "./useTabIndicatorStyles" ;
2012
2113export interface TabsListProps
2214 extends HTMLAttributes < HTMLDivElement > ,
@@ -36,11 +28,13 @@ export interface TabsListProps
3628 /**
3729 * Boolean if the indicator transition should be disabled while the active tab
3830 * index changes.
31+ *
32+ * @defaultValue `false`
3933 */
4034 disableTransition ?: boolean ;
4135}
4236
43- const block = bem ( "rmd-tabs" ) ;
37+ const styles = bem ( "rmd-tabs" ) ;
4438
4539/**
4640 * The `TabsList` component is the container for all the individual `Tab`s that
@@ -57,7 +51,7 @@ export const TabsList = forwardRef<HTMLDivElement, TabsListProps>(
5751 {
5852 style,
5953 className,
60- onClick ,
54+ onFocus ,
6155 onKeyDown,
6256 children,
6357 activeIndex,
@@ -69,91 +63,46 @@ export const TabsList = forwardRef<HTMLDivElement, TabsListProps>(
6963 disableTransition = false ,
7064 ...props
7165 } ,
72- forwardedRef
66+ ref
7367 ) {
7468 const horizontal = orientation === "horizontal" ;
75- const { tabs, itemRefs, handleClick, handleKeyDown } = useTabsMovement ( {
76- onClick,
69+ const isKeyboard = useIsUserInteractionMode ( "keyboard" ) ;
70+ const { focusIndex : _focusIndex , ...eventHandlers } = useKeyboardFocus ( {
71+ onFocus,
7772 onKeyDown,
78- children,
79- horizontal,
80- activeIndex,
81- onActiveIndexChange,
82- automatic,
73+ onFocusChange ( element , focusIndex ) {
74+ element . focus ( ) ;
75+ if ( automatic ) {
76+ onActiveIndexChange ( focusIndex ) ;
77+ }
78+ } ,
8379 } ) ;
84- const [ mergedStyle , tabsRefHandler , tabsRef , updateIndicatorStyles ] =
85- useTabIndicatorStyle ( {
86- style,
87- ref : forwardedRef ,
88- align,
89- itemRefs,
90- totalTabs : tabs . length ,
91- activeIndex,
92- } ) ;
93- const isKeyboard = useIsUserInteractionMode ( "keyboard" ) ;
9480
95- const prevActiveIndex = useRef ( activeIndex ) ;
96- useEffect ( ( ) => {
97- const tabs = tabsRef . current ;
98- const tabRef = itemRefs [ activeIndex ] && itemRefs [ activeIndex ] . current ;
99- const incrementing = prevActiveIndex . current < activeIndex ;
100- prevActiveIndex . current = activeIndex ;
101- if ( ! tabs || ! tabRef ) {
102- return ;
103- }
104-
105- const currentX = tabs . scrollLeft + tabs . offsetWidth ;
106- const tabLeft = tabRef . offsetLeft ;
107- const tabWidth = tabRef . offsetWidth ;
108- if ( incrementing && currentX < tabLeft + tabWidth ) {
109- tabs . scrollLeft = tabLeft - tabWidth ;
110- } else if ( ! incrementing && tabs . scrollLeft > tabLeft ) {
111- tabs . scrollLeft = tabLeft ;
112- }
113-
114- // don't want this to trigger on itemRefs or tabsRef changes since those
115- // have a chance of updating each render.
116- // eslint-disable-next-line react-hooks/exhaustive-deps
117- } , [ activeIndex ] ) ;
81+ const { refCallback, indicatorStyles } = useTabIndicatorStyles ( {
82+ ref,
83+ activeIndex,
84+ } ) ;
11885
11986 return (
120- < UpdateIndicatorStylesProvider value = { updateIndicatorStyles } >
121- < div
122- { ...props }
123- aria-orientation = { orientation }
124- style = { mergedStyle }
125- role = "tablist"
126- className = { cn (
127- block ( {
128- [ align ] : true ,
129- padded,
130- vertical : ! horizontal ,
131- animate : ! disableTransition && ( ! automatic || ! isKeyboard ) ,
132- } ) ,
133- className
134- ) }
135- ref = { tabsRefHandler }
136- onClick = { handleClick }
137- onKeyDown = { handleKeyDown }
138- >
139- { Children . map ( tabs , ( child , i ) => {
140- if ( ! isValidElement ( child ) ) {
141- return child ;
142- }
143-
144- const tab = Children . only ( child ) ;
145- let ref : Ref < HTMLElement > = itemRefs [ i ] ;
146- if ( tab . props . ref ) {
147- ref = ( instance : HTMLElement | null ) => {
148- itemRefs [ i ] . current = instance ;
149- applyRef ( instance , tab . props . ref ) ;
150- } ;
151- }
152-
153- return cloneElement ( tab , { ref } ) ;
154- } ) }
155- </ div >
156- </ UpdateIndicatorStylesProvider >
87+ < div
88+ { ...props }
89+ aria-orientation = { orientation }
90+ style = { { ...style , ...indicatorStyles } }
91+ role = "tablist"
92+ ref = { refCallback }
93+ className = { cn (
94+ styles ( {
95+ [ align ] : true ,
96+ padded,
97+ vertical : ! horizontal ,
98+ animate : ! disableTransition && ( ! automatic || ! isKeyboard ) ,
99+ } ) ,
100+ className
101+ ) }
102+ { ...eventHandlers }
103+ >
104+ { children }
105+ </ div >
157106 ) ;
158107 }
159108) ;
0 commit comments