@ -1,19 +1,19 @@
import React from "react" ;
import React , { useReducer } from "react" ;
import Link from "next/link" ;
import { useRouter } from "next/router" ;
import { Image } from "./Image" ;
import styles from "./Navbar.module.css" ;
interface NavLink {
type Menu = {
name : string ;
route : string ;
submenu ? : {
name : string ;
route : string ;
} [ ] ;
}
} [ ] ;
const menu : NavLink [ ] = [
const menu : Menu = [
{
name : "Home" ,
route : "/" ,
@ -66,28 +66,146 @@ const menu: NavLink[] = [
} ,
{
name : "CS Club Wiki" ,
route : "https://wiki.csclub.uwaterloo.ca/ " ,
route : "https://wiki.csclub.uwaterloo.ca" ,
} ,
] ,
} ,
] ;
function NavItem ( props : NavLink ) {
export function Navbar() {
const router = useRouter ( ) ;
const externalLink =
const [ state , dispatch ] = useReducer ( reducer , initialState ) ;
return (
< nav className = { styles . navbar } >
< div className = { styles . navContent } >
< Link href = "/" >
< a className = { styles . logo } >
< Image src = "/images/logo-icon.svg" alt = "CSC Logo" / >
< / a >
< / Link >
< button
className = { styles . hamburger }
onClick = { ( ) = > dispatch ( { type : "open" , route : router.pathname } ) }
>
< Image src = "/images/hamburger.svg" alt = "Menu" / >
< / button >
< div
className = {
state . isNavOpen
? ` ${ styles . navMobileBackground } ${ styles . show } `
: styles . navMobileBackground
}
onClick = { ( ) = > dispatch ( { type : "close" } ) }
/ >
< div className = { styles . navMenuWrapper } >
< Link href = "/" >
< a
className = { styles . logoMobile }
onClick = { ( ) = > dispatch ( { type : "close" } ) }
>
< Image src = "/images/logo-icon.svg" alt = "CSC Logo" / >
< / a >
< / Link >
< ul className = { styles . navMenu } >
{ menu . map ( ( item ) = > {
return (
< li className = { styles . itemWrapper } key = { item . name } >
< NavItem
name = { item . name }
route = { item . route }
submenu = { item . submenu }
mainRouteActive = { state . activeSubmenus . has (
getMainRoute ( item . route )
) }
onClose = { ( ) = > dispatch ( { type : "close" } ) }
onToggle = { ( route ) = > dispatch ( { type : "toggle" , route } ) }
/ >
< / li >
) ;
} ) }
< / ul >
< / div >
< / div >
< / nav >
) ;
}
interface MobileState {
isNavOpen : boolean ;
activeSubmenus : Set < string > ; // 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 =
router . pathname === props . route ||
( props . submenu != null &&
router . pathname . startsWith ( getMainRoute ( 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 (
< >
{ externalLink ? (
{ isE xternalLink ? (
< a
href = { props . route }
target = "_blank"
rel = "noopener noreferrer"
onClick = { ( ) = > {
if ( document . activeElement instanceof HTMLElement ) {
document . activeElement . blur ( ) ;
}
} }
onClick = { handleClick }
>
{ props . name }
< / a >
@ -95,61 +213,57 @@ function NavItem(props: NavLink) {
< Link href = { props . route } >
< a
title = { props . name }
className = {
router . pathname === props . route ||
( ( props . submenu ? . length ? ? 0 ) > 0 &&
router . pathname . startsWith ( props . route ) )
? styles . currentPage
: ""
}
onClick = { ( ) = > {
if ( document . activeElement instanceof HTMLElement ) {
document . activeElement . blur ( ) ;
}
} }
className = { isCurrentPage ? styles . currentPage : "" }
onClick = { handleClick }
>
{ props . name }
< / a >
< / Link >
) }
{ ( props . submenu ? . length ? ? 0 ) > 0 ? (
< ul className = { styles . dropdown } >
{ props . submenu ? . map ( ( item ) = > {
return (
< li className = { styles . dropdownWrapper } key = { item . name } >
< NavItem name = { item . name } route = { item . route } / >
< / li >
) ;
} ) }
< / ul >
< >
< button
className = {
props . mainRouteActive
? ` ${ styles . dropdownIcon } ${ styles . rotate } `
: styles . dropdownIcon
}
onClick = { ( ) = > props . onToggle ( props . route ) }
>
< Image src = "/images/dropdown-icon.svg" alt = "Dropdown Icon" / >
< / button >
< ul
className = {
props . mainRouteActive
? ` ${ styles . dropdown } ${ styles . show } `
: styles . dropdown
}
>
{ props . submenu ? . map ( ( item ) = > {
return (
< li className = { styles . itemWrapper } key = { item . name } >
< NavItem
name = { item . name }
route = { item . route }
mainRouteActive = { props . mainRouteActive }
onClose = { ( ) = > props . onClose ( ) }
onToggle = { ( route ) = > props . onToggle ( route ) }
/ >
< / li >
) ;
} ) }
< / ul >
< / >
) : null }
< / >
) ;
}
export function Navbar() {
return (
< nav className = { styles . navbar } >
< div className = { styles . navContent } >
< Link href = "/" >
< a className = { styles . logo } >
< Image src = "/images/logo-icon.png" alt = "CSC Logo" / >
< / a >
< / Link >
< ul className = { styles . navMenu } >
{ menu . map ( ( item ) = > {
return (
< li className = { styles . dropdownWrapper } key = { item . name } >
< NavItem
name = { item . name }
route = { item . route }
submenu = { item . submenu }
/ >
< / li >
) ;
} ) }
< / ul >
< / div >
< / nav >
) ;
function getMainRoute ( route : string ) {
if ( route === "/" ) {
return "/" ;
} else if ( route . startsWith ( "http://" ) || route . startsWith ( "https://" ) ) {
return route ;
}
return "/" + route . split ( "/" ) [ 1 ] ;
}