Skip to content

Commit

Permalink
Mobile Navigation Component (#234)
Browse files Browse the repository at this point in the history
* mostly done

need to figure out gradient length fix and add white gradient when white navgiation bar

* temp fix to about

* white gradient

* login button styling

* gradient styling fixes

* styling

* cn reformatting

* text too big :(

* hover scale styling

* Close mobile menu on outside click

* delete unused routes

* fixed signup and auth error handling

* fixed formatting

* responses fixed

* update authform

changed sign up input text's preview text to be username or email.

* Fix blog validations on client (#241)

Yippee

* Fix blogs validation part 2, remove last_update_date (#242)

* Fix blog validations on client

* Remove last_update_date

* Make b:push run first

---------

Co-authored-by: Ricky Zhang <[email protected]>
Co-authored-by: UF SASE Webmaster <[email protected]>

* wrote better program description

* trivial change

---------

Co-authored-by: Ricky Zhang <[email protected]>
Co-authored-by: Thuy Le <[email protected]>
Co-authored-by: redtinypoop <[email protected]>
Co-authored-by: Sihala Senevirathne <[email protected]>
Co-authored-by: UF SASE Webmaster <[email protected]>
Co-authored-by: Elliot Liu <[email protected]>
  • Loading branch information
7 people authored Feb 19, 2025
1 parent 0cf3bcd commit a75ed22
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 94 deletions.
54 changes: 29 additions & 25 deletions src/client/components/navigation/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,27 +53,21 @@ const Header: React.FC = () => {
const menuRef = useRef<HTMLDivElement>(null);
const hamburgerRef = useRef<HTMLButtonElement>(null);

const handleOutsideClick = (event: MouseEvent) => {
if (
menuRef.current &&
!menuRef.current.contains(event.target as Node) &&
hamburgerRef.current &&
!hamburgerRef.current.contains(event.target as Node)
) {
setMenuOpen(false);
}
};

useEffect(() => {
if (menuOpen) {
document.addEventListener("mousedown", handleOutsideClick);
} else {
document.removeEventListener("mousedown", handleOutsideClick);
}
return () => {
document.removeEventListener("mousedown", handleOutsideClick);
const handleOutsideClick = (event: MouseEvent) => {
if (
hamburgerRef.current &&
!hamburgerRef.current.contains(event.target as Node) &&
menuRef.current &&
!menuRef.current.contains(event.target as Node)
) {
setMenuOpen(false);
}
};
}, [menuOpen]);

document.addEventListener("mousedown", handleOutsideClick);
return () => document.removeEventListener("mousedown", handleOutsideClick);
}, []);

useEffect(() => {
const handleResize = () => {
Expand All @@ -89,7 +83,7 @@ const Header: React.FC = () => {

return (
<header
className={cn(`sticky left-0 top-0 z-50 w-full font-poppins font-medium shadow-md`, {
className={cn(`sticky left-0 top-0 z-50 w-full font-redhat font-medium shadow-md`, {
"bg-black text-white": isHomePage,
"bg-white text-black": !isHomePage,
})}
Expand All @@ -98,26 +92,36 @@ const Header: React.FC = () => {
{/* Logo */}
<Logo />

{/* Desktop Navigation */}
{/* Desktop Nav */}
<div className="hidden w-full items-center justify-between md:flex">
<div className="ml-auto flex items-center gap-4">
<DesktopMenu navItems={navItems} isHomePage={isHomePage} />
<SearchBar />
<UserButton isLoggedIn={isAuthenticated} onLogout={logout} />
<div className="hidden md:block">
<UserButton isLoggedIn={isAuthenticated} onLogout={logout} isHomePage={isHomePage} />
</div>
</div>
</div>

{/* Mobile Navigation */}
{/* Mobile Nav */}
<div className="flex items-center gap-4 md:hidden">
<SearchBar className="w-32 focus:w-64" />
<UserButton isLoggedIn={isAuthenticated} onLogout={logout} />
<button ref={hamburgerRef} className="focus:outline-none">
<Hamburger toggled={menuOpen} toggle={setMenuOpen} color={isHomePage ? "#fff" : "#000"} size={22} />
</button>
</div>

{/* Mobile Menu */}
<MobileMenu navItems={navItems} isOpen={menuOpen} onClose={() => setMenuOpen(false)} isHomePage={isHomePage} />
<div ref={menuRef}>
<MobileMenu
navItems={navItems}
isOpen={menuOpen}
onClose={() => setMenuOpen(false)}
isHomePage={isHomePage}
isLoggedIn={isAuthenticated}
onLogout={logout}
/>
</div>
</nav>
</header>
);
Expand Down
167 changes: 102 additions & 65 deletions src/client/components/navigation/MobileMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { cn } from "@/shared/utils";
import { Icon } from "@iconify/react";
import { UserButton } from "@navigation/UserButton";
import { useRouter } from "@tanstack/react-router";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";

interface NavItem {
name: string;
Expand All @@ -13,91 +15,126 @@ interface MobileMenuProps {
isOpen: boolean;
onClose: () => void;
isHomePage: boolean;
isLoggedIn: boolean;
onLogout: () => void;
}

export const MobileMenu: React.FC<MobileMenuProps> = ({ isHomePage, isOpen, navItems, onClose }) => {
if (!isOpen) return null;
export const MobileMenu: React.FC<MobileMenuProps> = ({ isHomePage, isLoggedIn, isOpen, navItems, onClose, onLogout }) => {
const [allSubmenusClosed, setAllSubmenusClosed] = useState(false);

useEffect(() => {
setAllSubmenusClosed(!isOpen);
}, [isOpen]);

const menuClasses = cn(
"fixed right-0 top-16 h-auto w-auto rounded-bl-2xl pb-20 font-redhat text-lg shadow-md transition-all duration-300 ease-in-out",
isOpen ? "pointer-events-auto translate-x-0 opacity-100" : "pointer-events-none translate-x-20 opacity-0",
isHomePage ? "bg-black text-white" : "bg-white text-black",
);

const ulClasses = cn("flex w-full flex-col items-end space-y-2 pb-4 pr-4 pt-3");

return (
<div className={cn("absolute left-0 top-16 z-40 w-full shadow-md", isHomePage ? "bg-black text-white" : "bg-white text-black")}>
<ul className="flex flex-col space-y-2 p-4">
<div className={menuClasses}>
<ul className={ulClasses}>
{navItems.map((item) => (
<MobileNavItem key={item.name} item={item} onClose={onClose} isHomePage={isHomePage} />
<MobileNavItem key={item.name} item={item} onClose={onClose} isHomePage={isHomePage} closeAll={allSubmenusClosed} />
))}
<UserButton isLoggedIn={isLoggedIn} onLogout={onLogout} isHomePage={isHomePage} />
</ul>
</div>
);
};

const MobileNavItem: React.FC<{ item: NavItem; onClose: () => void; isHomePage: boolean }> = ({ isHomePage, item, onClose }) => {
const router = useRouter(); // tanstack router instance
const MobileNavItem: React.FC<{
item: NavItem;
onClose: () => void;
isHomePage: boolean;
closeAll: boolean;
}> = ({ closeAll, isHomePage, item, onClose }) => {
const router = useRouter();
const [submenuOpen, setSubmenuOpen] = useState(false);
const hasChildren = item.children && item.children.length > 0;

useEffect(() => {
if (closeAll) setSubmenuOpen(false);
}, [closeAll]);

const handleClick = () => {
if (submenuOpen && ["About", "Events", "Programs"].includes(item.name) && item.path) {
if (item.children && item.children.length > 0) {
if (submenuOpen) {
if (item.path) {
router.navigate({ to: item.path });
onClose();
}
} else {
setSubmenuOpen(true);
}
} else if (item.path) {
router.navigate({ to: item.path });
onClose();
} else {
setSubmenuOpen(!submenuOpen);
}
};

const liClasses = cn("w-full text-right");

const buttonClasses = cn(
"flex w-full items-center justify-end px-2 py-1 pl-20 text-right transition-colors duration-300 focus:outline-none",
isHomePage ? "text-white hover:text-[#0f6cb6]" : "text-black hover:text-[#0f6cb6]",
);

const svgClasses = cn("mr-1 h-4 w-4 transition-transform duration-300");

const gradientClasses = cn(
"mr-4 mt-1 w-auto bg-gradient-to-r transition-all duration-500 ease-in-out",
isHomePage ? "from-saseGreen via-saseBlue to-black" : "from-saseBlue via-saseGreen to-white",
submenuOpen
? "pointer-events-auto max-h-screen translate-y-0 transform opacity-100"
: "pointer-events-none max-h-0 -translate-y-2 transform opacity-0",
);

const childUlClasses = cn("flex w-full flex-col items-end space-y-1", isHomePage ? "text-white" : "text-black");

const childButtonClasses = cn("block w-full px-2 py-2 text-right transition-transform duration-300 hover:scale-105");

return (
<li>
{hasChildren ? (
<>
<button
className={cn(
"flex w-full items-center justify-between px-2 py-1 text-left transition-colors duration-300 focus:outline-none",
isHomePage ? "text-white hover:text-[#0f6cb6]" : "text-black hover:text-[#0f6cb6]",
)}
onClick={handleClick}
aria-haspopup="true"
aria-expanded={submenuOpen}
<li className={liClasses}>
<button className={buttonClasses} onClick={handleClick} aria-haspopup="true" aria-expanded={submenuOpen}>
{item.name === "Home" && <Icon icon="oui:home" className="mr-2 h-4 w-4" />}
{item.children && item.children.length > 0 && (
<svg
className={svgClasses}
style={{ transform: submenuOpen ? "rotate(90deg)" : "rotate(0deg)" }}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<span>{item.name}</span>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
{submenuOpen ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
)}
</svg>
</button>
{submenuOpen && (
<ul className="ml-4 mt-1 flex flex-col space-y-1">
{item.children?.map(
(child) =>
child.path && (
<li key={child.name}>
<button
onClick={() => {
router.navigate({ to: child.path });
onClose();
}}
className="block w-full px-2 py-2 text-left transition-colors duration-300 hover:text-[#0f6cb6]"
>
{child.name}
</button>
</li>
),
)}
</ul>
)}
</>
) : (
item.path && (
<button
onClick={() => {
router.navigate({ to: item.path });
onClose();
}}
className="block w-full px-2 py-2 text-left transition-colors duration-300 hover:text-[#0f6cb6]"
>
{item.name}
</button>
)
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
)}
<span>{item.name}</span>
</button>
{item.children && item.children.length > 0 && (
<div className={gradientClasses}>
<ul className={childUlClasses}>
{item.children.map(
(child) =>
child.path && (
<li key={child.name} className="w-full">
<button
onClick={() => {
router.navigate({ to: child.path });
onClose();
}}
className={childButtonClasses}
>
{child.name}
</button>
</li>
),
)}
</ul>
</div>
)}
</li>
);
Expand Down
7 changes: 4 additions & 3 deletions src/client/components/navigation/UserButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import React from "react";
interface UserButtonProps {
isLoggedIn: boolean;
onLogout: () => void;
isHomePage: boolean; // Add the isHomePage prop
}

export const UserButton: React.FC<UserButtonProps> = ({ isLoggedIn, onLogout }) =>
export const UserButton: React.FC<UserButtonProps> = ({ isHomePage, isLoggedIn, onLogout }) =>
isLoggedIn ? (
<Link to="/profile">
<button className="transform rounded-full text-gray-600 duration-300 hover:scale-105 hover:text-[#000000]">
<button className={`transform rounded-full ${isHomePage ? "text-white" : "text-black"} duration-300 hover:scale-105 hover:text-[#000000]`}>
<span className="icon-[qlementine-icons--user-16] h-8 w-8"></span>
</button>
</Link>
) : (
<Link to="/login">
<button
className="inline-block transform rounded-full bg-[#0f6cb6] px-5 py-2 text-white duration-300 hover:scale-105 hover:bg-[#8dc63f] hover:text-[#000000]"
className={`inline-block transform rounded-full border-2 ${isHomePage ? "border-white" : "border-black"} bg-saseBlue px-5 py-1 text-white duration-300 hover:scale-105 hover:bg-saseGreen hover:text-black`}
onClick={onLogout}
>
Log In
Expand Down
2 changes: 1 addition & 1 deletion src/client/routes/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const Route = createFileRoute("/about")({
],
component: () => {
return (
<div className="mt-5 flex min-h-screen flex-col items-center justify-center bg-white font-redhat">
<div className="mt-5 flex min-h-screen flex-col items-center justify-center overflow-x-hidden bg-white font-redhat">
<div className="w-full max-w-7xl px-4 py-8">
<HeaderSection />
<div className="mb-14 flex justify-center">
Expand Down

0 comments on commit a75ed22

Please sign in to comment.