Skip to content

Commit

Permalink
SUM-45 | @rebeccahongsf | style header
Browse files Browse the repository at this point in the history
  • Loading branch information
rebeccahongsf committed Apr 19, 2024
1 parent 8b1343f commit cdce8e6
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 90 deletions.
4 changes: 1 addition & 3 deletions src/components/global/page-header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import SiteSearchForm from "@components/search/site-search-form";
import MainMenu from "@components/menu/main-menu";
import GlobalMessage from "@components/config-pages/global-message";
import Lockup from "@components/elements/lockup/lockup";
Expand Down Expand Up @@ -30,11 +29,10 @@ const PageHeader = async () => {
</div>
</div>
{globalMessageConfig && <GlobalMessage {...globalMessageConfig}/>}
<div className="relative shadow">
<div className="relative bg-fog-light">
<div className="centered min-h-50 pr-24 lg:pr-0">
<div className="flex w-full justify-between">
<Lockup {...siteSettingsConfig} {...lockupSettingsConfig}/>
<SiteSearchForm className="hidden lg:block"/>
</div>
</div>

Expand Down
253 changes: 168 additions & 85 deletions src/components/menu/main-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,81 +4,156 @@ import Link from "@components/elements/link";
import SiteSearchForm from "@components/search/site-search-form";
import useActiveTrail from "@lib/hooks/useActiveTrail";
import useOutsideClick from "@lib/hooks/useOutsideClick";
import {ChevronDownIcon} from "@heroicons/react/20/solid";
import {MenuItem as MenuItemType} from "@lib/gql/__generated__/drupal.d";
import {clsx} from "clsx";
import {useBoolean, useEventListener} from "usehooks-ts";
import {useCallback, useEffect, useId, useLayoutEffect, useRef, useState} from "react";
import {usePathname} from "next/navigation";
import {
ChevronDownIcon,
MagnifyingGlassIcon,
} from "@heroicons/react/20/solid";
import { MenuItem as MenuItemType } from "@lib/gql/__generated__/drupal.d";
import { clsx } from "clsx";
import { useBoolean, useEventListener } from "usehooks-ts";
import {
useCallback,
useEffect,
useId,
useLayoutEffect,
useRef,
useState,
} from "react";
import { usePathname } from "next/navigation";

const menuLevelsToShow = 2;

type Props = {
/**
* Array of nested menu items.
*/
menuItems: MenuItemType[]
}
menuItems: MenuItemType[];
};

const MainMenu = ({menuItems}: Props) => {
const buttonRef = useRef<HTMLButtonElement>(null)
const MainMenu = ({ menuItems }: Props) => {
const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
const navId = useId();

const {value: menuOpen, setFalse: closeMenu, toggle: toggleMenu} = useBoolean(false)
const browserUrl = usePathname()
const {
value: menuOpen,
setFalse: closeMenu,
toggle: toggleMenu,
} = useBoolean(false);
const browserUrl = usePathname();
const activeTrail = useActiveTrail(menuItems, usePathname() || "");

useOutsideClick(menuRef, closeMenu);

const handleEscape = useCallback((event: KeyboardEvent) => {
if (event.key !== "Escape" || !menuOpen) return;
const handleEscape = useCallback(
(event: KeyboardEvent) => {
if (event.key !== "Escape" || !menuOpen) return;

closeMenu()
buttonRef.current?.focus();
}, [menuOpen, closeMenu]);
closeMenu();
buttonRef.current?.focus();
},
[menuOpen, closeMenu]
);

useEffect(() => closeMenu(), [browserUrl, closeMenu]);
useEventListener("keydown", handleEscape, menuRef);

return (
<nav id={navId} aria-label="Main Navigation" className="lg:centered" ref={menuRef}>
<button ref={buttonRef} className="flex flex-col items-center lg:hidden absolute top-5 right-10 group" onClick={toggleMenu} aria-expanded={menuOpen} aria-labelledby={navId}>
<nav
id={navId}
aria-label="Main Navigation"
className="lg:centered"
ref={menuRef}
>
<button
ref={buttonRef}
className="flex flex-col items-center lg:hidden absolute top-5 right-10 group"
onClick={toggleMenu}
aria-expanded={menuOpen}
aria-labelledby={navId}
>
<span className="flex flex-col justify-center items-center w-[30px] h-[30px]">
<span className={clsx("bg-black-true block transition-all duration-300 ease-out h-[3px] w-full rounded-sm", {"rotate-45 translate-y-4": menuOpen, "-translate-y-0.5": !menuOpen})}/>
<span className={clsx("bg-black-true block transition-all duration-300 ease-out h-[3px] w-full rounded-sm my-3",{"opacity-0" :menuOpen , "opacity-100": !menuOpen})}/>
<span className={clsx("bg-black-true block transition-all duration-300 ease-out h-[3px] w-full rounded-sm", {"-rotate-45 -translate-y-4": menuOpen, "translate-y-0.5": !menuOpen})}/>
<span
className={clsx(
"bg-black-true block transition-all duration-300 ease-out h-[3px] w-full rounded-sm",
{
"rotate-45 translate-y-4": menuOpen,
"-translate-y-0.5": !menuOpen,
}
)}
/>
<span
className={clsx(
"bg-black-true block transition-all duration-300 ease-out h-[3px] w-full rounded-sm my-3",
{ "opacity-0": menuOpen, "opacity-100": !menuOpen }
)}
/>
<span
className={clsx(
"bg-black-true block transition-all duration-300 ease-out h-[3px] w-full rounded-sm",
{
"-rotate-45 -translate-y-4": menuOpen,
"translate-y-0.5": !menuOpen,
}
)}
/>
</span>
<span className="group-hocus:underline" aria-hidden>
{menuOpen ? "Close" : "Menu"}
</span>
<span className="group-hocus:underline" aria-hidden>{menuOpen ? "Close" : "Menu"}</span>
</button>

<div
className={(menuOpen ? "block" : "hidden") + " lg:block bg-black lg:bg-transparent absolute top-100 lg:relative z-10 w-full"}>
<SiteSearchForm className="px-10 lg:hidden"/>
className={
(menuOpen ? "block" : "hidden") +
" lg:flex lg:justify-end lg:items-center bg-black lg:bg-transparent absolute top-100 lg:relative z-10 w-full"
}
>
<SiteSearchForm className="px-10 lg:hidden" />
<ul className="list-unstyled lg:flex lg:justify-end flex-wrap m-0 p-0">
{menuItems.map(item =>
<MenuItem key={item.id} {...item} activeTrail={activeTrail} level={0}/>
)}
{menuItems.map((item) => (
<MenuItem
key={item.id}
{...item}
activeTrail={activeTrail}
level={0}
/>
))}
<button>
<MagnifyingGlassIcon width={25} className="text-archway-dark" />
</button>
<SiteSearchForm className="hidden lg:block" />
</ul>
</div>
</nav>
)
}
);
};

