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" ;
33import { ThemeContext } from "../context/ThemeContext" ;
44import { 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+
613const 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