Files
portifolio-evandrodevbr/app/components/Header.tsx
T
Evandro Fonseca Junior 7c9cada65f feat(about, contact, header, hero, skills): enhance component structure and navigation
- Added section IDs to About, Contact, and Hero components for improved navigation and accessibility.
- Refactored Header component to utilize a structured navigation array, enhancing maintainability and responsiveness.
- Updated Skills component to improve type handling and visual consistency, including a loading state and error handling.
- Adjusted styling and animations across components for a smoother user experience and better visual appeal.
2026-01-12 16:12:33 -03:00

163 lines
6.1 KiB
TypeScript

"use client";
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { MenuIcon, XIcon, Layers } from "lucide-react";
import { cn } from "@/lib/utils";
// Definição de Tipos para consistência de dados
interface NavItem {
href: string;
label: string;
}
const navItems: NavItem[] = [
{ href: "#home", label: "Home" },
{ href: "#about", label: "Sobre" },
{ href: "#skills", label: "Habilidades" },
{ href: "#experience", label: "Experiência" },
{ href: "#projects", label: "Projetos" },
{ href: "#contact", label: "Contato" },
];
export function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
// Hook de detecção de scroll para mudança de estado do Header
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<>
<motion.header
initial={{ y: -100 }}
animate={{ y: 0 }}
className={cn(
"fixed top-0 left-0 right-0 z-[100] transition-all duration-500 ease-in-out px-6 py-4",
scrolled
? "bg-black/40 backdrop-blur-xl border-b border-white/5 py-3"
: "bg-transparent py-6"
)}
>
<nav className="max-w-7xl mx-auto flex items-center justify-between">
{/* Logo com Identidade Visual de Engenheiro */}
<motion.a
href="#home"
onClick={(e) => {
e.preventDefault();
const element = document.querySelector("#home");
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "start" });
}
}}
whileHover={{ scale: 1.05 }}
className="flex items-center gap-2 cursor-pointer"
>
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center shadow-[0_0_15px_rgba(37,99,235,0.4)]">
<Layers className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-black tracking-tighter text-white">
EVANDRO<span className="text-blue-500">.DEV</span>
</span>
</motion.a>
{/* Desktop Navigation - Focada em Espaçamento Ótimo */}
<div className="hidden md:flex items-center gap-1 bg-white/5 p-1 rounded-full border border-white/10 backdrop-blur-md">
{navItems.map((item) => (
<a
key={item.href}
href={item.href}
onClick={(e) => {
e.preventDefault();
const element = document.querySelector(item.href);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "start" });
}
}}
className="px-5 py-2 text-xs font-bold uppercase tracking-widest text-slate-300 hover:text-white hover:bg-white/10 rounded-full transition-all duration-300 cursor-pointer"
>
{item.label}
</a>
))}
</div>
{/* Botão de Ação CTA (Call to Action) */}
<div className="hidden md:block">
<a
href="/Profile.pdf"
download="Evandro_Fonseca_Junior_CV.pdf"
className="px-6 py-2 bg-white text-black text-xs font-bold uppercase tracking-widest rounded-full hover:bg-blue-500 hover:text-white transition-all duration-500 inline-block"
>
Download CV
</a>
</div>
{/* Mobile Toggle */}
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="md:hidden p-2 text-white bg-white/5 rounded-lg border border-white/10"
>
{isMenuOpen ? <XIcon size={20} /> : <MenuIcon size={20} />}
</button>
</nav>
</motion.header>
{/* Mobile Menu Overlay com Framer Motion para fluidez */}
<AnimatePresence>
{isMenuOpen && (
<motion.div
initial={{ opacity: 0, x: "100%" }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: "100%" }}
transition={{ type: "spring", damping: 25, stiffness: 200 }}
className="fixed inset-0 z-[110] bg-black/95 backdrop-blur-2xl md:hidden flex flex-col items-center justify-center gap-8"
>
<button
onClick={() => setIsMenuOpen(false)}
className="absolute top-8 right-8 p-4 text-white/50 hover:text-white"
>
<XIcon size={32} />
</button>
{navItems.map((item, i) => (
<motion.a
key={item.href}
href={item.href}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
onClick={(e) => {
e.preventDefault();
setIsMenuOpen(false);
const element = document.querySelector(item.href);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "start" });
}
}}
className="text-3xl font-black text-white uppercase tracking-tighter hover:text-blue-500 transition-colors cursor-pointer"
>
{item.label}
</motion.a>
))}
<motion.a
href="/Profile.pdf"
download="Evandro_Fonseca_Junior_CV.pdf"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: navItems.length * 0.1 }}
className="mt-8 px-8 py-4 bg-white text-black text-xl font-black uppercase tracking-tighter rounded-full hover:bg-blue-500 hover:text-white transition-all duration-300"
>
Download CV
</motion.a>
</motion.div>
)}
</AnimatePresence>
</>
);
}