1
+ "use client" ;
2
+
3
+ import { useState , useEffect } from "react" ;
4
+ import { motion } from "motion/react" ;
5
+ import { cn } from "../../lib/utils" ;
6
+
7
+ const Tabs = ( {
8
+ tabs : propTabs ,
9
+ containerClassName,
10
+ activeTabClassName,
11
+ tabClassName,
12
+ contentClassName,
13
+ theme = 'light' ,
14
+ } ) => {
15
+ const currentTheme = theme ;
16
+ const [ active , setActive ] = useState ( propTabs [ 0 ] ) ;
17
+ const [ tabs , setTabs ] = useState ( propTabs ) ;
18
+
19
+ // Force re-render when theme changes
20
+ useEffect ( ( ) => {
21
+ setTabs ( [ ...propTabs ] ) ;
22
+ } , [ theme , propTabs ] ) ;
23
+
24
+ const moveSelectedTabToTop = ( idx ) => {
25
+ const newTabs = [ ...propTabs ] ;
26
+ const selectedTab = newTabs . splice ( idx , 1 ) ;
27
+ newTabs . unshift ( selectedTab [ 0 ] ) ;
28
+ setTabs ( newTabs ) ;
29
+ setActive ( newTabs [ 0 ] ) ;
30
+ } ;
31
+
32
+ const [ hovering , setHovering ] = useState ( false ) ;
33
+
34
+ return (
35
+ < >
36
+ < div
37
+ className = { cn (
38
+ "flex flex-row items-center justify-start [perspective:1000px] relative overflow-auto sm:overflow-visible no-visible-scrollbar max-w-full w-full" ,
39
+ containerClassName
40
+ ) }
41
+ >
42
+ { propTabs . map ( ( tab , idx ) => (
43
+ < button
44
+ key = { tab . title }
45
+ onClick = { ( ) => {
46
+ moveSelectedTabToTop ( idx ) ;
47
+ } }
48
+ onMouseEnter = { ( ) => setHovering ( true ) }
49
+ onMouseLeave = { ( ) => setHovering ( false ) }
50
+ className = { cn ( "relative px-4 py-2 rounded-full" , tabClassName ) }
51
+ style = { {
52
+ transformStyle : "preserve-3d" ,
53
+ } }
54
+ >
55
+ { active . value === tab . value && (
56
+ < motion . div
57
+ layoutId = "clickedbutton"
58
+ transition = { { type : "spring" , bounce : 0.3 , duration : 0.6 } }
59
+ className = { cn (
60
+ "absolute inset-0 rounded-full" ,
61
+ activeTabClassName
62
+ ) }
63
+ />
64
+ ) }
65
+
66
+ < span className = { `relative block ${ currentTheme === 'dark' ? 'text-white' : 'text-black' } ` } >
67
+ { tab . title }
68
+ </ span >
69
+ </ button >
70
+ ) ) }
71
+ </ div >
72
+ < FadeInDiv
73
+ tabs = { tabs }
74
+ active = { active }
75
+ key = { active . value }
76
+ hovering = { hovering }
77
+ className = { cn ( "mt-8" , contentClassName ) }
78
+ />
79
+ </ >
80
+ ) ;
81
+ } ;
82
+
83
+ const FadeInDiv = ( {
84
+ className,
85
+ tabs,
86
+ hovering,
87
+ } ) => {
88
+ const isActive = ( tab ) => {
89
+ return tab . value === tabs [ 0 ] . value ;
90
+ } ;
91
+ return (
92
+ < div className = "relative w-full h-full" >
93
+ { tabs . map ( ( tab , idx ) => (
94
+ < motion . div
95
+ key = { tab . value }
96
+ layoutId = { tab . value }
97
+ style = { {
98
+ scale : 1 - idx * 0.1 ,
99
+ top : hovering ? idx * - 50 : 0 ,
100
+ zIndex : - idx ,
101
+ opacity : idx < 3 ? 1 - idx * 0.1 : 0 ,
102
+ } }
103
+ animate = { {
104
+ y : isActive ( tab ) ? [ 0 , 40 , 0 ] : 0 ,
105
+ } }
106
+ className = { cn ( "w-full h-full absolute top-0 left-0" , className ) }
107
+ >
108
+ { tab . content }
109
+ </ motion . div >
110
+ ) ) }
111
+ </ div >
112
+ ) ;
113
+ } ;
114
+
115
+ export default Tabs ;
0 commit comments