Files
e-tib.com/components/layout/Header.tsx
2026-05-07 11:40:47 +02:00

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>
</>
);
}