- Map is a titled iframe (accessibility); drop @next/third-parties - "Services" nav toggle is a real button with aria-expanded (SEO crawlable-anchors, accessibility) - Hero images use default quality; page-header image is preloaded (LCP) - Mobile services submenu no longer clips its last item Mobile Lighthouse: / 94/100/100/100, /services/computer-repair 96/100/100/100, /areas/henderson 97/100/100/100, /contact 95/100/100/100 (performance/accessibility/best practices/SEO). Co-Authored-By: Claude Opus 5.5 <[email protected]>
215 lines
6.8 KiB
JavaScript
215 lines
6.8 KiB
JavaScript
"use client";
|
|
|
|
import { useState, useRef, useEffect } from "react";
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import styles from "./Navbar.module.css";
|
|
import logo from "../assets/logo.svg";
|
|
import menu from "../assets/menu.svg";
|
|
import close from "../assets/close.svg";
|
|
import { usePathname } from "next/navigation";
|
|
import { ChevronDownIcon } from "lucide-react";
|
|
// import { motion } from "motion/react"
|
|
|
|
export default function Navbar() {
|
|
const pathname = usePathname();
|
|
const [isActive, setActive] = useState("false");
|
|
const [isDropdownActive, setDropdownActive] = useState(false);
|
|
|
|
function handleToggle() {
|
|
setActive(!isActive);
|
|
}
|
|
|
|
function handleDropdownToggle() {
|
|
setDropdownActive(!isDropdownActive);
|
|
}
|
|
|
|
const wrapperRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
/**
|
|
* Close the menu if clicked on outside of element
|
|
*/
|
|
function handleClickOutside(event) {
|
|
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
|
|
setActive(true);
|
|
}
|
|
}
|
|
// Bind the event listener
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => {
|
|
// Unbind the event listener on clean up
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<nav className={`${styles.navbar} shadow-sm`}>
|
|
<div className={`${styles.container} container mx-auto`} ref={wrapperRef}>
|
|
<div className={styles.header}>
|
|
<div>
|
|
<Link href="/" className={styles.logo}>
|
|
<Image src={logo} width="32" height="32" alt="" />
|
|
<p>Te Atatu Computers</p>
|
|
</Link>
|
|
</div>
|
|
<button
|
|
className={styles.menuIcon}
|
|
aria-label="Menu"
|
|
onClick={handleToggle}
|
|
>
|
|
<Image
|
|
src={isActive ? menu : close}
|
|
width="32"
|
|
height="32"
|
|
alt=""
|
|
/>
|
|
</button>
|
|
</div>
|
|
<ul className={`${styles.links} ${isActive ? "" : styles.active}`}>
|
|
<li>
|
|
<Link
|
|
href="/"
|
|
className={`link ${pathname === "/" ? styles.active : ""}`}
|
|
>
|
|
Home
|
|
</Link>
|
|
</li>
|
|
<li
|
|
className={`
|
|
${styles.dropdown}
|
|
${isDropdownActive && !isActive ? styles.dropdownActive : ""}
|
|
`}
|
|
onMouseEnter={() => setDropdownActive(true)}
|
|
onMouseLeave={() => setDropdownActive(false)}
|
|
>
|
|
<div
|
|
data-state={isDropdownActive ? "open" : "close"}
|
|
className="flex items-center justify-center gap-1 [&[data-state=open]>svg]:rotate-180"
|
|
>
|
|
<button
|
|
type="button"
|
|
aria-expanded={isDropdownActive}
|
|
className={`${styles.dropdownTitle}
|
|
${pathname.startsWith("/services") ? styles.active : ""}`}
|
|
onClick={handleDropdownToggle}
|
|
>
|
|
Services
|
|
</button>
|
|
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
</div>
|
|
<ul className={styles.dropdownMenu}>
|
|
<li>
|
|
<Link
|
|
href="/services/computer-repair"
|
|
className={`link ${
|
|
pathname === "/services/computer-repair"
|
|
? styles.active
|
|
: ""
|
|
}`}
|
|
>
|
|
Computer & Laptop Repairs
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link
|
|
href="/services/computer-upgrades"
|
|
className={`link ${
|
|
pathname === "/services/computer-upgrades"
|
|
? styles.active
|
|
: ""
|
|
}`}
|
|
>
|
|
Computer Upgrades & Speedup
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link
|
|
href="/services/it-support"
|
|
className={`link ${
|
|
pathname === "/services/it-support" ? styles.active : ""
|
|
}`}
|
|
>
|
|
IT Support
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link
|
|
href="/services/virus-removal"
|
|
className={`link ${
|
|
pathname === "/services/virus-removal" ? styles.active : ""
|
|
}`}
|
|
>
|
|
Virus Removal
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link
|
|
href="/business"
|
|
className={`link ${
|
|
pathname === "/business" ? styles.active : ""
|
|
}`}
|
|
>
|
|
Business IT Plans
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link
|
|
href="/windows-10"
|
|
className={`link ${
|
|
pathname === "/windows-10" ? styles.active : ""
|
|
}`}
|
|
>
|
|
Windows 10 Help
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<Link
|
|
href="/pricing"
|
|
className={`link ${pathname === "/pricing" ? styles.active : ""}`}
|
|
>
|
|
Pricing
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link
|
|
href="/about"
|
|
className={`link ${pathname === "/about" ? styles.active : ""}`}
|
|
>
|
|
About
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link
|
|
href="/#faq"
|
|
>
|
|
FAQ
|
|
</Link>
|
|
</li>
|
|
<li className="bg-sky-900 text-white lg:ml-4">
|
|
<Link
|
|
href="/contact"
|
|
className={`link ${pathname === "/contact" ? styles.active : ""}`}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M2 3.5A1.5 1.5 0 0 1 3.5 2h1.148a1.5 1.5 0 0 1 1.465 1.175l.716 3.223a1.5 1.5 0 0 1-1.052 1.767l-.933.267c-.41.117-.643.555-.48.95a11.542 11.542 0 0 0 6.254 6.254c.395.163.833-.07.95-.48l.267-.933a1.5 1.5 0 0 1 1.767-1.052l3.223.716A1.5 1.5 0 0 1 18 15.352V16.5a1.5 1.5 0 0 1-1.5 1.5H15c-1.149 0-2.263-.15-3.326-.43A13.022 13.022 0 0 1 2.43 8.326 13.019 13.019 0 0 1 2 5V3.5Z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
Contact Us
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|