diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 8a7e6d8..4f958c1 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,9 +1,173 @@ import FooterBasic from '@/components/sections/footer/FooterBasic'; -import NavbarCentered from '@/components/ui/NavbarCentered'; -import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary"; -import SiteBackgroundSlot from "@/components/ui/SiteBackgroundSlot"; +import SectionErrorBoundary from '@/components/ui/SectionErrorBoundary'; +import SiteBackgroundSlot from '@/components/ui/SiteBackgroundSlot'; import { Outlet } from 'react-router-dom'; -import { StyleProvider } from "@/components/ui/StyleProvider"; +import { StyleProvider } from '@/components/ui/StyleProvider'; +import { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import { Plus, ArrowRight } from 'lucide-react'; +import { cls } from '@/lib/utils'; +import Button from '@/components/ui/Button'; + +// ── Inlined from src/components/ui/NavbarCentered.tsx (was ) +// You can now edit InlinedNavbarCentered freely below — it is a local copy and +// will not affect any other page that imports the original NavbarCentered. +interface NavbarCenteredProps { + logo: string; + navItems: { name: string; href: string }[]; + ctaButton: { text: string; href: string }; +} + +const handleNavClick = (e: React.MouseEvent, href: string, onClose?: () => void) => { + if (href.startsWith("#")) { + e.preventDefault(); + const element = document.getElementById(href.slice(1)); + element?.scrollIntoView({ behavior: "smooth", block: "start" }); + } + onClose?.(); +}; + +const InlinedNavbarCentered = ({ logo, navItems, ctaButton }: NavbarCenteredProps) => { + const [isScrolled, setIsScrolled] = useState(false); + const [menuOpen, setMenuOpen] = useState(false); + const menuRef = useRef(null); + + useEffect(() => { + const handleScroll = () => setIsScrolled(window.scrollY > 50); + window.addEventListener("scroll", handleScroll, { passive: true }); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && menuOpen) setMenuOpen(false); + }; + const handleClickOutside = (e: MouseEvent) => { + if (menuOpen && menuRef.current && !menuRef.current.contains(e.target as Node)) { + setMenuOpen(false); + } + }; + document.addEventListener("keydown", handleKeyDown); + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [menuOpen]); + + return ( + <> + + + {logo} + + + {navItems.map((item) => ( + item.name === "Services" ? ( + handleNavClick(e, item.href)} + variant="primary" + animate={false} + className="bg-blue-600 text-white rounded-none" + /> + ) : ( + handleNavClick(e, item.href)} + className="text-base text-foreground hover:opacity-70 transition-opacity" + > + {item.name} + + ) + ))} + + + + + + setMenuOpen(!menuOpen)} + aria-label="Toggle menu" + aria-expanded={menuOpen} + > + + + + + + + + {menuOpen && ( + + + Menu + setMenuOpen(false)} + aria-label="Close menu" + > + + + + + + {navItems.map((item, index) => ( + + {item.name === "Services" ? ( + handleNavClick(e, item.href, () => setMenuOpen(false))} + variant="primary" + animate={false} + className="w-full bg-blue-600 text-white rounded-none" + /> + ) : ( + handleNavClick(e, item.href, () => setMenuOpen(false))} + className="flex items-center justify-between py-2 text-base font-medium text-foreground" + > + {item.name} + + + )} + {index < navItems.length - 1 && ( + + )} + + ))} + + + + + + + )} + + > + ); +}; export default function Layout() { const navItems = [ @@ -41,7 +205,7 @@ export default function Layout() { -
Menu