202 lines
7.3 KiB
TypeScript
202 lines
7.3 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Link, useLocation } from 'react-router-dom';
|
|
import { Menu, X } from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { navVariants, staggerContainer, staggerItem, buttonHover, buttonTap } from '@/utils/animations';
|
|
|
|
const Navigation = () => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
const location = useLocation();
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
setIsScrolled(window.scrollY > 50);
|
|
};
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
const navItems = [
|
|
{ name: 'Home', path: '/' },
|
|
{ name: 'Services', path: '/services' },
|
|
{ name: 'About', path: '/about' },
|
|
{ name: 'Blog', path: '/blog' },
|
|
{ name: 'Contact', path: '/contact' },
|
|
];
|
|
|
|
const isActive = (path: string) => location.pathname === path;
|
|
|
|
return (
|
|
<>
|
|
{/* Skip link for accessibility */}
|
|
<a href="#main-content" className="skip-link">
|
|
Skip to main content
|
|
</a>
|
|
|
|
<motion.nav
|
|
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled
|
|
? 'bg-white/5 backdrop-blur-2xl border-b border-white/20 shadow-2xl shadow-black/20'
|
|
: 'bg-transparent'
|
|
}`}
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={navVariants}
|
|
role="navigation"
|
|
aria-label="Main navigation"
|
|
>
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex items-center justify-between h-14 md:h-16">
|
|
{/* Logo with animation */}
|
|
<Link to="/" className="flex items-center space-x-3">
|
|
<motion.div
|
|
className="w-12 h-12 flex items-center justify-center overflow-visible"
|
|
whileHover={{ rotate: 360 }}
|
|
transition={{ duration: 0.6, ease: "easeInOut" }}
|
|
>
|
|
<img
|
|
src="/logo_bayarea.svg"
|
|
alt="Bay Area Affiliates Logo"
|
|
className="w-10 h-10 opacity-90"
|
|
/>
|
|
</motion.div>
|
|
<span className="font-heading font-bold text-lg text-white">
|
|
Bay Area Affiliates
|
|
</span>
|
|
</Link>
|
|
|
|
{/* Desktop Navigation with animations */}
|
|
<div className="hidden md:flex items-center space-x-8">
|
|
{navItems.map((item, index) => (
|
|
<motion.div
|
|
key={item.name}
|
|
initial={{ opacity: 0, y: -20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: index * 0.1, duration: 0.5 }}
|
|
>
|
|
<Link
|
|
to={item.path}
|
|
className={`font-medium transition-all duration-200 relative group px-2 py-1 ${isActive(item.path)
|
|
? 'text-neon'
|
|
: 'text-white hover:text-neon'
|
|
}`}
|
|
>
|
|
{item.name}
|
|
{/* Animated underline */}
|
|
<motion.span
|
|
className="absolute -bottom-1 left-0 h-0.5 bg-neon rounded-full"
|
|
initial={{ width: isActive(item.path) ? '100%' : 0 }}
|
|
whileHover={{ width: '100%', boxShadow: '0 0 8px rgba(51, 102, 255, 0.6)' }}
|
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
|
/>
|
|
{/* Glow effect on hover */}
|
|
<motion.span
|
|
className="absolute inset-0 bg-neon/5 rounded-lg -z-10"
|
|
initial={{ opacity: 0, scale: 0.8 }}
|
|
whileHover={{ opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.2 }}
|
|
/>
|
|
</Link>
|
|
</motion.div>
|
|
))}
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.8 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ delay: 0.5, duration: 0.5 }}
|
|
whileHover={buttonHover}
|
|
whileTap={buttonTap}
|
|
>
|
|
<Link
|
|
to="/contact"
|
|
className="btn-neon ml-4"
|
|
>
|
|
Get Started
|
|
</Link>
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* Mobile menu button with animation */}
|
|
<motion.button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="md:hidden text-white hover:text-neon transition-colors"
|
|
aria-label="Toggle navigation menu"
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
>
|
|
<AnimatePresence mode="wait">
|
|
{isOpen ? (
|
|
<motion.div
|
|
key="close"
|
|
initial={{ rotate: -90, opacity: 0 }}
|
|
animate={{ rotate: 0, opacity: 1 }}
|
|
exit={{ rotate: 90, opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<X size={24} />
|
|
</motion.div>
|
|
) : (
|
|
<motion.div
|
|
key="menu"
|
|
initial={{ rotate: 90, opacity: 0 }}
|
|
animate={{ rotate: 0, opacity: 1 }}
|
|
exit={{ rotate: -90, opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<Menu size={24} />
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</motion.button>
|
|
</div>
|
|
|
|
{/* Mobile Navigation with smooth animations */}
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
initial={{ height: 0, opacity: 0 }}
|
|
animate={{ height: 'auto', opacity: 1 }}
|
|
exit={{ height: 0, opacity: 0 }}
|
|
transition={{ duration: 0.3, ease: "easeInOut" }}
|
|
className="md:hidden bg-white/5 backdrop-blur-2xl border-t border-white/20 overflow-hidden"
|
|
>
|
|
<motion.div
|
|
className="px-2 pt-2 pb-3 space-y-1"
|
|
variants={staggerContainer}
|
|
initial="hidden"
|
|
animate="visible"
|
|
>
|
|
{navItems.map((item) => (
|
|
<motion.div key={item.name} variants={staggerItem}>
|
|
<Link
|
|
to={item.path}
|
|
onClick={() => setIsOpen(false)}
|
|
className={`block px-3 py-2 rounded-md text-base font-medium transition-colors ${isActive(item.path)
|
|
? 'text-neon bg-neon/10'
|
|
: 'text-white hover:text-neon hover:bg-neon/5'
|
|
}`}
|
|
>
|
|
{item.name}
|
|
</Link>
|
|
</motion.div>
|
|
))}
|
|
<motion.div variants={staggerItem}>
|
|
<Link
|
|
to="/contact"
|
|
onClick={() => setIsOpen(false)}
|
|
className="block w-full text-center btn-neon mt-4"
|
|
>
|
|
Get Started
|
|
</Link>
|
|
</motion.div>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</motion.nav>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Navigation; |