Skip to content

Commit 9c8f6af

Browse files
Merge pull request #617 from Luffy-456/enhance-navbar
enhance navbar
2 parents ba35855 + 320ad56 commit 9c8f6af

2 files changed

Lines changed: 213 additions & 259 deletions

File tree

src/components/Navbar.tsx

Lines changed: 104 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,35 @@
1-
import { NavLink, Link, useNavigate, useLocation } from "react-router-dom";
2-
import { useState, useContext } from "react";
1+
import { NavLink, Link } from "react-router-dom";
2+
import { useState, useContext, useRef, useEffect } from "react";
33
import { ThemeContext } from "../context/ThemeContext";
44
import { Moon, Sun, Menu, X } from "lucide-react";
55

6+
const navItems = [
7+
{ to: "/", label: "Home" },
8+
{ to: "/track", label: "Tracker" },
9+
{ to: "/contributors", label: "Contributors" },
10+
{ to: "/login", label: "Login" },
11+
];
12+
613
const Navbar: React.FC = () => {
714
const [isOpen, setIsOpen] = useState(false);
8-
const [user, setUser] = useState<NavbarUser | null>(() => readStoredUser());
9-
15+
const [pillStyle, setPillStyle] = useState<{
16+
left: number;
17+
width: number;
18+
opacity: number;
19+
}>({ left: 0, width: 0, opacity: 0 });
20+
21+
const [scrolled,setScrolled]= useState(false);
22+
useEffect( () => {
23+
const handleScroll = () => {
24+
setScrolled(window.scrollY>20);
25+
};
26+
27+
handleScroll();
28+
window.addEventListener("scroll", handleScroll, { passive: true });
29+
return () => window.removeEventListener("scroll", handleScroll);
30+
},[]);
31+
32+
const navRef = useRef<HTMLDivElement>(null);
1033
const themeContext = useContext(ThemeContext);
1134
const navigate = useNavigate();
1235
const location = useLocation();
@@ -16,55 +39,39 @@ const Navbar: React.FC = () => {
1639
const { toggleTheme, mode } = themeContext;
1740
const { isAuthenticated, isLoading, logout } = authContext;
1841

19-
const navLinkStyles = ({ isActive }: { isActive: boolean }) =>
20-
`px-4 py-2 rounded-xl text-sm lg:text-base font-semibold transition-all duration-300 ${
21-
isActive
22-
? "text-blue-600 bg-blue-100 dark:text-blue-400 dark:bg-blue-900/40 shadow-sm"
23-
: "text-slate-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800"
24-
}`;
25-
26-
const featureLinkStyles =
27-
"px-4 py-2 rounded-xl text-sm lg:text-base font-semibold transition-all duration-300 text-slate-700 dark:text-gray-300 hover:text-blue-500 cursor-pointer";
28-
2942
const closeMenu = () => setIsOpen(false);
30-
const handleLogout = () => {
31-
if (typeof window !== "undefined") {
32-
window.localStorage.removeItem(AUTH_STORAGE_KEY);
33-
}
34-
setUser(null);
35-
closeMenu();
36-
};
3743

38-
const handleLogout = async () => {
39-
try {
40-
await logout();
41-
navigate("/login", { replace: true });
42-
} catch {
43-
// optionally surface a toast/message
44-
} finally {
45-
closeMenu();
46-
}
44+
const handleMouseEnter = (e: React.MouseEvent<HTMLAnchorElement>) => {
45+
const nav = navRef.current;
46+
const item = e.currentTarget;
47+
if (!nav) return;
48+
const navRect = nav.getBoundingClientRect();
49+
const itemRect = item.getBoundingClientRect();
50+
setPillStyle({
51+
left: itemRect.left - navRect.left,
52+
width: itemRect.width,
53+
opacity: 1,
54+
});
4755
};
4856

49-
// Smooth scroll to #features on homepage
50-
const handleFeaturesClick = () => {
51-
closeMenu();
52-
if (location.pathname === "/") {
53-
const section = document.getElementById("features");
54-
if (section) {
55-
section.scrollIntoView({ behavior: "smooth" });
56-
}
57-
} else {
58-
navigate("/#features");
59-
setTimeout(() => {
60-
const section = document.getElementById("features");
61-
if (section) section.scrollIntoView({ behavior: "smooth" });
62-
}, 100);
63-
}
57+
const handleMouseLeave = () => {
58+
setPillStyle((prev) => ({ ...prev, opacity: 0 }));
6459
};
6560

61+
const navLinkClass = ({ isActive }: { isActive: boolean }) =>
62+
`relative z-10 px-4 py-2 rounded-xl text-sm lg:text-base font-semibold transition-colors duration-200 ${
63+
isActive
64+
? "text-blue-600 dark:text-blue-400"
65+
: "text-slate-700 dark:text-gray-300"
66+
}`;
67+
6668
return (
67-
<nav className="sticky top-0 z-50 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 transition-colors duration-300 backdrop-blur">
69+
<nav className={`sticky top-0 z-50 border-b transition-all duration-300 ${
70+
scrolled
71+
? "bg-white/70 dark:bg-gray-900/70 backdrop-blur-2xl border-gray-200 dark:border-gray-800 shadow-sm"
72+
: "bg-white dark:bg-gray-900 border-transparent"
73+
}`}
74+
>
6875
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
6976
<Link
7077
to="/"
@@ -78,34 +85,39 @@ const Navbar: React.FC = () => {
7885
<span>GitHub Tracker</span>
7986
</Link>
8087

81-
<div className="hidden md:flex items-center gap-3">
82-
<NavLink to="/" end className={navLinkStyles}>
83-
Home
84-
</NavLink>
85-
86-
{/* Features: smooth scroll to #features section on homepage */}
87-
<span className={featureLinkStyles} onClick={handleFeaturesClick}>
88-
Features
89-
</span>
90-
91-
<NavLink to="/about" className={navLinkStyles}>
92-
About
93-
</NavLink>
94-
95-
<NavLink to="/track" className={navLinkStyles}>
96-
Tracker
97-
</NavLink>
98-
<NavLink to="/contributors" className={navLinkStyles}>
99-
Contributors
100-
</NavLink>
101-
102-
{user ? (
103-
<ProfileDropdown user={user} onLogout={handleLogout} />
104-
) : (
105-
<NavLink to="/login" className={navLinkStyles}>
106-
Login
88+
{/* Desktop Navigation */}
89+
<div
90+
ref={navRef}
91+
className="hidden md:flex items-center gap-1 relative"
92+
onMouseLeave={handleMouseLeave}
93+
>
94+
{/* Sliding pill */}
95+
<span
96+
className="absolute top-0 h-full rounded-xl bg-gray-100 dark:bg-gray-800 pointer-events-none"
97+
style={{
98+
left: pillStyle.left,
99+
width: pillStyle.width,
100+
opacity: pillStyle.opacity,
101+
transition: "left 0.2s ease, width 0.2s ease, opacity 0.15s ease",
102+
backdropFilter: "blur(20px)",
103+
WebkitBackdropFilter: "blur(20px)",
104+
background:
105+
mode === "dark" ? "rgba(255,255,255,0.08)" : "rgba(255,255,255,0.35)",
106+
boxShadow:
107+
mode === "dark" ? "0 4px 20px rgba(0,0,0,0.25)" : "0 4px 20px rgba(0,0,0,0.08)",
108+
}}
109+
/>
110+
111+
{navItems.map((item) => (
112+
<NavLink
113+
key={item.to}
114+
to={item.to}
115+
onMouseEnter={handleMouseEnter}
116+
className={navLinkClass}
117+
>
118+
{item.label}
107119
</NavLink>
108-
)}
120+
))}
109121

110122
<button
111123
onClick={toggleTheme}
@@ -121,7 +133,6 @@ const Navbar: React.FC = () => {
121133
</div>
122134

123135
<div className="md:hidden flex items-center gap-2">
124-
{/* Theme Toggle */}
125136
<button
126137
onClick={toggleTheme}
127138
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
@@ -130,9 +141,10 @@ const Navbar: React.FC = () => {
130141
{mode === "dark" ? (
131142
<Sun className="h-5 w-5 text-yellow-400" />
132143
) : (
133-
<Moon className="h-5 w-5 text-white" />
144+
<Moon className="h-5 w-5 text-black" />
134145
)}
135146
</button>
147+
136148
<button
137149
onClick={() => setIsOpen(!isOpen)}
138150
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
@@ -148,32 +160,24 @@ const Navbar: React.FC = () => {
148160
</div>
149161

150162
{isOpen && (
151-
<div className="md:hidden border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900">
163+
<div className="md:hidden border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 ani-fade-in">
152164
<div className="px-6 py-5 flex flex-col gap-3">
153-
<NavLink to="/" end className={navLinkStyles} onClick={closeMenu}>
154-
Home
155-
</NavLink>
156-
157-
{/* Features: smooth scroll to #features section on homepage */}
158-
<span className={featureLinkStyles} onClick={handleFeaturesClick}>
159-
Features
160-
</span>
161-
162-
<NavLink to="/about" className={navLinkStyles} onClick={closeMenu}>
163-
About
164-
</NavLink>
165-
166-
<NavLink to="/track" className={navLinkStyles} onClick={closeMenu}>
167-
Tracker
168-
</NavLink>
169-
170-
<NavLink to="/contributors" className={navLinkStyles} onClick={closeMenu}>
171-
Contributors
172-
</NavLink>
173-
174-
<NavLink to="/login" className={navLinkStyles} onClick={closeMenu}>
175-
Login
176-
</NavLink>
165+
{navItems.map((item) => (
166+
<NavLink
167+
key={item.to}
168+
to={item.to}
169+
onClick={closeMenu}
170+
className={({ isActive }) =>
171+
`px-4 py-2 rounded-xl text-sm font-semibold transition-all duration-300 ${
172+
isActive
173+
? "text-blue-600 bg-blue-100 dark:bg-blue-900/40 shadow-sm"
174+
: "text-slate-700 dark:text-gray-300 hover:text-blue-500"
175+
}`
176+
}
177+
>
178+
{item.label}
179+
</NavLink>
180+
))}
177181
</div>
178182
</div>
179183
)}

0 commit comments

Comments
 (0)