Skip to content

Commit a40695b

Browse files
committed
Add more section to dropdown
1 parent 1e43131 commit a40695b

File tree

8 files changed

+134
-15
lines changed

8 files changed

+134
-15
lines changed

src/assets/state/contactMethods.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,27 @@ interface IContactMethod {
1111
title: ReactNode;
1212
href?: string;
1313
icon: React.ForwardRefExoticComponent<any>;
14+
iconAlt: string;
1415
}
1516

1617
const contactMethods: IContactMethod[] = [
1718
{
1819
title: "Newsletter Signup",
1920
href: "/#sign-up",
20-
icon: NewsIcon
21+
icon: NewsIcon,
22+
iconAlt: "Newspaper icon",
2123
},
2224
{
2325
title: "feedback@commitrocket.com",
2426
href: "mailto:feedback@commitrocket.com",
25-
icon: EnvelopeIcon
27+
icon: EnvelopeIcon,
28+
iconAlt: "Envelope icon"
2629
},
2730
{
2831
title: "Feedback Form",
2932
href: "/contribute#feedback",
30-
icon: PencilSquareIcon
33+
icon: PencilSquareIcon,
34+
iconAlt: "Form icon"
3135
},
3236
{
3337
title: <>
@@ -36,7 +40,8 @@ const contactMethods: IContactMethod[] = [
3640
(Coming Soon™)
3741
</span>
3842
</>,
39-
icon: DiscordIcon
43+
icon: DiscordIcon,
44+
iconAlt: "Discord icon"
4045
}
4146
];
4247

src/components/navigation/Header.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Link from "./Link";
88
import Button from "../controls/Button";
99
import NavLink from "./NavLink";
1010
import useOutsideClick from "@/hooks/useOutsideClick";
11+
import NavDropdown from "./NavDropdown";
1112

1213
const Header = () => {
1314
const router = useRouter();
@@ -37,6 +38,7 @@ const Header = () => {
3738
ref={buttonRef}
3839
className="p-2 ml-auto rounded-full md:hidden"
3940
color="secondary"
41+
aria-label="Expands the header"
4042
aria-expanded={open}
4143
aria-controls="header-items"
4244
onClick={() => setOpen(!open)}
@@ -45,7 +47,7 @@ const Header = () => {
4547
</Button>
4648
<div
4749
ref={itemsContainerRef}
48-
className="absolute flex flex-col bg-fill gap-0 p-4 top-full inset-x-4 rounded-md shadow shadow-primary z-10 data-[expanded='false']:hidden md:data-[expanded='false']:flex md:flex-row md:items-center md:p-0 md:shadow-none md:static md:bg-transparent md:gap-12"
50+
className="absolute flex flex-col bg-fill gap-0 p-4 top-full inset-x-4 rounded-md border-2 border-primary z-10 data-[expanded='false']:hidden md:data-[expanded='false']:flex md:flex-row md:items-center md:p-0 md:border-none md:static md:bg-transparent md:gap-12"
4951
id="header-items"
5052
data-expanded={open}
5153
>
@@ -58,12 +60,14 @@ const Header = () => {
5860
<NavLink href="/blog" currentHref={router.pathname}>
5961
Blog
6062
</NavLink>
61-
<NavLink href="/about" currentHref={router.pathname}>
62-
About
63-
</NavLink>
64-
<NavLink href="/contact" currentHref={router.pathname}>
65-
Contact
66-
</NavLink>
63+
<NavDropdown summary="More">
64+
<NavLink href="/about" currentHref={router.pathname}>
65+
About
66+
</NavLink>
67+
<NavLink href="/contact" currentHref={router.pathname}>
68+
Contact
69+
</NavLink>
70+
</NavDropdown>
6771
</div>
6872
</header>
6973
);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { ReactNode, SyntheticEvent, useCallback, useEffect, useRef, useState } from "react";
2+
import { twMerge } from "tailwind-merge";
3+
import ChevronDownIcon from "@heroicons/react/24/solid/ChevronDownIcon";
4+
5+
import { style as navlinkStyle } from "./NavLink";
6+
import { style as linkStyle } from "./Link";
7+
import useOutsideClick from "@/hooks/useOutsideClick";
8+
import useHover from "@/hooks/useHover";
9+
10+
export interface NavDropdownProps {
11+
summary: ReactNode;
12+
children: ReactNode;
13+
}
14+
15+
const SUMMARY_CLASSNAME = twMerge(
16+
navlinkStyle() + " " + linkStyle({ color: "primary" }),
17+
"flex items-center"
18+
);
19+
20+
const NavDropdown = ({ summary, children }: NavDropdownProps) => {
21+
const summaryRef = useRef<HTMLDetailsElement>(null);
22+
const containerRef = useRef<HTMLDivElement>(null);
23+
24+
const [open, setOpen] = useState<boolean | undefined>(false);
25+
26+
const handleToggle = useCallback((e: SyntheticEvent<HTMLDetailsElement, Event>) => {
27+
const target = e.target as HTMLDetailsElement;
28+
setOpen(target.open);
29+
}, []);
30+
31+
const isSummaryHovering = useHover(summaryRef);
32+
const isContainerHovering = useHover(containerRef);
33+
34+
useEffect(() => {
35+
const isHovering = isSummaryHovering || isContainerHovering;
36+
if (!isHovering) {
37+
setOpen(false);
38+
return;
39+
}
40+
41+
setOpen(true);
42+
}, [isSummaryHovering, isContainerHovering]);
43+
44+
useOutsideClick([summaryRef, containerRef], () => {
45+
setOpen(false);
46+
});
47+
48+
return (
49+
<details className="relative group/details" open={open} onToggle={handleToggle}>
50+
<summary ref={summaryRef} className={SUMMARY_CLASSNAME}>
51+
{summary}
52+
<ChevronDownIcon
53+
className="w-5 h-5 data-[open='true']:rotate-180 transition-all"
54+
data-open={open}
55+
/>
56+
</summary>
57+
<div ref={containerRef} className="absolute inset-x-0 flex flex-col gap-2 px-4 py-2 border-2 rounded-md group/dropdown border-primary bg-fill md:w-fit" data-in-group>
58+
{children}
59+
</div>
60+
</details>
61+
);
62+
};
63+
64+
export default NavDropdown;

src/components/navigation/NavLink.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { cva } from "class-variance-authority";
12
import { ReactNode } from "react";
23
import Link from "./Link";
34

@@ -7,11 +8,19 @@ export interface NavLinkProps {
78
children: ReactNode;
89
}
910

11+
export const style = cva(`
12+
text-lg font-bold py-2
13+
aria-[current='page']:text-primary-light
14+
group-data-[in-group='true']/dropdown:text-base group-data-[in-group='true']/dropdown:py-0
15+
`);
16+
17+
const CLASSNAME = style();
18+
1019
const NavLink = ({ href, currentHref, children }: NavLinkProps) => {
1120
const active = currentHref === href;
1221
return (
1322
<Link
14-
className="text-lg font-bold py-2 border-y border-primary hover:border-primary-dark aria-[current='page']:text-primary-light aria-[current='page']:border-primary first-of-type:border-t-2 last-of-type:border-b-2 md:border-0 md:first-of-type:border-t-0 md:last-of-type:border-b-0"
23+
className={CLASSNAME}
1524
aria-current={active ? "page" : undefined}
1625
color="primary"
1726
href={href}
@@ -22,4 +31,4 @@ const NavLink = ({ href, currentHref, children }: NavLinkProps) => {
2231
};
2332

2433

25-
export default NavLink
34+
export default NavLink;

src/hooks/useHover.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { MutableRefObject, useEffect, useRef, RefObject, useState } from "react";
2+
3+
const useHover = (ref: (RefObject<Element> | MutableRefObject<Element>)) => {
4+
5+
const [isHovering, setIsHovering] = useState(false);
6+
7+
useEffect(() => {
8+
if (!ref.current) return;
9+
10+
const handleMouseOver = () => setIsHovering(true);
11+
const handleMouseOut = () => setIsHovering(false);
12+
13+
ref.current.addEventListener("mouseover", handleMouseOver);
14+
ref.current.addEventListener("mouseout", handleMouseOut);
15+
16+
return () => {
17+
if (!ref.current) return;
18+
ref.current.removeEventListener("mouseover", handleMouseOver);
19+
ref.current.removeEventListener("mouseout", handleMouseOut);
20+
};
21+
}, [ref.current, setIsHovering]);
22+
23+
24+
return isHovering;
25+
};
26+
27+
export default useHover;

src/hooks/useOutsideClick.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const useOutsideClick = (refs: (RefObject<Element> | MutableRefObject<Element>)[
1010
if (!event.target || !ref.current) return true;
1111
return ref.current.contains(event.target as Element);
1212
});
13+
1314
if (isContained) return;
1415

1516
handlerRef.current(event);

src/pages/contact.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ const ContactPage: Page = ({ }) => {
2323
className={`flex flex-col items-center ${method.href ? "" : style({ color: "primary", underline: true })}`}
2424
//@ts-ignore
2525
href={method.href}
26-
scroll={true}
27-
underline={Boolean(method.href)}
26+
scroll={method.href ? true : undefined}
27+
underline={method.href ? true : undefined}
2828
>
2929
<method.icon
3030
className="w-16 h-16 md:w-24 md:h-24"
31+
aria-label={method.iconAlt}
3132
/>
3233
<p className="font-bold text-center md:text-lg">
3334
{method.title}

src/styles/main.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ html {
1313
@apply bg-fill text-fill-contrast;
1414
}
1515

16+
details>summary {
17+
list-style: none;
18+
}
19+
20+
details>summary::-webkit-details-marker {
21+
display: none;
22+
}
23+
1624
div.swal2-popup.swal2-toast {
1725
@apply p-3 shadow shadow-black/25 bg-white/50 backdrop-blur-sm;
1826
}

0 commit comments

Comments
 (0)