type MenuItemProps = MenuItemType & {
activeTrail: string[]
level: number
}
activeTrail: string[];
level: number;
};

const MenuItem = ({id, url, title, activeTrail, children, level}: MenuItemProps) => {
const MenuItem = ({
id,
url,
title,
activeTrail,
children,
level,
}: MenuItemProps) => {
const linkId = useId();
const menuItemRef = useRef<HTMLLIElement>(null);
const belowListRef = useRef<HTMLUListElement>(null);

const [positionRight, setPositionRight] = useState<boolean>(true)
const buttonRef = useRef<HTMLButtonElement>(null)
const {value: submenuOpen, setFalse: closeSubmenu, toggle: toggleSubmenu} = useBoolean(false)
const browserUrl = usePathname()
const [positionRight, setPositionRight] = useState<boolean>(true);
const buttonRef = useRef<HTMLButtonElement>(null);
const {
value: submenuOpen,
setFalse: closeSubmenu,
toggle: toggleSubmenu,
} = useBoolean(false);
const browserUrl = usePathname();

useOutsideClick(menuItemRef, closeSubmenu);

Expand All @@ -87,63 +162,73 @@ const MenuItem = ({id, url, title, activeTrail, children, level}: MenuItemProps)

useLayoutEffect(() => {
// If the right side of the submenu is not visible, set the position to be on the left of the menu item.
const {x, width} = belowListRef.current?.getBoundingClientRect() || {x: 0, width: 0}
const { x, width } = belowListRef.current?.getBoundingClientRect() || {
x: 0,
width: 0,
};
if (x + width > window.innerWidth) setPositionRight(false);
}, [submenuOpen])
}, [submenuOpen]);

// If the user presses escape on the keyboard, close the submenus.
const handleEscape = useCallback((event: KeyboardEvent) => {
if (event.key !== "Escape" || !submenuOpen) return;
const handleEscape = useCallback(
(event: KeyboardEvent) => {
if (event.key !== "Escape" || !submenuOpen) return;

closeSubmenu()
if (level === 0) buttonRef.current?.focus();
}, [level, submenuOpen, closeSubmenu]);
closeSubmenu();
if (level === 0) buttonRef.current?.focus();
},
[level, submenuOpen, closeSubmenu]
);

useEventListener("keydown", handleEscape, menuItemRef)
useEventListener("keydown", handleEscape, menuItemRef);

// List out the specific classes so tailwind will include them. Dynamic classes values don"t get compiled.
const zIndexes = ["z-[1]", "z-[2]", "z-[3]", "z-[4]", "z-[5]"]
const leftPadding = ["pl-10", "pl-20", "pl-28", "pl-48"]
const zIndexes = ["z-[1]", "z-[2]", "z-[3]", "z-[4]", "z-[5]"];
const leftPadding = ["pl-10", "pl-20", "pl-28", "pl-48"];

// The last item in the current trail would be the current item id if the user is on that page.
const isCurrent = activeTrail.at(-1) === id;
const inTrail = activeTrail.includes(id) && !isCurrent;

