1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118import React, { useEffect, useState, useRef } from 'react'; interface SectionLink { label: string; href: string; } const SECTION_LINKS: SectionLink[] = [ { label: 'About', href: '#about-section' }, { label: 'Projects', href: '#projects-section' }, { label: 'Micro-Grants', href: '#micro-grants-section' }, { label: 'Partners', href: '#partners-section' }, { label: 'Team', href: '#team-section' }, { label: 'Get Updates', href: '#newsletter-section' }, ]; export const StickyNav: React.FC = () => { const [isSticky, setIsSticky] = useState(false); const [activeSection, setActiveSection] = useState<string>(''); const navRef = useRef<HTMLElement>(null); useEffect(() => { const handleScroll = () => { if (navRef.current) { const rect = navRef.current.getBoundingClientRect(); setIsSticky(rect.top <= 0); } }; window.addEventListener('scroll', handleScroll); handleScroll(); return () => window.removeEventListener('scroll', handleScroll); }, []); useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setActiveSection(entry.target.id); } }); }, { // Trigger when the section is roughly in the middle-top of the viewport rootMargin: '-100px 0px -50% 0px', threshold: 0.1 } ); SECTION_LINKS.forEach((link) => { const id = link.href.replace('#', ''); const element = document.getElementById(id); if (element) observer.observe(element); }); return () => observer.disconnect(); }, []); const handleScrollTo = (e: React.MouseEvent<HTMLAnchorElement>, href: string) => { e.preventDefault(); const id = href.replace('#', ''); const element = document.getElementById(id); if (element) { const offset = 80; const elementPosition = element.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - offset; window.scrollTo({ top: offsetPosition, behavior: "smooth" }); // Update active section immediately for better UX setActiveSection(id); element.focus(); // Shift focus to the section for keyboard users } }; return ( <nav ref={navRef} className={`sticky top-0 z-40 transition-all duration-300 w-full bg-white/90 backdrop-blur-md supports-[backdrop-filter]:bg-white/60 border-b border-gray-200 ${isSticky ? 'shadow-lg' : '' }`} aria-label="Section navigation" > <div className="container mx-auto px-4"> <div className="flex items-center justify-center h-16"> <div className="flex items-center space-x-1 md:space-x-8 overflow-x-auto"> {SECTION_LINKS.map((link) => { const isActive = activeSection === link.href.replace('#', ''); return ( <a key={link.label} href={link.href} onClick={(e) => handleScrollTo(e, link.href)} className={`text-sm md:text-base font-medium whitespace-nowrap transition-all px-3 py-2 rounded-md ${link.label === 'Get Updates' ? 'ml-2 bg-uic-red text-white hover:bg-red-700 shadow-sm hover:shadow' : isActive ? 'text-uic-red bg-red-50' : 'text-gray-700 hover:text-uic-red hover:bg-gray-50/50' }`} aria-current={isActive ? 'page' : undefined} > {link.label} </a> ); })} </div> </div> </div> </nav> ); };