175 lines
6.6 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|
|
|