const linkStyles = clsx(
"w-full relative inline-block text-white lg:text-digital-red hocus:text-white lg:hocus:text-black no-underline hocus:underline py-5 lg:pl-0 border-l-[6px]",
"w-full relative inline-block text-white lg:text-black hocus:text-white lg:hocus:text-black no-underline py-5 border-b-[4px] hocus:border-spirited-light",
leftPadding[level],
// Top menu item styles.
{
"lg:border-l-0 lg:border-b-[6px] ml-5 lg:ml-0 lg:pb-2": level === 0,
"lg:border-l-0 lg:border-b-[4px] mx-5 lg:pb-10": level === 0,
"border-digital-red lg:border-black": level === 0 && isCurrent,
"border-transparent lg:border-foggy-dark": level === 0 && !isCurrent && inTrail,
"border-transparent lg:border-spirited-light":
level === 0 && !isCurrent && inTrail,
"border-transparent": level === 0 && !isCurrent && !inTrail,
},
// Child menu item styles.
{
"ml-5 lg:ml-0 lg:pl-5": level !== 0,
"border-digital-red": level !== 0 && isCurrent,
"border-transparent": level !== 0 && !isCurrent
"border-spirited-light": level !== 0 && isCurrent,
"border-transparent": level !== 0 && !isCurrent,
}
)
);

const subMenuStyles = clsx(
"list-unstyled w-full min-w-[300px] lg:bg-white lg:shadow-2xl px-0 lg:absolute",
"list-unstyled w-full min-w-[300px] lg:bg-fog-light lg:shadow-2xl lg:absolute lg:rounded-[25px] lg:px-12 lg:py-4",
zIndexes[level],
{
"lg:top-full lg:right-0": level === 0,
"lg:top-0": level !== 0,
"lg:left-full": level !== 0 && positionRight,
"lg:right-full": level !== 0 && !positionRight,
"block": submenuOpen,
"hidden": !submenuOpen,
block: submenuOpen,
hidden: !submenuOpen,
}
)
);

return (
<li
ref={menuItemRef}
className={clsx("m-0 py-2 lg:py-0 relative border-b first:border-t last:border-0 border-cool-grey lg:border-black-20 lg:relative lg:mr-5 last:lg:mr-0", {"lg:border-b-0 first:border-t-0": level === 0})}
className={clsx(
"m-0 py-2 lg:py-0 relative border-b last:border-0 border-spirited-light lg:relative",
{ "lg:border-b-0 first:border-t-0": level === 0 }
)}
>
<div className="flex items-center justify-between lg:justify-end">
<Link
Expand All @@ -155,41 +240,39 @@ const MenuItem = ({id, url, title, activeTrail, children, level}: MenuItemProps)
{title}
</Link>

{(children.length > 0 && level < menuLevelsToShow) &&
<>
{level === 0 && <div className="block ml-5 w-[1px] h-[25px] mb-[6px] bg-archway-light shrink-0"/>}
<button
ref={buttonRef}
className="shrink-0 relative right-10 lg:right-0 text-white lg:text-digital-red bg-digital-red lg:bg-transparent rounded-full lg:rounded-none group border-b border-transparent hocus:border-black hocus:bg-white"
onClick={toggleSubmenu}
aria-expanded={submenuOpen}
aria-labelledby={linkId}
>
<ChevronDownIcon
height={35}
className={clsx("transition group-hocus:scale-125 group-hocus:text-black ease-in-out duration-150", {"rotate-180": submenuOpen})}
/>
</button>

</>
}

{children.length > 0 && level < menuLevelsToShow && (
<button
ref={buttonRef}
className="shrink-0 relative right-10 lg:right-0 text-white lg:text-digital-red bg-digital-red lg:bg-transparent rounded-full lg:rounded-none group"
onClick={toggleSubmenu}
aria-expanded={submenuOpen}
aria-labelledby={linkId}
>
<ChevronDownIcon
height={35}
className={clsx(
"transition group-hocus:scale-125 group-hocus:text-black ease-in-out duration-150",
{ "rotate-180": submenuOpen }
)}
/>
</button>
)}
</div>

{(children.length > 0 && level < menuLevelsToShow) &&
{children.length > 0 && level < menuLevelsToShow && (
<ul className={subMenuStyles} ref={belowListRef}>
{children.map(item =>
{children.map((item) => (
<MenuItem
key={item.id}
{...item}
level={level + 1}
activeTrail={activeTrail}
/>
)}
))}
</ul>
}
)}
</li>
)
}
);
};

export default MainMenu;
export default MainMenu;
4 changes: 2 additions & 2 deletions src/components/search/site-search-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const SiteSearchForm = ({...props}: HTMLAttributes<HTMLFormElement>) => {
const inputId = useId();
return (
<form aria-label="Site Search" action="/search" {...props}>
<div className="relative mt-10">
<div className="relative">
<label htmlFor={inputId} className="sr-only">
Search this site
</label>
Expand All @@ -18,7 +18,7 @@ const SiteSearchForm = ({...props}: HTMLAttributes<HTMLFormElement>) => {
required
/>
<button type="submit" className="absolute top-2 right-5">
<MagnifyingGlassIcon width={25} className="text-digital-red"/>
<MagnifyingGlassIcon width={25} className="text-archway-dark"/>
<span className="sr-only">Submit Search</span>
</button>
</div>
Expand Down

0 comments on commit cdce8e6

Please sign in to comment.