Files
e-tib.com/components/layout/Header.tsx
Marc Mintel b13f628b55 feat: complete MDX migration, stabilize environment & verify via E2E tests
Former-commit-id: ec3e64156a2e182535cbdcf0d975cd54603a517d
2026-05-03 18:46:41 +02:00

175 lines
6.6 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';
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 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);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// Close mobile menu on route change
React.useEffect(() => {
setIsMobileMenuOpen(false);
}, [pathname]);
const LanguageSwitcher = ({ mobile = false }) => (
<div className={`flex items-center gap-2 ${mobile ? 'text-xl' : 'text-sm font-bold'} ${mobile ? 'text-neutral-dark' : isScrolled ? 'text-text-primary' : 'text-neutral-light'}`}>
<Link href={getSwitchedUrl('de')} className={`transition-colors hover:text-primary ${currentLocale === 'de' ? 'text-primary' : ''}`}>DE</Link>
<span className="opacity-50">|</span>
<Link href={getSwitchedUrl('en')} className={`transition-colors hover:text-primary ${currentLocale === 'en' ? 'text-primary' : ''}`}>EN</Link>
</div>
);
return (
<>
<header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ease-in-out ${
isScrolled
? '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={(isScrolled || 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 ${
isScrolled
? 'text-text-primary hover:text-primary'
: 'text-neutral-light hover:text-primary-light'
}`}
>
{link.label}
</Link>
);
})}
<div className="h-4 w-px bg-white/20 mx-2" />
<LanguageSwitcher />
<Link
href={`/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`}
className="rounded-xl bg-primary hover:bg-primary-dark text-neutral-light px-8 py-3 font-bold text-sm uppercase tracking-wider transition-all shadow-xl hover:shadow-primary/20 hover:-translate-y-0.5 active:translate-y-0"
>
{currentLocale === 'de' ? 'Kontakt' : 'Contact'}
</Link>
</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={(isScrolled || 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>
</>
);
}