diff --git a/frontend/src/components/shared/Navbar.jsx b/frontend/src/components/shared/Navbar.jsx index b09b7d2..aa10e55 100644 --- a/frontend/src/components/shared/Navbar.jsx +++ b/frontend/src/components/shared/Navbar.jsx @@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from "react"; import { Link, useNavigate, useLocation } from "react-router-dom"; import { useAuth } from "../../context/AuthContext"; -// ─── Mega Menu Data ──────────────────────────────────────────────────────────── +// ─── Mega Menu Data (unchanged) ─────────────────────────────────────────────── const MEGA_MENU_ITEMS = [ { label: "Practice CP", @@ -67,20 +67,187 @@ const MEGA_MENU_ITEMS = [ }, ]; +// ─── Tag Badge ───────────────────────────────────────────────────────────────── +// Refined: thinner border, tighter tracking, color-coded per type +const TAG_COLORS = { + HOT: "border-orange-400 text-orange-500", + NEW: "border-emerald-400 text-emerald-600", + PRO: "border-zinc-400 text-zinc-500", +}; + +function Tag({ label }) { + if (!label) return null; + return ( + + {label} + + ); +} + +// ─── Chevron Icon ────────────────────────────────────────────────────────────── +function Chevron({ open, className = "" }) { + return ( + + ); +} + +// ─── Desktop Mega Menu Panel ─────────────────────────────────────────────────── +function MegaMenuPanel({ megaRef, onMouseEnter, onMouseLeave, onClose, megaTriggerRef, firstMenuItemRef, }) { + return ( +
+ ); +} + +// ─── Main Navbar ─────────────────────────────────────────────────────────────── export default function Navbar() { - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [megaOpen, setMegaOpen] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [megaOpen, setMegaOpen] = useState(false); const [mobileMegaOpen, setMobileMegaOpen] = useState(false); const [expandedSubmenu, setExpandedSubmenu] = useState(null); - const megaRef = useRef(null); + const [scrolled, setScrolled] = useState(false); + + const megaRef = useRef(null); + const firstMenuItemRef = useRef(null); const megaTriggerRef = useRef(null); const megaLeaveTimer = useRef(null); const { isAuthenticated, user, logout } = useAuth(); - const navigate = useNavigate(); - const location = useLocation(); + const navigate = useNavigate(); + const location = useLocation(); - // Close mega on outside click + // ── Scroll elevation shadow ────────────────────────────────────────────── + useEffect(() => { + const onScroll = () => setScrolled(window.scrollY > 4); + window.addEventListener("scroll", onScroll, { passive: true }); + return () => window.removeEventListener("scroll", onScroll); + }, []); + + // ── Close mega on outside click ────────────────────────────────────────── useEffect(() => { const handler = (e) => { if ( @@ -96,18 +263,46 @@ export default function Navbar() { return () => document.removeEventListener("mousedown", handler); }, []); + // ── Close mobile menu on route change ─────────────────────────────────── + useEffect(() => { + setIsMenuOpen(false); + setMobileMegaOpen(false); + }, [location.pathname]); + + useEffect(() => { + return () => { + clearTimeout(megaLeaveTimer.current); + }; +}, []); + + + // ── Handlers ──────────────────────────────────────────────────────────── const handleLogout = () => { logout(); navigate("/"); setIsMenuOpen(false); }; - const toggleMenu = () => setIsMenuOpen((v) => !v); - const closeMenu = () => { + const closeMenu = () => { setIsMenuOpen(false); setMobileMegaOpen(false); + setMegaOpen(false); + }; + + const handleMegaMouseEnter = () => { + clearTimeout(megaLeaveTimer.current); + setMegaOpen(true); + + setTimeout(() => { + firstMenuItemRef.current?.focus(); + }, 0); }; + const handleMegaMouseLeave = () => { + megaLeaveTimer.current = setTimeout(() => setMegaOpen(false), 120); + }; + + // ── User helpers ───────────────────────────────────────────────────────── const getUserDisplayName = () => { if (!user) return ""; return user.name @@ -116,68 +311,70 @@ export default function Navbar() { ? user.email.split("@")[0].toUpperCase() : ""; }; - const getUserInitial = () => { - if (!user) return ""; - if (user.name) return user.name.charAt(0).toUpperCase(); + if (!user) return "U"; + if (user.name) return user.name.charAt(0).toUpperCase(); if (user.email) return user.email.charAt(0).toUpperCase(); return "U"; }; - // Hover intent — stay open when moving cursor from trigger to panel - const handleMegaMouseEnter = () => { - clearTimeout(megaLeaveTimer.current); - setMegaOpen(true); - }; - - const handleMegaMouseLeave = () => { - megaLeaveTimer.current = setTimeout(() => setMegaOpen(false), 120); - }; - - const isActive = (path) => location.pathname === path; - - const linkCls = (path) => - `text-sm font-black uppercase tracking-widest transition-all duration-150 ${ + // ── Active link style ──────────────────────────────────────────────────── + // font-semibold + thin underline instead of font-black + decoration-4 + const isActive = (path) => location.pathname === path; + const navLinkCls = (path) => + `text-[13px] font-semibold uppercase tracking-[0.09em] transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2 ${ isActive(path) - ? "text-black underline underline-offset-8 decoration-4 decoration-black" - : "text-black hover:underline underline-offset-8 decoration-4 decoration-black" + ? "text-black underline underline-offset-4 decoration-[1.5px] decoration-black" + : "text-zinc-500 hover:text-black" }`; + // ── Mobile link row style ──────────────────────────────────────────────── + const mobileLinkCls = + "px-5 py-3.5 text-[13px] font-semibold uppercase tracking-[0.09em] text-zinc-600 border-b border-zinc-100 hover:bg-zinc-50 hover:text-black transition-colors duration-150 flex items-center justify-between"; + return ( -