1- import React , { useRef , useState , useLayoutEffect , useCallback } from "react" ;
1+ import React , { useRef , useCallback } from "react" ;
22import { TabsProvider , useTabsContext } from "./TabComponents" ;
33
4- const HORIZONTAL_PADDING = 16 ;
5-
64function AnimatedTabs ( { children, defaultIndex = 0 , ...rest } ) {
7- const [ activeRect , setActiveRect ] = useState ( null ) ;
8- const containerRef = useRef ( null ) ;
9- const [ containerRect , setContainerRect ] = useState ( null ) ;
10-
11- // Update container rect on mount and resize
12- useLayoutEffect ( ( ) => {
13- const updateRect = ( ) => {
14- if ( containerRef . current ) {
15- setContainerRect ( containerRef . current . getBoundingClientRect ( ) ) ;
16- }
17- } ;
18-
19- updateRect ( ) ;
20- window . addEventListener ( "resize" , updateRect ) ;
21- return ( ) => window . removeEventListener ( "resize" , updateRect ) ;
22- } , [ ] ) ;
23-
245 return (
256 < TabsProvider defaultIndex = { defaultIndex } >
26- < TabsContextBridge setActiveRect = { setActiveRect } >
27- < div
28- ref = { containerRef }
29- style = { { ...rest . style , position : "relative" } }
30- { ...rest }
31- >
32- { /* Animated underline indicator */ }
33- < div
34- className = "absolute bg-sky-500"
35- style = { {
36- height : 4 ,
37- transition : "all 300ms ease" ,
38- left : activeRect && containerRect
39- ? activeRect . left - containerRect . left + HORIZONTAL_PADDING
40- : 0 ,
41- top : activeRect && containerRect
42- ? activeRect . bottom - containerRect . top - 3
43- : 0 ,
44- width : activeRect ? activeRect . width - HORIZONTAL_PADDING * 1.5 : 0 ,
45- opacity : activeRect ? 1 : 0 ,
46- } }
47- />
48- { children }
49- </ div >
50- </ TabsContextBridge >
7+ < div style = { { ...rest . style } } { ...rest } >
8+ { children }
9+ </ div >
5110 </ TabsProvider >
5211 ) ;
5312}
5413
55- // Helper component to access context and pass setActiveRect
56- function TabsContextBridge ( { children, setActiveRect } ) {
57- return (
58- < ActiveRectContext . Provider value = { setActiveRect } >
59- { children }
60- </ ActiveRectContext . Provider >
61- ) ;
62- }
63-
64- const ActiveRectContext = React . createContext ( null ) ;
65-
6614function AnimatedTab ( { index, children, style, ...props } ) {
6715 const { selectedIndex, selectTab } = useTabsContext ( ) ;
6816 const isSelected = selectedIndex === index ;
6917 const tabRef = useRef ( null ) ;
70- const setActiveRect = React . useContext ( ActiveRectContext ) ;
71-
72- // Update active rect when this tab becomes selected
73- useLayoutEffect ( ( ) => {
74- if ( isSelected && tabRef . current ) {
75- setActiveRect ( tabRef . current . getBoundingClientRect ( ) ) ;
76- }
77- } , [ isSelected , setActiveRect ] ) ;
78-
79- // Also update on resize
80- useLayoutEffect ( ( ) => {
81- if ( ! isSelected ) return ;
82-
83- const updateRect = ( ) => {
84- if ( tabRef . current ) {
85- setActiveRect ( tabRef . current . getBoundingClientRect ( ) ) ;
86- }
87- } ;
88-
89- window . addEventListener ( "resize" , updateRect ) ;
90- return ( ) => window . removeEventListener ( "resize" , updateRect ) ;
91- } , [ isSelected , setActiveRect ] ) ;
9218
9319 const handleClick = useCallback ( ( ) => {
9420 selectTab ( index ) ;
@@ -99,15 +25,33 @@ function AnimatedTab({ index, children, style, ...props }) {
9925 ref = { tabRef }
10026 role = "tab"
10127 aria-selected = { isSelected }
102- className = { `text-gray-500 hover:cursor-pointer focus:outline-none transition-colors duration-200 ${
103- isSelected ? "text-gray-900" : ""
104- } `}
10528 onClick = { handleClick }
10629 style = { {
107- ...style ,
108- padding : `8px ${ HORIZONTAL_PADDING } px` ,
109- background : "white" ,
30+ display : "flex" ,
31+ alignItems : "center" ,
32+ padding : "6px 12px" ,
33+ fontSize : "13px" ,
34+ fontWeight : isSelected ? "600" : "500" ,
35+ color : isSelected ? "#1f2937" : "#6b7280" ,
36+ backgroundColor : isSelected ? "#ffffff" : "transparent" ,
11037 border : "none" ,
38+ borderRadius : "6px" ,
39+ cursor : "pointer" ,
40+ transition : "all 0.15s ease" ,
41+ boxShadow : isSelected ? "0 1px 2px rgba(0, 0, 0, 0.05)" : "none" ,
42+ ...style ,
43+ } }
44+ onMouseEnter = { ( e ) => {
45+ if ( ! isSelected ) {
46+ e . currentTarget . style . color = "#374151" ;
47+ e . currentTarget . style . backgroundColor = "rgba(255, 255, 255, 0.5)" ;
48+ }
49+ } }
50+ onMouseLeave = { ( e ) => {
51+ if ( ! isSelected ) {
52+ e . currentTarget . style . color = "#6b7280" ;
53+ e . currentTarget . style . backgroundColor = "transparent" ;
54+ }
11155 } }
11256 { ...props }
11357 >
0 commit comments