Skip to content

Commit b40736f

Browse files
committed
feat: enhance theme toggle with animation and overlay effect
1 parent 354dbc6 commit b40736f

1 file changed

Lines changed: 66 additions & 3 deletions

File tree

components/sections/navbar.tsx

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
"use client"
22

3-
import { useState, useEffect } from "react"
4-
import { motion } from "framer-motion"
3+
import { useState, useEffect, useRef } from "react"
4+
import { motion, AnimatePresence } from "framer-motion"
55
import { User, Code, Briefcase, Mail, Folder, Sun, Moon } from "lucide-react"
66
import { useTheme } from "next-themes"
77

88
export default function Navbar() {
99
const [activeSection, setActiveSection] = useState("about")
1010
const [scrolled, setScrolled] = useState(false)
1111
const [isOpen, setIsOpen] = useState(false)
12+
const [isAnimating, setIsAnimating] = useState(false)
13+
const [animationOrigin, setAnimationOrigin] = useState({ x: 0, y: 0 })
14+
const desktopThemeButtonRef = useRef<HTMLButtonElement>(null)
15+
const mobileThemeButtonRef = useRef<HTMLButtonElement>(null)
1216
const { theme, setTheme } = useTheme()
1317

1418
const navItems = [
@@ -62,11 +66,68 @@ export default function Navbar() {
6266
}
6367

6468
const toggleTheme = () => {
65-
setTheme(theme === "dark" ? "light" : "dark")
69+
// Determine which button is currently visible and get its position
70+
const currentButtonRef = window.innerWidth >= 768 ? desktopThemeButtonRef : mobileThemeButtonRef
71+
const buttonElement = currentButtonRef.current
72+
73+
if (buttonElement) {
74+
const rect = buttonElement.getBoundingClientRect()
75+
setAnimationOrigin({
76+
x: rect.left + rect.width / 2,
77+
y: rect.top + rect.height / 2
78+
})
79+
setIsAnimating(true)
80+
81+
// Change theme after animation starts
82+
setTimeout(() => {
83+
setTheme(theme === "dark" ? "light" : "dark")
84+
}, 300)
85+
86+
// Reset animation state
87+
setTimeout(() => {
88+
setIsAnimating(false)
89+
}, 1400)
90+
}
6691
}
6792

6893
return (
6994
<>
95+
{/* Theme Transition Overlay */}
96+
<AnimatePresence>
97+
{isAnimating && (
98+
<motion.div
99+
initial={{
100+
position: "fixed",
101+
top: animationOrigin.y,
102+
left: animationOrigin.x,
103+
width: 0,
104+
height: 0,
105+
borderRadius: "50%",
106+
backgroundColor: theme === "dark" ? "#ffffff" : "#000000",
107+
zIndex: 9999,
108+
opacity: 0.8
109+
}}
110+
animate={{
111+
width: Math.max(window.innerWidth, window.innerHeight) * 2,
112+
height: Math.max(window.innerWidth, window.innerHeight) * 2,
113+
top: animationOrigin.y - Math.max(window.innerWidth, window.innerHeight),
114+
left: animationOrigin.x - Math.max(window.innerWidth, window.innerHeight),
115+
opacity: 0
116+
}}
117+
exit={{
118+
opacity: 0,
119+
scale: 0
120+
}}
121+
transition={{
122+
duration: 1.2,
123+
ease: "easeInOut"
124+
}}
125+
style={{
126+
transformOrigin: "center"
127+
}}
128+
/>
129+
)}
130+
</AnimatePresence>
70131
{/* Desktop Floating Navbar */}
71132
<div className="fixed top-3 left-0 right-0 z-50 hidden md:block">
72133
<div className="flex justify-center">
@@ -91,6 +152,7 @@ export default function Navbar() {
91152
/>
92153
))}
93154
<button
155+
ref={desktopThemeButtonRef}
94156
onClick={toggleTheme}
95157
aria-label="Toggle theme"
96158
className="ml-2 inline-flex items-center justify-center rounded-full border border-border bg-accent hover:bg-accent/80 text-foreground transition-colors h-8 w-8"
@@ -132,6 +194,7 @@ export default function Navbar() {
132194
{/* Theme toggle (mobile) */}
133195
<li>
134196
<button
197+
ref={mobileThemeButtonRef}
135198
onClick={toggleTheme}
136199
aria-label="Toggle theme"
137200
className="w-full py-3 flex flex-col items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"

0 commit comments

Comments
 (0)