rushiljain's picture
Upload 29 files
691cdd0 verified
import React, { useEffect, useState, useRef } from 'react';
import { Link, NavLink } from 'react-router-dom';
import clsx from 'clsx';
import ScrollProgress from './ScrollProgress.jsx';
import ImageFlex from './ImageFlex.jsx';
export default function Header() {
const [scrolled, setScrolled] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const [projectsOpen, setProjectsOpen] = useState(false);
const [headerVisible, setHeaderVisible] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const closeTimerRef = useRef(null);
const headerRef = useRef(null);
useEffect(() => {
const onScroll = () => {
const scrollY = window.scrollY;
setScrolled(scrollY > 24);
// Show header when scrolled
setHeaderVisible(scrollY > 50);
};
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
useEffect(() => {
// Show header on hover or when menu is open, regardless of scroll position
if (isHovered || menuOpen) {
setHeaderVisible(true);
} else {
// Only hide if not scrolled
const scrollY = window.scrollY;
if (scrollY <= 50) {
setHeaderVisible(false);
}
}
}, [isHovered, menuOpen]);
useEffect(() => {
document.body.style.overflow = menuOpen ? 'hidden' : '';
}, [menuOpen]);
const navLink = (to, label) => (
<NavLink
to={to}
className={({ isActive }) =>
clsx(
'relative px-3 py-2 text-sm font-medium transition',
// Animated underline (temporary) from right -> left on hover
'after:content-[""] after:absolute after:left-3 after:right-3 after:-bottom-1 after:h-[2px] after:bg-brand-600',
'after:transform after:scale-x-0 after:origin-right after:transition-transform after:duration-300',
'hover:after:origin-left hover:after:scale-x-100',
isActive ? 'text-brand-700' : 'text-slate-700 hover:text-brand-700'
)
}
>
{label}
</NavLink>
);
return (
<>
{/* Invisible hover area at top of page to reveal header */}
<div
className="fixed inset-x-0 top-0 z-40 h-4"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
aria-hidden="true"
/>
<header
ref={headerRef}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={clsx(
'fixed inset-x-0 top-0 z-50 bg-white transition-all duration-300',
scrolled ? 'shadow' : '',
headerVisible
? 'translate-y-0 opacity-100 pointer-events-auto'
: '-translate-y-full opacity-0 pointer-events-none'
)}
role="banner"
style={{
transform: headerVisible ? 'translateY(0)' : 'translateY(-100%)',
}}
>
<ScrollProgress />
<a href="#main-content" className="skip-link">
Skip to content
</a>
<div className="container flex items-center justify-between py-4">
<Link to="/" className="flex items-center gap-3" aria-label="Jade Infra home">
<ImageFlex
base="/assets/logo"
alt="Jade Infra logo"
className="h-12 md:h-14 w-auto -my-2 md:-my-3"
/>
<span className="sr-only">Jade Infra</span>
</Link>
<nav aria-label="Primary" className="hidden items-center gap-8 md:flex">
{navLink('/', 'Home')}
{navLink('/about', 'About')}
{/* Projects with dropdown (hover with grace period) */}
<div
className="relative"
onMouseEnter={() => {
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
setProjectsOpen(true);
}}
onMouseLeave={() => {
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
closeTimerRef.current = setTimeout(() => setProjectsOpen(false), 250);
}}
>
<NavLink
to="/projects/development"
className={({ isActive }) =>
clsx(
'px-3 py-2 text-sm font-medium transition',
isActive ? 'text-brand-700' : 'text-slate-700 hover:text-brand-700'
)
}
aria-haspopup="true"
aria-expanded={projectsOpen}
onFocus={() => setProjectsOpen(true)}
>
Projects
</NavLink>
<div
className={clsx(
'absolute left-0 top-full w-56 border border-slate-200 bg-white p-2 shadow-card',
'transition-opacity',
projectsOpen ? 'opacity-100 pointer-events-auto mt-2' : 'opacity-0 pointer-events-none mt-2'
)}
role="menu"
aria-label="Projects submenu"
>
<NavLink
to="/projects/development"
className={({ isActive }) =>
clsx(
'relative block rounded px-3 py-2 text-sm transition',
'after:content-[""] after:absolute after:left-3 after:right-3 after:-bottom-0.5 after:h-[2px] after:bg-brand-600',
'after:transform after:scale-x-0 after:origin-right after:transition-transform after:duration-300',
'hover:after:origin-left hover:after:scale-x-100',
isActive ? 'text-brand-700' : 'text-slate-700 hover:text-brand-700'
)
}
role="menuitem"
>
Development
</NavLink>
<NavLink
to="/projects/construction"
className={({ isActive }) =>
clsx(
'relative block rounded px-3 py-2 text-sm transition',
'after:content-[""] after:absolute after:left-3 after:right-3 after:-bottom-0.5 after:h-[2px] after:bg-brand-600',
'after:transform after:scale-x-0 after:origin-right after:transition-transform after:duration-300',
'hover:after:origin-left hover:after:scale-x-100',
isActive ? 'text-brand-700' : 'text-slate-700 hover:text-brand-700'
)
}
role="menuitem"
>
Project Contracting
</NavLink>
<NavLink
to="/projects/sra"
className={({ isActive }) =>
clsx(
'relative block rounded px-3 py-2 text-sm transition',
'after:content-[""] after:absolute after:left-3 after:right-3 after:-bottom-0.5 after:h-[2px] after:bg-brand-600',
'after:transform after:scale-x-0 after:origin-right after:transition-transform after:duration-300',
'hover:after:origin-left hover:after:scale-x-100',
isActive ? 'text-brand-700' : 'text-slate-700 hover:text-brand-700'
)
}
role="menuitem"
>
SRA
</NavLink>
</div>
</div>
{navLink('/projects/redevelopment', 'Redevelopment')}
{navLink('/contact', 'Contact')}
</nav>
<div className="flex items-center gap-3">
<button
type="button"
aria-controls="mobile-menu"
aria-expanded={menuOpen}
aria-label="Toggle menu"
className="inline-flex h-10 w-10 items-center justify-center rounded md:hidden
focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-600"
onClick={() => setMenuOpen((v) => !v)}
>
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">
<path
d={menuOpen ? 'M6 18L18 6M6 6l12 12' : 'M3 6h18M3 12h18M3 18h18'}
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
</button>
</div>
</div>
<div
id="mobile-menu"
className={clsx(
'md:hidden',
menuOpen ? 'block' : 'hidden'
)}
>
<div className="container space-y-1 pb-6">
<NavLink
to="/"
className="block rounded px-4 py-3 text-base hover:bg-slate-50"
onClick={() => setMenuOpen(false)}
>
Home
</NavLink>
<NavLink
to="/about"
className="block rounded px-4 py-3 text-base hover:bg-slate-50"
onClick={() => setMenuOpen(false)}
>
About
</NavLink>
<div>
<NavLink
to="/projects/development"
className="block rounded px-4 py-3 text-base hover:bg-slate-50"
onClick={() => setMenuOpen(false)}
>
Projects
</NavLink>
<div className="ml-4 mt-1 space-y-1">
<NavLink to="/projects/development" className="block rounded px-4 py-2 text-sm hover:bg-slate-50" onClick={() => setMenuOpen(false)}>Development</NavLink>
<NavLink to="/projects/construction" className="block rounded px-4 py-2 text-sm hover:bg-slate-50" onClick={() => setMenuOpen(false)}>Project Contracting</NavLink>
<NavLink to="/projects/sra" className="block rounded px-4 py-2 text-sm hover:bg-slate-50" onClick={() => setMenuOpen(false)}>SRA</NavLink>
</div>
</div>
<NavLink
to="/projects/redevelopment"
className="block rounded px-4 py-3 text-base hover:bg-slate-50"
onClick={() => setMenuOpen(false)}
>
Redevelopment
</NavLink>
<NavLink
to="/contact"
className="block rounded px-4 py-3 text-base hover:bg-slate-50"
onClick={() => setMenuOpen(false)}
>
Contact
</NavLink>
</div>
</div>
</header>
</>
);
}