Import source: src/components/Header
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,145 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { usePathname, useRouter } from '@/i18n/routing';
|
||||||
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
|
import { Sun, Moon, Globe, Menu, X } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const t = useTranslations('Header');
|
||||||
|
const locale = useLocale();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [theme, setTheme] = useState<'light' | 'dark'>('dark');
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check local storage or document class to sync initial state
|
||||||
|
const isDark = document.documentElement.classList.contains('dark');
|
||||||
|
setTheme(isDark ? 'dark' : 'light');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
if (theme === 'dark') {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
localStorage.setItem('theme', 'light');
|
||||||
|
setTheme('light');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
localStorage.setItem('theme', 'dark');
|
||||||
|
setTheme('dark');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleLanguage = () => {
|
||||||
|
const nextLocale = locale === 'en' ? 'fa' : 'en';
|
||||||
|
router.replace(pathname, { locale: nextLocale });
|
||||||
|
};
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ key: 'about', href: '#about' },
|
||||||
|
{ key: 'expertise', href: '#expertise' },
|
||||||
|
{ key: 'projects', href: '#projects' },
|
||||||
|
{ key: 'writing', href: '#writing' },
|
||||||
|
{ key: 'contact', href: '#contact' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="sticky top-0 z-50 w-full border-b border-sand/30 dark:border-charcoal/50 bg-warm-white/80 dark:bg-near-black/80 backdrop-blur-md transition-colors duration-300">
|
||||||
|
<div className="max-w-[1200px] mx-auto px-6 h-16 flex items-center justify-between">
|
||||||
|
{/* Name / Logo */}
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-lg font-bold tracking-tight text-teal-brand hover:opacity-85 transition-opacity"
|
||||||
|
>
|
||||||
|
{locale === 'fa' ? 'آرسام صباغ' : 'Arsam Sabbagh'}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Desktop Nav Links */}
|
||||||
|
<nav className="hidden md:flex items-center space-x-6 rtl:space-x-reverse">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item.key}
|
||||||
|
href={item.href}
|
||||||
|
className="text-sm font-medium text-charcoal/70 dark:text-warm-white/70 hover:text-teal-brand dark:hover:text-teal-brand transition-colors"
|
||||||
|
>
|
||||||
|
{t(`nav.${item.key}`)}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Action Controls */}
|
||||||
|
<div className="hidden md:flex items-center space-x-4 rtl:space-x-reverse">
|
||||||
|
{/* Language Switcher */}
|
||||||
|
<button
|
||||||
|
onClick={toggleLanguage}
|
||||||
|
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full border border-sand dark:border-charcoal text-xs font-semibold hover:border-teal-brand dark:hover:border-teal-brand hover:text-teal-brand transition-all cursor-pointer"
|
||||||
|
aria-label="Toggle language"
|
||||||
|
>
|
||||||
|
<Globe className="w-3.5 h-3.5" />
|
||||||
|
<span>{locale === 'en' ? 'فارسی' : 'English'}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Theme Switcher */}
|
||||||
|
<button
|
||||||
|
onClick={toggleTheme}
|
||||||
|
className="p-2 rounded-full border border-sand dark:border-charcoal hover:border-teal-brand dark:hover:border-teal-brand hover:text-teal-brand transition-all cursor-pointer"
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
>
|
||||||
|
{theme === 'dark' ? (
|
||||||
|
<Sun className="w-4 h-4 text-terracotta" />
|
||||||
|
) : (
|
||||||
|
<Moon className="w-4 h-4 text-teal-brand" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile controls & toggle */}
|
||||||
|
<div className="flex items-center space-x-2 rtl:space-x-reverse md:hidden">
|
||||||
|
<button
|
||||||
|
onClick={toggleTheme}
|
||||||
|
className="p-2 rounded-full border border-sand dark:border-charcoal hover:text-teal-brand cursor-pointer"
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
>
|
||||||
|
{theme === 'dark' ? <Sun className="w-4 h-4 text-terracotta" /> : <Moon className="w-4 h-4 text-teal-brand" />}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={toggleLanguage}
|
||||||
|
className="p-2 rounded-full border border-sand dark:border-charcoal hover:text-teal-brand cursor-pointer"
|
||||||
|
aria-label="Toggle language"
|
||||||
|
>
|
||||||
|
<Globe className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
|
className="p-2 text-charcoal dark:text-warm-white cursor-pointer"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
{mobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu Panel */}
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<div className="md:hidden border-t border-sand/20 dark:border-charcoal/50 bg-warm-white dark:bg-near-black w-full py-4 px-6 animate-in fade-in slide-in-from-top-4 duration-200">
|
||||||
|
<nav className="flex flex-col space-y-4">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item.key}
|
||||||
|
href={item.href}
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
className="text-base font-semibold text-charcoal/80 dark:text-warm-white/80 hover:text-teal-brand dark:hover:text-teal-brand transition-colors"
|
||||||
|
>
|
||||||
|
{t(`nav.${item.key}`)}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user