191 lines
6.7 KiB
TypeScript
191 lines
6.7 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import Link from 'next/link';
|
|
import Image from 'next/image';
|
|
import { usePathname } from 'next/navigation';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { LanguageSwitcher } from './LanguageSwitcher';
|
|
import { Button } from '@/components/ui/Button';
|
|
|
|
export interface NavLink {
|
|
label: string;
|
|
url: string;
|
|
}
|
|
|
|
interface HeaderProps {
|
|
navLinks: NavLink[];
|
|
}
|
|
|
|
export function Header({ navLinks }: HeaderProps) {
|
|
const [isScrolled, setIsScrolled] = React.useState(false);
|
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
|
|
const [forceSolid, setForceSolid] = React.useState(false);
|
|
const pathname = usePathname() || '/';
|
|
|
|
// Determine current locale from pathname
|
|
const currentLocale = pathname.startsWith('/en') ? 'en' : 'de';
|
|
|
|
// Helper to switch languages
|
|
const getSwitchedUrl = (newLocale: string) => {
|
|
if (!pathname) return `/${newLocale}`;
|
|
const pathWithoutLocale = pathname.replace(/^\/(en|de)/, '');
|
|
return `/${newLocale}${pathWithoutLocale === '' ? '' : pathWithoutLocale}`;
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
const handleScroll = () => {
|
|
setIsScrolled(window.scrollY > 20);
|
|
};
|
|
window.addEventListener('scroll', handleScroll);
|
|
|
|
// Check for forced solid header (used by error pages, legal pages without hero, etc.)
|
|
const checkSolid = () => {
|
|
setForceSolid(document.body.getAttribute('data-force-solid-header') === 'true');
|
|
};
|
|
checkSolid();
|
|
|
|
// Set up observer for client-side navigation changes
|
|
const observer = new MutationObserver(checkSolid);
|
|
observer.observe(document.body, { attributes: true, attributeFilter: ['data-force-solid-header'] });
|
|
|
|
// Re-check when pathname changes
|
|
checkSolid();
|
|
|
|
return () => {
|
|
window.removeEventListener('scroll', handleScroll);
|
|
observer.disconnect();
|
|
};
|
|
}, [pathname]);
|
|
|
|
// Close mobile menu on route change
|
|
React.useEffect(() => {
|
|
setIsMobileMenuOpen(false);
|
|
}, [pathname]);
|
|
|
|
const isSolidMode = isScrolled || forceSolid;
|
|
|
|
|
|
return (
|
|
<>
|
|
<header
|
|
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ease-in-out ${
|
|
isSolidMode
|
|
? 'bg-neutral-light/90 backdrop-blur-md shadow-sm py-4'
|
|
: 'bg-transparent py-6'
|
|
}`}
|
|
>
|
|
<div className="container flex items-center justify-between">
|
|
<Link href={`/${currentLocale}`} className="relative flex items-center h-12 w-48 z-50">
|
|
<Image
|
|
src={(isSolidMode || isMobileMenuOpen) ? "/assets/logo.png" : "/assets/logo-white.png"}
|
|
alt="E-TIB Gruppe"
|
|
fill
|
|
className="object-contain object-left"
|
|
priority
|
|
sizes="192px"
|
|
/>
|
|
</Link>
|
|
|
|
<nav className="hidden md:flex items-center gap-10">
|
|
{navLinks && navLinks.length > 0 && navLinks.map((link) => {
|
|
const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\/(en|de)/)
|
|
? `/${currentLocale}${link.url}`
|
|
: link.url;
|
|
|
|
return (
|
|
<Link
|
|
key={link.url}
|
|
href={mappedUrl}
|
|
className={`font-semibold text-sm uppercase tracking-widest transition-all duration-300 ${
|
|
isSolidMode
|
|
? 'text-text-primary hover:text-primary'
|
|
: 'text-neutral-light hover:text-primary-light'
|
|
}`}
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
<div className={`h-4 w-px mx-2 transition-colors duration-300 ${isSolidMode ? 'bg-neutral-300' : 'bg-white/20'}`} />
|
|
<LanguageSwitcher isSolidMode={isSolidMode} />
|
|
<Button
|
|
href={`/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`}
|
|
variant="primary"
|
|
className="px-8"
|
|
>
|
|
{currentLocale === 'de' ? 'Kontakt' : 'Contact'}
|
|
</Button>
|
|
</nav>
|
|
|
|
{/* Mobile Nav Button */}
|
|
<button
|
|
className="md:hidden z-50 text-primary p-2"
|
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
aria-label="Toggle menu"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={(isSolidMode || isMobileMenuOpen) ? "currentColor" : "white"} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
{isMobileMenuOpen ? (
|
|
<>
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</>
|
|
) : (
|
|
<>
|
|
<line x1="3" x2="21" y1="6" y2="6"/>
|
|
<line x1="3" x2="21" y1="12" y2="12"/>
|
|
<line x1="3" x2="21" y1="18" y2="18"/>
|
|
</>
|
|
)}
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Mobile Menu Overlay */}
|
|
<AnimatePresence>
|
|
{isMobileMenuOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="fixed inset-0 z-40 bg-white md:hidden pt-32 pb-8 px-6 overflow-y-auto"
|
|
>
|
|
<nav className="flex flex-col gap-6">
|
|
{navLinks && navLinks.length > 0 && navLinks.map((link) => {
|
|
const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\/(en|de)/)
|
|
? `/${currentLocale}${link.url}`
|
|
: link.url;
|
|
|
|
return (
|
|
<Link
|
|
key={link.url}
|
|
href={mappedUrl}
|
|
className="text-2xl font-heading font-bold text-neutral-dark border-b border-neutral-100 pb-4"
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
|
|
<Link
|
|
href={`/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`}
|
|
className="text-2xl font-heading font-bold text-primary border-b border-neutral-100 pb-4"
|
|
>
|
|
{currentLocale === 'de' ? 'Kontakt' : 'Contact'}
|
|
</Link>
|
|
|
|
<div className="mt-8 pt-6">
|
|
<p className="text-text-secondary text-sm font-bold uppercase tracking-widest mb-4">Sprache wählen</p>
|
|
<LanguageSwitcher mobile={true} />
|
|
</div>
|
|
</nav>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
}
|
|
|