/
gitverse
/
studio-baker-template
Обзор
Документация
Войти
/
gitverse
/
studio-baker-template
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
master
src/components/header.tsx
192 строки
7 KB
studio-agent
GigaStudio шаблон Пекарня
12 май 2026, 15:25
12 май 2026, 15:25
4e87baf
Код
Авторство
О чём код?
"use client"; import { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Button } from "@/components/ui/button"; import { Menu as MenuIcon, X, Phone, Globe } from "lucide-react"; import { cn } from "@/lib/utils"; const NAV_ITEMS = [ { name: "Главная", href: "#hero" }, { name: "О нас", href: "#about" }, { name: "Меню", href: "#menu" }, { name: "Галерея", href: "#gallery" }, { name: "Контакты", href: "#contact" }, ]; export default function Header() { const [isScrolled, setIsScrolled] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [activeSection, setActiveSection] = useState("hero"); useEffect(() => { // Объединенный обработчик скролла для фона хедера и Scroll Spy const handleScroll = () => { const scrollY = window.scrollY; // Эффект затемнения хедера setIsScrolled(scrollY > 20); // Scroll Spy логика: находим секцию, которая ближе всего к верху вьюпорта (с учетом хедера) // offset 100px - примерное расстояние от верха экрана до точки срабатывания const scrollPosition = scrollY + 150; const sections = NAV_ITEMS.map((item) => document.querySelector(item.href)); let current = "hero"; // Проходим по секциям и находим последнюю, которую мы "прошли" sections.forEach((section) => { if (section && section.offsetTop <= scrollPosition) { current = section.id; } }); setActiveSection(current); }; window.addEventListener("scroll", handleScroll, { passive: true }); // Вызываем один раз при загрузке, чтобы установить активную ссылку handleScroll(); return () => window.removeEventListener("scroll", handleScroll); }, []); return ( <header className={cn( "fixed top-0 left-0 right-0 z-50 transition-all duration-300", isScrolled ? "bg-[#FAF9F6]/95 dark:bg-[#1a1614]/95 backdrop-blur-md shadow-md py-3" : "bg-transparent py-5" )} > <div className="container flex items-center justify-between"> {/* Logo */} <motion.a href="#" initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }} className="font-cormorant text-3xl font-light tracking-widest text-[#fbbf24] hover:text-[#FDFBF7] transition-colors" > Пекарня </motion.a> {/* Desktop Nav */} <nav className="hidden md:flex items-center gap-8"> {NAV_ITEMS.map((item) => { const isActive = activeSection === item.href.substring(1); return ( <a key={item.name} href={item.href} className={cn( "text-sm font-medium transition-colors relative group", isScrolled ? "text-[#78350F]" : "text-[#FDFBF7]", isActive ? "text-[#fbbf24]" : "hover:text-[#fbbf24]" )} > {item.name} <span className={cn( "absolute -bottom-1 left-0 h-0.5 bg-[#fbbf24] transition-all", isActive ? "w-full" : "w-0 group-hover:w-full" )} /> </a> ); })} </nav> {/* Actions */} <div className="flex items-center gap-4"> {/* Phone */} <div className={cn( "hidden lg:flex items-center gap-2 text-sm font-semibold", isScrolled ? "text-[#78350F]" : "text-[#FDFBF7]" )}> <Phone className="h-4 w-4" /> <span>+7 (999) ***-**-**</span> </div> {/* Lang */} <div className="hidden lg:flex items-center gap-2"> <Button variant="ghost" size="icon" className={cn( "hover:bg-transparent", isScrolled ? "text-[#78350F] hover:text-[#d97706]" : "text-[#FDFBF7] hover:text-[#fbbf24]" )} > <Globe className="h-4 w-4" /> </Button> </div> {/* Order Button */} <a href="#contact"> <Button className="hidden md:inline-flex bg-[#d97706] hover:bg-[#b45309] text-white px-8 py-6 h-auto transition-transform hover:scale-105 shadow-xl"> Заказать </Button> </a> {/* Mobile Toggle */} <Button variant="ghost" size="icon" className={cn( "md:hidden hover:bg-transparent", isScrolled ? "text-[#78350F] hover:text-[#d97706]" : "text-[#FDFBF7] hover:text-[#fbbf24]" )} onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} > {isMobileMenuOpen ? <X /> : <MenuIcon />} </Button> </div> </div> {/* Mobile Menu */} <AnimatePresence> {isMobileMenuOpen && ( <motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "100vh" }} exit={{ opacity: 0, height: 0 }} className="absolute top-full left-0 w-full bg-[#FAF9F6] dark:bg-[#1a1614] border-b overflow-hidden md:hidden" > <nav className="flex flex-col p-6 gap-6 text-center"> {NAV_ITEMS.map((item, i) => ( <motion.a key={item.name} href={item.href} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.1 }} onClick={() => setIsMobileMenuOpen(false)} className="text-xl font-cormorant text-[#78350F]" > {item.name} </motion.a> ))} <div className="pt-4 flex flex-col gap-4"> <a href="#contact" className="w-full"> <Button className="w-full bg-[#d97706] hover:bg-[#b45309] shadow-xl hover:scale-105 transition-transform"> Заказать </Button> </a> <div className="flex justify-center gap-4"> <Button variant="ghost" size="icon"> <Globe /> </Button> </div> </div> </nav> </motion.div> )} </AnimatePresence> </header> ); }