Skip to content

Commit

Permalink
build navbar:
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzhang1618 committed Dec 14, 2024
1 parent abd1f64 commit 00c77e2
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Footer from '@/components/Footer';
import Navbar from '@/components/Navbar';
import '@/styles/globals.scss';
import type { Metadata } from 'next';
import { DM_Sans } from 'next/font/google';
Expand All @@ -14,6 +15,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<html lang="en">
<body className={dmSans.className}>
<Navbar />
{children}
<Footer />
</body>
Expand Down
119 changes: 119 additions & 0 deletions src/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
'use client';

import { useEffect, useRef, useState } from 'react';
import styles from './style.module.scss';
import Typography from '../Typography';
import Image from 'next/image';
import Link from 'next/link';
import MenuIcon from '@mui/icons-material/Menu';
import CloseIcon from '@mui/icons-material/Close';
import { SwipeableDrawer } from '@mui/material';

const NavbarLogo = () => (
<div className={styles.logo}>
<Image src="/assets/acm-logo.png" alt="ACM Logo" width={48} height={48} />
<Typography variant="body/large" className={styles.logoText}>
<b>diamond</b>
<br />
hacks
</Typography>
</div>
);

const links = [
{ name: 'Home', href: '/' },
{ name: 'About', href: '/#about' },
{ name: 'Impact', href: '/#impact' },
{ name: 'FAQ', href: '/#faq' },
{ name: 'Sponsors', href: '/#sponsors' },
{ name: '2024', href: 'https://2024.diamondhacks.acmucsd.com' },
];

const DEBOUNCE_MS = 150; // Prevent back-to-back updates within 150ms
const MOBILE_BREAKPOINT = 870; // Matches $breakpoint-md from vars.scss

export default function Navbar() {
const [prevScrollPos, setPrevScrollPos] = useState(0);
const [visible, setVisible] = useState(true);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const lastUpdate = useRef(0);

const onLinkClick = () => {
lastUpdate.current = Date.now();
setVisible(false);
setMobileMenuOpen(false);
};

useEffect(() => {
// Show/hide navbar on scroll
const handleScroll = () => {
const currentScrollPos = window.scrollY;
const now = Date.now();

const readyToUpdate = now - lastUpdate.current > DEBOUNCE_MS;

// Show navbar when scrolling up or at top of page
const scrollingUp = currentScrollPos < prevScrollPos;
const topOfPage = currentScrollPos < 10;
const shouldBeVisible = scrollingUp || topOfPage;

if (readyToUpdate) {
setVisible(shouldBeVisible);
setPrevScrollPos(currentScrollPos);
lastUpdate.current = now;
}
};

window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [prevScrollPos]);

useEffect(() => {
// Close mobile menu when screen gets larger than mobile breakpoint
const handleResize = () => {
if (window.innerWidth > MOBILE_BREAKPOINT) {
setMobileMenuOpen(false);
}
};

window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);

return (
<>
<div className={`${styles.container} ${visible ? styles.visible : styles.hidden}`}>
<NavbarLogo />
<Typography variant="body/large" className={styles.desktopLinks}>
{links.map(link => (
<Link href={link.href} className={styles.link}>
{link.name}
</Link>
))}
</Typography>
<div className={styles.mobileIcons}>
<div className={`${styles.menuIcon} ${mobileMenuOpen ? '' : styles.hidden}`}>
<CloseIcon onClick={() => setMobileMenuOpen(false)} />
</div>
<div className={`${styles.menuIcon} ${mobileMenuOpen ? styles.hidden : ''}`}>
<MenuIcon onClick={() => setMobileMenuOpen(true)} />
</div>
</div>
</div>
<SwipeableDrawer
anchor="top"
open={mobileMenuOpen}
onClose={() => setMobileMenuOpen(false)}
onOpen={() => setMobileMenuOpen(true)}
>
<div className={styles.mobileMenu}>
{links.map(link => (
<Link href={link.href} className={styles.link} onClick={onLinkClick}>
{link.name}
</Link>
))}
</div>
</SwipeableDrawer>
</>
);
}
103 changes: 103 additions & 0 deletions src/components/Navbar/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
@use '../../styles/vars.scss' as vars;

.container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
padding: 2rem vars.$side-padding;
background-color: vars.$black;
transition: transform 0.15s ease-in-out;
z-index: 2000;

@media screen and (max-width: vars.$breakpoint-md) {
padding: 1rem vars.$side-padding-mobile;
}
}

.visible {
transform: translateY(0);
}

.hidden {
transform: translateY(-100%);
}

.logo {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;

@media screen and (max-width: vars.$breakpoint-md) {
color: vars.$white;
align-self: flex-start;
}
}

.logoText {
font-weight: 200 !important;
line-height: 1.375rem !important;
}

.desktopLinks {
display: flex;
flex-direction: row;
gap: 2rem;

@media screen and (max-width: vars.$breakpoint-md) {
display: none;
}
}

.link {
text-decoration: none;
color: vars.$white;

@media screen and (max-width: vars.$breakpoint-md) {
background-color: vars.$black;
text-align: center;
}
}

.mobileIcons {
display: none;
position: relative;
width: 24px;
height: 24px;
overflow: hidden;

.menuIcon {
position: absolute;
right: 0;
top: 0;
opacity: 1;
transition: opacity 0.3s ease-in-out;
cursor: pointer;

&.hidden {
opacity: 0;
pointer-events: none;
}
}

@media screen and (max-width: vars.$breakpoint-md) {
display: block;
}
}

.mobileMenu {
padding: 1.5rem vars.$side-padding-mobile;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1.5rem;
background-color: vars.$black;
padding-top: 6rem;
}
2 changes: 1 addition & 1 deletion src/sections/About/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const About: React.FC = () => {
];

return (
<section className={styles.container}>
<section className={styles.container} id="about">
<AboutDescription />
<div className={styles.isMobile}>
<ImagePicker images={images} />
Expand Down
2 changes: 1 addition & 1 deletion src/sections/FAQ/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import FAQAccordion from '@/components/FAQAccordion';

const FAQ: React.FC = () => {
return (
<section className={styles.container}>
<section className={styles.container} id="faq">
<Typography variant="display/heavy/small" className={styles.title}>
Frequently Asked Questions
</Typography>
Expand Down
2 changes: 1 addition & 1 deletion src/sections/Sponsors/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { SPONSORS } from './sponsors';

const Sponsors: React.FC = () => {
return (
<section className={styles.container}>
<section className={styles.container} id="sponsors">
<div className={styles.content}>
<Typography variant="display/heavy/small" className={styles.title}>
Thank you to our past sponsors...
Expand Down
2 changes: 1 addition & 1 deletion src/sections/Statistics/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Image from 'next/image';

const Statistics: React.FC = () => {
return (
<section className={styles.container}>
<section className={styles.container} id="impact">
<div className={styles.diamond}>
<div className={styles.slidingDiamondWrapper}>
<Image
Expand Down
6 changes: 5 additions & 1 deletion src/styles/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ body {

@media screen and (max-width: vars.$breakpoint-md) {
padding: 0 vars.$side-padding-mobile;
margin-top: 4rem;
margin-top: 6rem;
}
}

section {
padding-top: 4rem;
}

0 comments on commit 00c77e2

Please sign in to comment.