@@ -4,59 +4,178 @@ import {
4
4
MotionConfig ,
5
5
Variants ,
6
6
} from "motion/react" ;
7
+ import { useTheme } from "@/providers/theme" ;
7
8
import { useState } from "react" ;
9
+
8
10
const modeVariants : Variants = {
9
11
initial : {
10
- y : - 40 ,
12
+ y : - 20 ,
11
13
opacity : 0 ,
12
14
} ,
13
15
visible : {
14
16
y : 0 ,
15
17
opacity : 1 ,
18
+ transition : {
19
+ type : "spring" ,
20
+ stiffness : 300 ,
21
+ damping : 20 ,
22
+ duration : 0.3 ,
23
+ } ,
16
24
} ,
17
25
hidden : {
18
- y : 40 ,
26
+ y : 20 ,
19
27
opacity : 0 ,
28
+ transition : {
29
+ duration : 0.3 ,
30
+ } ,
31
+ } ,
32
+ } ;
33
+
34
+ const containerVariants : Variants = {
35
+ hover : {
36
+ scale : 1.05 ,
37
+ } ,
38
+ tap : {
39
+ scale : 0.95 ,
20
40
} ,
21
41
} ;
22
42
23
43
const ModeToggle = ( ) => {
24
- const [ mode , setMode ] = useState < "light" | "dark" > ( "light" ) ;
44
+ const { theme, setTheme } = useTheme ( ) ;
45
+ const [ isAnimating , setIsAnimating ] = useState ( false ) ;
46
+
47
+ const handleToggle = ( ) => {
48
+ setIsAnimating ( true ) ;
49
+ setTheme ( theme === "light" ? "dark" : "light" ) ;
50
+ // Reset animation state after animation completes
51
+ setTimeout ( ( ) => setIsAnimating ( false ) , 600 ) ;
52
+ } ;
25
53
26
54
return (
27
- < MotionConfig transition = { { duration : 0.3 , ease : "easeInOut" } } >
55
+ < MotionConfig
56
+ transition = { {
57
+ easings : [ 0.1 , 0.9 , 0.2 , 1 ] ,
58
+ duration : 0.5 ,
59
+ } }
60
+ >
28
61
< div className = "full center" >
29
62
< m . button
30
- animate = { { width : "auto" } }
31
- whileTap = { { scale : 0.95 } }
32
- className = "rounded-full bg-muted flex items-center justify-center gap-1 py-2 px-4 overflow-hidden "
33
- onClick = { ( ) => setMode ( mode === "light" ? "dark" : "light" ) }
63
+ variants = { containerVariants }
64
+ whileHover = "hover"
65
+ whileTap = "tap"
66
+ animate = { {
67
+ width : "auto" ,
68
+ } }
69
+ className = "rounded-full bg-muted flex items-center justify-center py-2 px-4 overflow-hidden outline-none relative"
70
+ onClick = { handleToggle }
34
71
>
35
- < div className = "size-5 rounded bg-muted-foreground" > </ div >
36
- < AnimatePresence mode = "wait" >
37
- { mode === "light" ? (
38
- < m . span
39
- variants = { modeVariants }
40
- initial = "initial"
41
- animate = "visible"
42
- exit = "hidden"
43
- key = "light"
44
- >
45
- Light
46
- </ m . span >
47
- ) : (
48
- < m . span
49
- variants = { modeVariants }
50
- initial = "initial"
51
- animate = "visible"
52
- exit = "hidden"
53
- key = "dark"
54
- >
55
- Dark
56
- </ m . span >
57
- ) }
58
- </ AnimatePresence >
59
- < span > Mode</ span >
72
+ < m . div className = "size-10 rounded-full center relative" layout >
73
+ < m . svg
74
+ xmlns = "http://www.w3.org/2000/svg"
75
+ width = "100"
76
+ height = "100"
77
+ viewBox = "0 0 100 100"
78
+ fill = "none"
79
+ className = "size-9 will-change-transform"
80
+ animate = { {
81
+ rotate : theme === "light" ? 0 : 180 ,
82
+ scale : isAnimating ? [ 1 , 1.1 , 1 ] : 1 ,
83
+ } }
84
+ transition = { {
85
+ duration : 0.5 ,
86
+ } }
87
+ >
88
+ < rect width = "100" height = "100" > </ rect >
89
+ < m . path
90
+ d = "M50 18C58.4869 18 66.6262 21.3714 72.6274 27.3726C78.6286 33.3737 82 41.513 82 50C82 58.4869 78.6286 66.6262 72.6275 72.6274C66.6263 78.6286 58.487 82 50.0001 82L50 50L50 18Z"
91
+ className = "will-change-transform fill-primary"
92
+ animate = { {
93
+ fillOpacity : isAnimating ? [ 1 , 0.7 , 1 ] : 1 ,
94
+ } }
95
+ transition = { {
96
+ duration : 0.3 ,
97
+ times : [ 0 , 0.5 , 1 ] ,
98
+ } }
99
+ > </ m . path >
100
+ < m . circle
101
+ cx = "50"
102
+ cy = "50"
103
+ r = "30"
104
+ className = "will-change-transform stroke-primary"
105
+ strokeWidth = "4"
106
+ animate = { {
107
+ strokeWidth : isAnimating ? [ 4 , 5 , 4 ] : 4 ,
108
+ } }
109
+ transition = { {
110
+ duration : 0.5 ,
111
+ times : [ 0 , 0.5 , 1 ] ,
112
+ } }
113
+ > </ m . circle >
114
+ < m . circle
115
+ cx = "50"
116
+ cy = "50"
117
+ r = "12"
118
+ className = "will-change-transform fill-primary"
119
+ animate = { {
120
+ scale : isAnimating ? [ 1 , 0.9 , 1 ] : 1 ,
121
+ } }
122
+ transition = { {
123
+ duration : 0.5 ,
124
+ times : [ 0 , 0.5 , 1 ] ,
125
+ } }
126
+ > </ m . circle >
127
+ < m . path
128
+ d = "M50 62C53.1826 62 56.2348 60.7357 58.4853 58.4853C60.7357 56.2348 62 53.1826 62 50C62 46.8174 60.7357 43.7652 58.4853 41.5147C56.2348 39.2643 53.1826 38 50 38L50 50L50 62Z"
129
+ className = "will-change-transform fill-primary-foreground"
130
+ animate = { {
131
+ fillOpacity : isAnimating ? [ 1 , 0.7 , 1 ] : 1 ,
132
+ } }
133
+ transition = { {
134
+ duration : 0.5 ,
135
+ times : [ 0 , 0.5 , 1 ] ,
136
+ } }
137
+ > </ m . path >
138
+ </ m . svg >
139
+ </ m . div >
140
+
141
+ < div className = "flex items-center gap-1 font-medium" >
142
+ < AnimatePresence mode = "wait" >
143
+ { theme === "light" ? (
144
+ < m . span
145
+ variants = { modeVariants }
146
+ initial = "initial"
147
+ animate = "visible"
148
+ exit = "hidden"
149
+ key = "light"
150
+ className = "font-medium text-primary"
151
+ >
152
+ Light
153
+ </ m . span >
154
+ ) : (
155
+ < m . span
156
+ variants = { modeVariants }
157
+ initial = "initial"
158
+ animate = "visible"
159
+ exit = "hidden"
160
+ key = "dark"
161
+ className = "font-medium text-primary"
162
+ >
163
+ Dark
164
+ </ m . span >
165
+ ) }
166
+ </ AnimatePresence >
167
+ < m . span
168
+ animate = { {
169
+ opacity : isAnimating ? [ 1 , 0.7 , 1 ] : 1 ,
170
+ } }
171
+ transition = { {
172
+ duration : 0.3 ,
173
+ times : [ 0 , 0.5 , 1 ] ,
174
+ } }
175
+ >
176
+ Mode
177
+ </ m . span >
178
+ </ div >
60
179
</ m . button >
61
180
</ div >
62
181
</ MotionConfig >
0 commit comments