feat: Initialize backend with Express and MySQL, and restructure frontend with new routing and language support.
This commit is contained in:
159
frontend/src/components/Header.tsx
Normal file
159
frontend/src/components/Header.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Menu, X, Sun, Moon, Globe } from 'lucide-react';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { useNavigate, useLocation, Link } from 'react-router-dom';
|
||||
|
||||
interface HeaderProps {
|
||||
darkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
}
|
||||
|
||||
const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const { language, setLanguage, t } = useLanguage();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const menuItems = [
|
||||
{ id: 'home', name: t('nav.home'), href: '#hero' },
|
||||
{ id: 'about', name: t('nav.about'), href: '#about' },
|
||||
{ id: 'skills', name: t('nav.skills'), href: '#skills' },
|
||||
{ id: 'projects', name: t('nav.projects'), href: '#projects' },
|
||||
{ id: 'education', name: t('nav.education'), href: '#education' },
|
||||
{ id: 'contact', name: t('nav.contact'), href: '#contact' }
|
||||
];
|
||||
|
||||
const handleNavigation = (href: string) => {
|
||||
setIsMenuOpen(false);
|
||||
|
||||
if (location.pathname === `/${language}` || location.pathname === `/${language}/`) {
|
||||
// Already on home, just scroll
|
||||
const element = document.querySelector(href);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
} else {
|
||||
// Not on home, navigate then scroll (using a simple timeout for simplicity or hash)
|
||||
navigate(`/${language}`);
|
||||
// Small timeout to allow navigation to render Home before scrolling
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector(href);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleLanguage = () => {
|
||||
setLanguage(language === 'fr' ? 'en' : 'fr');
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="header"
|
||||
>
|
||||
<nav className="nav">
|
||||
<motion.div
|
||||
className="nav-brand"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Link to={`/${language}`} onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNavigation('#hero');
|
||||
}}>
|
||||
Dayron Van Leemput
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
{/* Navigation desktop */}
|
||||
<ul className="nav-menu desktop-menu">
|
||||
{menuItems.map((item, index) => (
|
||||
<motion.li
|
||||
key={item.id}
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<a
|
||||
href={item.href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNavigation(item.href);
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="nav-controls">
|
||||
{/* Toggle langue */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={toggleLanguage}
|
||||
className="language-toggle"
|
||||
aria-label={t('btn.changeLang')}
|
||||
title={t('btn.changeLang')}
|
||||
>
|
||||
<Globe size={18} />
|
||||
<span className="language-text">{language === 'fr' ? 'EN' : 'FR'}</span>
|
||||
</motion.button>
|
||||
|
||||
{/* Toggle thème */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={toggleDarkMode}
|
||||
className="theme-toggle"
|
||||
aria-label={t('btn.changeTheme')}
|
||||
>
|
||||
{darkMode ? <Sun size={20} /> : <Moon size={20} />}
|
||||
</motion.button>
|
||||
|
||||
{/* Menu hamburger mobile */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
className="menu-toggle"
|
||||
aria-label={t('btn.menu')}
|
||||
>
|
||||
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
{/* Navigation mobile */}
|
||||
<motion.ul
|
||||
className={`nav-menu mobile-menu ${isMenuOpen ? 'open' : ''}`}
|
||||
initial={false}
|
||||
animate={isMenuOpen ? { opacity: 1, x: 0 } : { opacity: 0, x: '100%' }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
{menuItems.map((item) => (
|
||||
<li key={item.id}>
|
||||
<a
|
||||
href={item.href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNavigation(item.href);
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</motion.ul>
|
||||
</nav>
|
||||
</motion.header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
Reference in New Issue
Block a user