171 lines
5.7 KiB
TypeScript
171 lines
5.7 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
import ThemeSelector from '../molecules/ThemeSelector';
|
|
|
|
// Icons
|
|
const MenuIcon = ({ className = '' }: { className?: string }) => (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
strokeWidth={1.5}
|
|
stroke="currentColor"
|
|
className={`w-6 h-6 ${className}`}
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
|
</svg>
|
|
);
|
|
|
|
const CloseIcon = ({ className = '' }: { className?: string }) => (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
strokeWidth={1.5}
|
|
stroke="currentColor"
|
|
className={`w-6 h-6 ${className}`}
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
);
|
|
|
|
interface NavLinkProps {
|
|
href: string;
|
|
children: React.ReactNode;
|
|
isMobile?: boolean;
|
|
onClick?: () => void;
|
|
}
|
|
|
|
const NavLink = ({ href, children, isMobile = false, onClick }: NavLinkProps) => {
|
|
const pathname = usePathname();
|
|
const isActive = pathname === href;
|
|
|
|
const baseStyles = "transition-colors duration-200 px-3 py-2 rounded-md text-lg font-medium";
|
|
const mobileStyles = isMobile ? "block w-full text-left" : "";
|
|
// Create a visually distinct style for active links using the exact primary blue color
|
|
const activeStyles = isActive
|
|
? "text-white font-bold border-b-4 border-accent"
|
|
: "text-gray-700 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700";
|
|
|
|
// Apply background color using inline style for active links to ensure exact color match
|
|
const linkStyle = isActive ? { backgroundColor: 'var(--color-primary)' } : {};
|
|
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className={`${baseStyles} ${mobileStyles} ${activeStyles}`}
|
|
style={linkStyle}
|
|
aria-current={isActive ? "page" : undefined}
|
|
onClick={onClick}
|
|
>
|
|
{children}
|
|
</Link>
|
|
);
|
|
};
|
|
|
|
const Navigation = () => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [scrolled, setScrolled] = useState(false);
|
|
|
|
const toggleMenu = () => setIsOpen(!isOpen);
|
|
const closeMenu = () => setIsOpen(false);
|
|
|
|
// Handle scroll effect
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
const offset = window.scrollY;
|
|
if (offset > 50) {
|
|
setScrolled(true);
|
|
} else {
|
|
setScrolled(false);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
// Close menu on ESC key
|
|
useEffect(() => {
|
|
const handleEsc = (event: KeyboardEvent) => {
|
|
if (event.key === 'Escape') {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('keydown', handleEsc);
|
|
return () => window.removeEventListener('keydown', handleEsc);
|
|
}, []);
|
|
|
|
return (
|
|
<nav
|
|
className={`
|
|
fixed top-0 z-40 w-full
|
|
transition-all duration-300 ease-in-out
|
|
${scrolled ? 'bg-white dark:bg-gray-900 shadow-md' : 'bg-transparent dark:bg-gray-900'}
|
|
`}
|
|
aria-label="Main Navigation"
|
|
>
|
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex justify-between h-16 items-center">
|
|
{/* Logo and site name */}
|
|
<div className="flex-shrink-0">
|
|
<Link href="/" className="flex items-center" onClick={closeMenu}>
|
|
<span className="text-xl font-bold text-primary dark:text-white">Olathe Club of the Deaf</span>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Desktop navigation */}
|
|
<div className="hidden md:block">
|
|
<div className="ml-10 flex items-center space-x-4">
|
|
<NavLink href="/">Home</NavLink>
|
|
<NavLink href="/about">About</NavLink>
|
|
<NavLink href="/events">Events</NavLink>
|
|
<NavLink href="/membership">Membership</NavLink>
|
|
<NavLink href="/contact">Contact</NavLink>
|
|
<NavLink href="/donate">Donate</NavLink>
|
|
<ThemeSelector />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile menu button */}
|
|
<div className="md:hidden">
|
|
<button
|
|
type="button"
|
|
className="inline-flex items-center justify-center p-2 rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary"
|
|
aria-controls="mobile-menu"
|
|
aria-expanded={isOpen}
|
|
onClick={toggleMenu}
|
|
>
|
|
<span className="sr-only">{isOpen ? 'Close main menu' : 'Open main menu'}</span>
|
|
{isOpen ? <CloseIcon /> : <MenuIcon />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile menu, show/hide based on menu state */}
|
|
<div
|
|
className={`md:hidden ${isOpen ? 'block' : 'hidden'}`}
|
|
id="mobile-menu"
|
|
>
|
|
<div className="px-2 pt-2 pb-3 space-y-1 bg-white dark:bg-gray-900 shadow-lg">
|
|
<NavLink href="/" isMobile onClick={closeMenu}>Home</NavLink>
|
|
<NavLink href="/about" isMobile onClick={closeMenu}>About</NavLink>
|
|
<NavLink href="/events" isMobile onClick={closeMenu}>Events</NavLink>
|
|
<NavLink href="/membership" isMobile onClick={closeMenu}>Membership</NavLink>
|
|
<NavLink href="/contact" isMobile onClick={closeMenu}>Contact</NavLink>
|
|
<NavLink href="/donate" isMobile onClick={closeMenu}>Donate</NavLink>
|
|
<div className="px-3 py-2">
|
|
<ThemeSelector />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
);
|
|
};
|
|
|
|
export default Navigation;
|