ocd-website/frontend/src/components/organisms/Navigation.tsx

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;