import Link from "next/link"; import { useRouter } from "next/router"; import React, { useReducer } from "react"; import { Image } from "./Image"; import styles from "./Navbar.module.css"; type Menu = { name: string; route: string; exact?: boolean; submenu?: Menu; }[]; const menu: Menu = [ { name: "Home", route: "/", exact: true, }, { name: "About", route: "/about", submenu: [ { name: "About Us", route: "/about", exact: true, }, { name: "Meet the Team", route: "/about/team", }, { name: "Members", route: "/about/members", }, { name: "Constitution", route: "/about/constitution", }, { name: "Code of Conduct", route: "/about/code-of-conduct", }, { name: "Our Supporters", route: "/about/our-supporters", }, ], }, { name: "Get Involved", route: "/get-involved", }, { name: "Events", route: "/events", }, { name: "Resources", route: "/resources/services", submenu: [ { name: "Services", route: "/resources/services", }, { name: "Machine Usage", route: "/resources/machine-usage-agreement", }, { name: "Tech Talks", route: "/resources/tech-talks", }, { name: "CS Club Wiki", route: "https://wiki.csclub.uwaterloo.ca", }, { name: "Advice", route: "/resources/advice/co-op", submenu: [ { name: "Co-op Advice", route: "/resources/advice/co-op", }, { name: "Academic Advice", route: "/resources/advice/academic", }, { name: "Additional Resources", route: "/resources/advice/misc", }, ], }, { name: "Internships", route: "https://github.com/uwcsc/2023-internships", }, ], }, ]; export function Navbar() { const router = useRouter(); const [state, dispatch] = useReducer(reducer, initialState); return ( ); } interface MobileState { isNavOpen: boolean; activeSubmenus: Set; // strings are NavLink routes } type MobileAction = | { type: "open"; route: string } | { type: "toggle"; route: string } | { type: "close" }; const initialState: MobileState = { isNavOpen: false, activeSubmenus: new Set(), }; function reducer(state: MobileState, action: MobileAction): MobileState { switch (action.type) { case "open": return { isNavOpen: true, activeSubmenus: new Set([getMainRoute(action.route)]), }; case "toggle": { const newSet = new Set(state.activeSubmenus); if (state.activeSubmenus.has(getMainRoute(action.route))) { newSet.delete(getMainRoute(action.route)); } else { newSet.add(getMainRoute(action.route)); } return { isNavOpen: state.isNavOpen, activeSubmenus: newSet, }; } case "close": return initialState; } } interface NavItemProps { name: string; route: string; submenu?: { name: string; route: string; }[]; mainRouteActive: boolean; onToggle(route: string): void; onClose(): void; } function NavItem(props: NavItemProps) { const router = useRouter(); const isCurrentPage = shouldHighlight( router.pathname, props.name, props.route ); const isExternalLink = props.route.includes("http://") || props.route.includes("https://"); function handleClick() { if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } props.onClose(); } return ( <> {isExternalLink ? ( {props.name} ) : ( {props.name} )} {(props.submenu?.length ?? 0) > 0 ? ( <> ) : null} ); } interface Leaf { name: string; route: string; exact?: boolean; ancestors: { name: string; route: string }[]; } function collectLeaves( accumulator: Leaf[], entry: { name: string; route: string; exact?: boolean; submenu?: Menu; } ): Leaf[] { if (entry.submenu == null) { return [...accumulator, { ...entry, ancestors: [] }]; } const subleaves = entry.submenu.reduce(collectLeaves, [] as Leaf[]); return [ ...accumulator, ...subleaves.map((leaf) => ({ ...leaf, ancestors: [...leaf.ancestors, { name: entry.name, route: entry.route }], })), ]; } const leaves: Leaf[] = menu.reduce(collectLeaves, [] as Leaf[]); function shouldHighlight( pathname: string, name: string, route: string ): boolean { const match = leaves.find((leaf) => leaf.exact ? leaf.route === pathname : pathname.startsWith(leaf.route) ); return match ? (match.name === name && match.route === route) || match.ancestors.find( (ancestor) => ancestor.name === name && ancestor.route === route ) != null : false; } function getMainRoute(route: string) { if (route === "/") { return "/"; } else if (route.startsWith("http://") || route.startsWith("https://")) { return route; } return "/" + route.split("/")[1]; } function HamburgerSvg() { return ( ); } function DropdownSvg() { return ( ); }