diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 00000000..bca0b8ce --- /dev/null +++ b/.drone.yml @@ -0,0 +1,36 @@ +--- +kind: pipeline +type: docker +name: node14 + +steps: +- name: install-deps + image: node:14 + commands: + - npm install + +- name: lint + image: node:14 + depends_on: + - install-deps + commands: + - npm run lint + +- name: build + image: node:14 + depends_on: + - install-deps + commands: + - npm run build + +- name: export + image: node:14 + depends_on: + - build + commands: + - npm run export + +trigger: + event: + exclude: + - pull_request #avoid double build on PRs \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 55e2e5a6..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,49 +0,0 @@ -default: - image: node:14 - - cache: - paths: - - node_modules/ - - .next - -stages: - - build - - staging - -variables: - NEXT_PUBLIC_BASE_PATH: '/~a3thakra/csc' - -install_deps: - stage: .pre - script: - - npm install - -lint: - stage: build - script: - - npm run lint - -build: - stage: build - script: - - npm run build - -staging: - stage: staging - script: - - npm run export - artifacts: - paths: - - out - only: - refs: - - main - -deploy_staging: - stage: .post - needs: ["staging"] - script: - - 'curl -XPOST -H "Authorization: Basic $STAGING_SECRET" "https://csclub.uwaterloo.ca/~a3thakra/csc/"' - only: - refs: - - main \ No newline at end of file diff --git a/components/Navbar.module.css b/components/Navbar.module.css index 96c13beb..fa656d66 100644 --- a/components/Navbar.module.css +++ b/components/Navbar.module.css @@ -13,26 +13,36 @@ justify-content: space-between; align-items: center; - width: stretch; + width: 100%; max-width: calc(1440rem / 16); height: auto; padding: calc(28rem / 16) calc(136rem / 16); } -.logo { +.logo, +.logoMobile { display: flex; justify-content: center; align-items: center; } -.logo:hover { +.logo:hover, +.logoMobile:hover { cursor: pointer; } +.logoMobile { + display: none; +} + .logo img { width: calc(96rem / 16); } +.hamburger { + display: none; +} + .navMenu { display: inline-flex; flex-direction: row; @@ -60,7 +70,7 @@ color: var(--blue-2); } -.navMenu > li:hover > a { +.navMenu > li > a:hover { color: var(--blue-2); font-weight: 600; } @@ -79,15 +89,19 @@ padding: 1rem; } -.dropdownWrapper { +.itemWrapper { display: flex; flex-direction: column; justify-content: flex-start; align-items: center; } +.dropdownIcon { + display: none; +} + .dropdown { - visibility: visible; + visibility: hidden; display: flex; flex-direction: column; @@ -107,37 +121,33 @@ font-size: calc(14rem / 16); } -.dropdown > li { +.dropdown li { width: 100%; } -.dropdown > li > a { +.dropdown li a { padding: calc(8rem / 16); width: 100%; box-sizing: border-box; } -.dropdown > li:hover > a, -.dropdown > li > a:focus { +.dropdown li:hover a, +.dropdown li a:focus { background-color: var(--blue-1-20); } -.dropdown > li:first-child > a { +.dropdown li:first-child a { padding-top: 1rem; border-radius: calc(8rem / 16) calc(8rem / 16) 0 0; } -.dropdown > li:last-child > a { +.dropdown li:last-child a { padding-bottom: 1rem; border-radius: 0 0 calc(8rem / 16) calc(8rem / 16); } -.navMenu > li .dropdown { - visibility: hidden; -} - -.navMenu > li:hover .dropdown, -.navMenu > li:focus-within .dropdown { +.navMenu li:hover .dropdown, +.navMenu li:focus-within .dropdown { visibility: visible; } @@ -149,3 +159,214 @@ padding: calc(28rem / 16) calc(64rem / 16); } } + +@media screen and (max-width: calc(768rem / 16)) { + .navContent { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-areas: ". logo hamburger"; + column-gap: 0; + justify-items: end; + align-items: center; + + padding: calc(20rem / 16); + } + + .logo { + grid-area: logo; + justify-self: center; + } + + .logoMobile { + display: inline-flex; + justify-content: center; + align-items: center; + + margin-bottom: calc(20rem / 16); + } + + .logo img, + .logoMobile img { + width: calc(80rem / 16); + } + + .logoMobile img { + padding: 1rem; + } + + .hamburger { + grid-area: hamburger; + + display: flex; + justify-content: center; + align-items: center; + + padding: calc(12rem / 16); + width: calc(36rem / 16); + box-sizing: content-box; + + border: none; + background: none; + } + + .hamburger:hover { + cursor: pointer; + } + + .navMobileBackground { + position: fixed; + visibility: hidden; + + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 20; + + background-color: var(--navbar-gray); + opacity: 0; + + transition: 0.5s; + } + + .navMenuWrapper { + position: fixed; + + width: calc(288rem / 16); + box-sizing: border-box; + height: 100%; + top: 0; + right: 0; + overflow: auto; + z-index: 30; + + padding: calc(calc(64rem / 16) - 1rem); + padding-left: calc(calc(78rem / 16) - 1rem); + + background-color: var(--off-white); + + transform: translateX(100vw); + transition: 0.5s; + } + + .navMenu { + display: flex; + flex-direction: column; + gap: calc(4rem / 16); + + width: auto; + + font-size: calc(18rem / 16); + font-weight: 500; + text-align: left; + } + + .navMenu > .itemWrapper { + display: grid; + grid-template-columns: 1fr auto; + grid-template-areas: + "link button" + "dropdown dropdown"; + column-gap: 0; + row-gap: 0; + justify-items: start; + align-items: center; + + width: 100%; + } + + .navMenu > .itemWrapper > a { + grid-area: link; + } + + .dropdownIcon { + grid-area: button; + justify-self: end; + + display: flex; + justify-content: center; + align-items: center; + + padding: 1rem; + box-sizing: content-box; + + border: none; + background: none; + } + + .dropdownIcon:hover { + cursor: pointer; + } + + .dropdown { + grid-area: dropdown; + + display: none; + visibility: visible; + + justify-content: space-between; + align-items: start; + + position: static; + + margin: 0; + margin-left: calc(18rem / 16); + margin-bottom: calc(18rem / 16); + + border-radius: 0; + background: none; + box-shadow: none; + + font-size: 1rem; + } + + .dropdown > .itemWrapper { + align-items: start; + } + + .dropdown li { + width: auto; + text-align: left; + } + + .dropdown li a { + width: auto; + } + + .dropdown li:hover a, + .dropdown li a:focus { + background: none; + } + + .dropdown li:hover a { + color: default; + font-weight: default; + } + + .dropdown li:first-child a { + padding-top: calc(8rem / 16); + border-radius: 0; + } + + .dropdown li:last-child a { + padding-bottom: calc(8rem / 16); + border-radius: 0; + } + + .show { + display: block; + } + + .show.navMobileBackground { + visibility: visible; + opacity: 100%; + } + + .show.navMobileBackground + .navMenuWrapper { + transform: translateX(0); + } + + .rotate { + transform: rotate(180deg); + } +} diff --git a/components/Navbar.tsx b/components/Navbar.tsx index dd97771b..a127b938 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -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 ( + + ); +} + +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 = + 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 ? ( + {isExternalLink ? ( { - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur(); - } - }} + onClick={handleClick} > {props.name} @@ -95,61 +213,57 @@ function NavItem(props: NavLink) { 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} )} {(props.submenu?.length ?? 0) > 0 ? ( - + <> + + + ) : null} ); } -export function Navbar() { - return ( - - ); +function getMainRoute(route: string) { + if (route === "/") { + return "/"; + } else if (route.startsWith("http://") || route.startsWith("https://")) { + return route; + } + return "/" + route.split("/")[1]; } diff --git a/pages/_app.css b/pages/_app.css index 664bdb16..a3ba734c 100644 --- a/pages/_app.css +++ b/pages/_app.css @@ -18,6 +18,8 @@ body { #1481e3 -17.95%, #4ed4b2 172.82% ); + /* used in mobile navbar background */ + --navbar-gray: #787878b2; color: var(--black); font-family: "Poppins", "sans-serif"; @@ -45,6 +47,8 @@ body { #1481e3 -17.95%, #4ed4b2 172.82% ); + /* used in mobile navbar background */ + --navbar-gray: #787878b2; } h1, diff --git a/pages/resources/index.tsx b/pages/resources/index.tsx new file mode 100644 index 00000000..804c4d98 --- /dev/null +++ b/pages/resources/index.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import Head from "next/head"; + +export default function Resources() { + return ( + + + + ); +} diff --git a/public/images/dropdown-icon.svg b/public/images/dropdown-icon.svg new file mode 100644 index 00000000..7f847339 --- /dev/null +++ b/public/images/dropdown-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/hamburger.svg b/public/images/hamburger.svg new file mode 100644 index 00000000..2d1430ea --- /dev/null +++ b/public/images/hamburger.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/logo-icon.png b/public/images/logo-icon.png deleted file mode 100644 index dc0b2b8d..00000000 Binary files a/public/images/logo-icon.png and /dev/null differ diff --git a/public/images/logo-icon.svg b/public/images/logo-icon.svg new file mode 100644 index 00000000..733f4889 --- /dev/null +++ b/public/images/logo-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + +