From eec9c4ea5fd9455ea48a31d2de6f2ca7687a1076 Mon Sep 17 00:00:00 2001 From: Aish-kul16 Date: Sun, 17 May 2026 16:45:11 +0530 Subject: [PATCH 1/2] refactor: improve navbar hierarchy and responsive layout --- frontend/src/components/shared/Navbar.jsx | 661 +++++++++++----------- 1 file changed, 342 insertions(+), 319 deletions(-) diff --git a/frontend/src/components/shared/Navbar.jsx b/frontend/src/components/shared/Navbar.jsx index b09b7d2..94b04a4 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,175 @@ 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 }) { + return ( +
+ {/* Single thin top accent — replaces aggressive border-4 */} +
+ +
+ {/* Section label */} +

+ AI-Powered Tools — GSSoC '26 +

+ + {/* Item grid — gap-px with bg-zinc-100 creates razor-thin dividers */} +
+ {MEGA_MENU_ITEMS.map((item) => { + const hasSubmenu = item.submenu && item.submenu.length > 0; + + // ── Contest Arsenal: full-width with nested submenu ──────────── + if (hasSubmenu) { + return ( +
+ {/* Parent header */} +
+
+ {item.icon} + {item.label} + +
+

+ {item.desc} +

+
+ {/* Submenu grid */} +
+ {item.submenu.map((sub) => ( + +
+ {sub.icon} + + {sub.label} + +
+

+ {sub.desc} +

+ + ))} +
+
+ ); + } + + // ── Standard grid item ───────────────────────────────────────── + const Wrapper = item.to ? Link : "div"; + const wrapperProps = item.to ? { to: item.to, onClick: onClose } : {}; + + return ( + +
+ {item.icon} + {item.label} + +
+

+ {item.desc} +

+
+ ); + })} +
+ + {/* Footer strip */} +
+ + More tools shipping soon + + + View All → + +
+
+
+ ); +} + +// ─── 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 megaTriggerRef = useRef(null); const megaLeaveTimer = useRef(null); const { isAuthenticated, user, logout } = useAuth(); - const navigate = useNavigate(); - const location = useLocation(); + const navigate = useNavigate(); + const location = useLocation(); + + // ── 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 + // ── Close mega on outside click ────────────────────────────────────────── useEffect(() => { const handler = (e) => { if ( @@ -96,18 +251,33 @@ export default function Navbar() { return () => document.removeEventListener("mousedown", handler); }, []); + // ── Close mobile menu on route change ─────────────────────────────────── + useEffect(() => { + setIsMenuOpen(false); + setMobileMegaOpen(false); + }, [location.pathname]); + + // ── 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); + }; + const handleMegaMouseLeave = () => { + megaLeaveTimer.current = setTimeout(() => setMegaOpen(false), 120); }; + // ── User helpers ───────────────────────────────────────────────────────── const getUserDisplayName = () => { if (!user) return ""; return user.name @@ -116,68 +286,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 ( -