141 lines
4.5 KiB
TypeScript
141 lines
4.5 KiB
TypeScript
import { useState, useEffect, useRef } from 'react';
|
|
import gsap from 'gsap';
|
|
|
|
const NAV_LINKS = [
|
|
{ href: '/', label: 'All Work' },
|
|
{ href: '/music-videos', label: 'Music Videos' },
|
|
{ href: '/commercials', label: 'Commercials' },
|
|
{ href: '/about', label: 'About' },
|
|
{ href: '/contact', label: 'Contact' },
|
|
];
|
|
|
|
interface Props {
|
|
currentPath?: string;
|
|
}
|
|
|
|
export default function Navigation({ currentPath = '/' }: Props) {
|
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
const linksRef = useRef<HTMLAnchorElement[]>([]);
|
|
const overlayRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const onScroll = () => setIsScrolled(window.scrollY > 40);
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
return () => window.removeEventListener('scroll', onScroll);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape' && menuOpen) closeMenu();
|
|
};
|
|
window.addEventListener('keydown', onKey);
|
|
return () => window.removeEventListener('keydown', onKey);
|
|
}, [menuOpen]);
|
|
|
|
function openMenu() {
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
setMenuOpen(true);
|
|
document.body.style.overflow = 'hidden';
|
|
return;
|
|
}
|
|
|
|
setMenuOpen(true);
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
const ctx = gsap.context(() => {
|
|
gsap.fromTo(overlayRef.current,
|
|
{ opacity: 0 },
|
|
{ opacity: 1, duration: 0.3, ease: 'power2.out' }
|
|
);
|
|
gsap.fromTo(linksRef.current,
|
|
{ y: 30, opacity: 0 },
|
|
{ y: 0, opacity: 1, duration: 0.5, ease: 'power2.out', stagger: 0.07, delay: 0.1 }
|
|
);
|
|
});
|
|
|
|
return () => ctx.revert();
|
|
}
|
|
|
|
function closeMenu() {
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
setMenuOpen(false);
|
|
document.body.style.overflow = '';
|
|
return;
|
|
}
|
|
|
|
gsap.to(overlayRef.current, {
|
|
opacity: 0,
|
|
duration: 0.25,
|
|
ease: 'power2.in',
|
|
onComplete: () => {
|
|
setMenuOpen(false);
|
|
document.body.style.overflow = '';
|
|
},
|
|
});
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<header
|
|
className={`fixed top-0 left-0 right-0 z-50 flex items-center justify-between px-6 md:px-10 transition-all duration-300 ${
|
|
isScrolled ? 'bg-black/80 backdrop-blur-md py-4' : 'py-6'
|
|
}`}
|
|
style={{ height: 'var(--nav-height)' }}
|
|
>
|
|
<a href="/" className="text-white font-display text-xl font-semibold tracking-wide hover:opacity-70 transition-opacity">
|
|
OSKV
|
|
</a>
|
|
|
|
<nav className="hidden md:flex items-center gap-8">
|
|
{NAV_LINKS.map((link) => (
|
|
<a
|
|
key={link.href}
|
|
href={link.href}
|
|
className={`text-sm font-light tracking-widest uppercase transition-opacity duration-200 ${
|
|
currentPath === link.href
|
|
? 'text-white opacity-100'
|
|
: 'text-white/60 hover:text-white hover:opacity-100'
|
|
}`}
|
|
>
|
|
{link.label}
|
|
</a>
|
|
))}
|
|
</nav>
|
|
|
|
<button
|
|
onClick={menuOpen ? closeMenu : openMenu}
|
|
className="md:hidden flex flex-col gap-1.5 p-2 -mr-2 group"
|
|
aria-label={menuOpen ? 'Close menu' : 'Open menu'}
|
|
>
|
|
<span className={`block w-6 h-px bg-white transition-all duration-300 ${menuOpen ? 'rotate-45 translate-y-[7px]' : ''}`} />
|
|
<span className={`block w-6 h-px bg-white transition-all duration-300 ${menuOpen ? 'opacity-0' : ''}`} />
|
|
<span className={`block w-6 h-px bg-white transition-all duration-300 ${menuOpen ? '-rotate-45 -translate-y-[7px]' : ''}`} />
|
|
</button>
|
|
</header>
|
|
|
|
{/* Mobile fullscreen menu */}
|
|
<div
|
|
ref={overlayRef}
|
|
className={`fixed inset-0 z-40 bg-black flex flex-col justify-center px-8 md:hidden ${menuOpen ? 'flex' : 'hidden'}`}
|
|
>
|
|
<nav className="flex flex-col gap-6">
|
|
{NAV_LINKS.map((link, i) => (
|
|
<a
|
|
key={link.href}
|
|
href={link.href}
|
|
ref={(el) => { if (el) linksRef.current[i] = el; }}
|
|
onClick={closeMenu}
|
|
className={`font-display text-4xl font-medium tracking-tight transition-opacity ${
|
|
currentPath === link.href ? 'text-white' : 'text-white/50 hover:text-white'
|
|
}`}
|
|
>
|
|
{link.label}
|
|
</a>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|