116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
import { useState, useEffect, useRef } from 'react'
|
|
import { Link, useLocation } from 'react-router-dom'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import { HiMenu, HiX } from 'react-icons/hi'
|
|
|
|
const Navbar = () => {
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
const location = useLocation()
|
|
|
|
const navItems = [
|
|
{ name: 'Home', path: '/' },
|
|
{ name: 'Services', path: '/services' },
|
|
{ name: 'About', path: '/about' },
|
|
{ name: 'Resources', path: '/resources' },
|
|
{ name: 'Contact', path: '/contact' }
|
|
]
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false)
|
|
}
|
|
}
|
|
|
|
document.addEventListener('mousedown', handleClickOutside)
|
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
}, [])
|
|
|
|
// Close mobile menu when route changes
|
|
useEffect(() => {
|
|
setIsOpen(false)
|
|
}, [location])
|
|
|
|
return (
|
|
<nav className="bg-primary text-accent-snow relative z-50">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex items-center justify-between h-24">
|
|
<Link
|
|
to="/"
|
|
className="font-black text-2xl sm:text-3xl tracking-wider text-accent-snow drop-shadow-lg"
|
|
aria-label="DeafGain Home"
|
|
>
|
|
DeafGain
|
|
</Link>
|
|
|
|
{/* Desktop Navigation */}
|
|
<div className="hidden md:block">
|
|
<div className="flex items-center space-x-8">
|
|
{navItems.map((item) => (
|
|
<Link
|
|
key={item.name}
|
|
to={item.path}
|
|
className={`text-lg font-bold tracking-wide transition-colors drop-shadow-md
|
|
${location.pathname === item.path
|
|
? 'text-accent-lake'
|
|
: 'text-accent-snow hover:text-accent-lake'}`}
|
|
aria-current={location.pathname === item.path ? 'page' : undefined}
|
|
>
|
|
{item.name}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile menu button */}
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="md:hidden inline-flex items-center justify-center p-2 rounded-md
|
|
text-accent-snow hover:text-accent-lake hover:bg-secondary/20
|
|
transition-colors duration-200"
|
|
aria-expanded={isOpen}
|
|
aria-controls="mobile-menu"
|
|
aria-label={isOpen ? "Close menu" : "Open menu"}
|
|
>
|
|
{isOpen ? <HiX size={28} /> : <HiMenu size={28} />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile Navigation */}
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
ref={menuRef}
|
|
id="mobile-menu"
|
|
initial={{ opacity: 0, y: -20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="md:hidden absolute w-full bg-primary shadow-lg"
|
|
>
|
|
<div className="px-4 pt-2 pb-3 space-y-1">
|
|
{navItems.map((item) => (
|
|
<Link
|
|
key={item.name}
|
|
to={item.path}
|
|
className={`block px-3 py-4 text-base font-medium rounded-md
|
|
transition-colors duration-200
|
|
${location.pathname === item.path
|
|
? 'bg-secondary text-accent-lake'
|
|
: 'text-accent-snow hover:bg-secondary/20 hover:text-accent-lake'}`}
|
|
aria-current={location.pathname === item.path ? 'page' : undefined}
|
|
>
|
|
{item.name}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</nav>
|
|
)
|
|
}
|
|
|
|
export default Navbar
|