Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add one more level to header dropdown #107

Merged
merged 17 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions lib/components/buttons/popover.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { useEffect, useState } from "react";
import {
VscChevronRight,
VscChevronLeft,
VscChevronUp,
VscChevronDown,
} from "react-icons/vsc";

const AVAIL_DIRECTIONS = ["right", "left", "top", "bottom"];

function PopoutMenu({ title, children, direction = "right", className }) {
direction = direction.toLowerCase();

const [isOpen, setIsOpen] = useState(false);

// TODO: This does not handle clicking other popout buttons
function handleClickOutside() {
// Handle when a user clicks outside the popout menu
setIsOpen(false);
}

useEffect(() => {
if (isOpen) {
document.addEventListener("click", handleClickOutside);
} else {
document.removeEventListener("click", handleClickOutside);
}

return () => {
document.removeEventListener("click", handleClickOutside);
};
}, [isOpen]);

if (!AVAIL_DIRECTIONS.includes(direction)) {
throw new Error(
`Invalid direction in component <Popover direction="${direction}". Valid directions are: ${AVAIL_DIRECTIONS.join(
", "
)}`
);
}

// Map direction to position classes
const directionClasses = {
right:
"gw-left-full gw-top-1/2 gw-transform gw--translate-y-1/2 gw-translate-x-2",
left: "gw-right-full gw-top-1/2 gw-transform gw--translate-y-1/2 gw--translate-x-2",
top: "gw-bottom-full gw-left-1/2 gw-transform gw--translate-x-1/2 gw--translate-y-2",
bottom:
"gw-top-full gw-left-1/2 gw-transform gw--translate-x-1/2 gw-translate-y-2",
};

/// Map the icons to their appropriate directions
const ChevronIcon = {
right: VscChevronRight,
left: VscChevronLeft,
top: VscChevronUp,
bottom: VscChevronDown,
}[direction];

// Flip the direction for closure
const ChevronIconOpposite = {
right: VscChevronLeft,
left: VscChevronRight,
top: VscChevronDown,
bottom: VscChevronUp,
}[direction];

return (
<Popover
name="gw-popout-menu"
className={`gw-relative gw-cursor-not-allowed gw-select-none ${className}`}
onClose={() => setIsOpen(false)}
>
<PopoverButton
className="gw-inline-flex gw-w-full gw-items-center gw-justify-between gw-text-sm gw-leading-6 gw-ps-1 focus:gw-outline-none"
onClick={() => {
setIsOpen(!isOpen);
}}
>
<span>{title}</span>
{isOpen ? (
<ChevronIconOpposite
aria-hidden="true"
className="gw-h-5 gw-w-5"
size={16}
/>
) : (
<ChevronIcon aria-hidden="true" className="gw-h-5 gw-w-5" size={12} />
)}
</PopoverButton>

<PopoverPanel
transition="true"
className={`gw-absolute gw-ms-2 gw-pt-1 ${directionClasses[direction]} gw-z-10 gw-mt-2 gw-w-56 gw-shrink gw-rounded-xl gw-bg-white gw-text-sm gw-leading-6 gw-text-gray-900 gw-shadow-lg gw-ring-1 gw-ring-gray-900/5`}
>
{children}
</PopoverPanel>
</Popover>
);
}

export default PopoutMenu;
export { PopoutMenu };
37 changes: 37 additions & 0 deletions lib/components/form/dropdown.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useState } from "react";

export default function Dropdown({
label,
options,
labelClassName,
className,
...props
}) {
const [selectedValue, setSelectedValue] = useState(options[0].props.value);
if (!options || options.length === 0) {
throw new Error(`Dropdown ${label} must have at least one option`);
}
return (
<>
<label
htmlFor={label + "-id"}
className={`block text-sm font-medium leading-6 text-gray-900 ${labelClassName}`}
>
{label}
</label>
<select
id={label ? label + "-id" : undefined}
name={label ? label + "-name" : undefined}
onChange={(e) => {
setSelectedValue(e.target.value);
props.onChange(e);
}}
className={`mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 ${className}`}
value={selectedValue || options[0].props.value}
{...props}
>
{options}
</select>
</>
);
}
15 changes: 10 additions & 5 deletions lib/components/layout/usace-box.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { useMemo } from "react";
import gwMerge from "../../gw-merge";

function UsaceBox({ children, className, title, customTitle }) {
function UsaceBox({
children,
className,
title,
customTitle,
id,
propRef: ref,
}) {
const CustomTitle = customTitle;
const boxClass = "gw-mb-10 gw-relative";
const beforeClass =
Expand All @@ -13,10 +20,8 @@ function UsaceBox({ children, className, title, customTitle }) {
return gwMerge(boxClass, beforeClass, afterClass, className);
}, [className]);
return (
<div className={usaceBoxClass}>
<h2
className="gw-font-bold gw-mb-5 gw-text-[1.3rem]"
>
<div ref={ref} className={usaceBoxClass} id={id}>
<h2 className="gw-font-bold gw-mb-5 gw-text-[1.3rem]">
{title}
{CustomTitle && <CustomTitle />}
</h2>
Expand Down
47 changes: 39 additions & 8 deletions lib/composite/header/navbar-links.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,51 @@ function NavbarLinkItem({ link, ...props }) {
<Menu.Items
static
as="ul"
className="gw-absolute gw-left-0 gw-top-13 gw-width-auto gw-bg-nav-dark-gray !gw-z-20"
className="gw-absolute gw-left-0 gw-top-13 gw-bg-nav-dark-gray !gw-z-20 gw-w-max gw-p-0" // Removed padding and added gw-p-0
>
{link.children.map((child) => {
return (
<Menu.Item key={child.id || child.text} as={Fragment}>
{link.children.map((child) => (
<Menu.Item key={child.id || child.text} as={Fragment}>
{child.children ? (
<div className="gw-relative gw-group">
<a
href={child.href}
className="after:gw-content-['►'] after:gw-ml-2 after:gw-text-[10px] gw-block gw-text-sm gw-border-b gw-border-nav-black gw-bg-nav-dark-gray gw-hover:gw-bg-nav-translucent-gray gw-text-nav-light-gray gw-hover:gw-text-white gw-text-nowrap gw-font-semibold gw-px-3 gw-py-2 gw-bg-none"
>
{child.text}
</a>
<Menu.Items
static
as="ul"
className="gw-absolute gw-left-full gw-top-0 gw-bg-nav-dark-gray !gw-z-30 gw-w-max gw-p-0 gw-shadow-lg gw-hidden group-hover:gw-block" // Removed padding and added gw-p-0
>
{child.children.map((grandChild) => {
if (!grandChild.children)
console.warn(
"Header items can only be 2 levels deep. Please reorganize your header links. This helps to avoid CSS issues."
);
return (
<Menu.Item key={grandChild.id || grandChild.text}>
<a
href={grandChild.href}
className="gw-block gw-text-sm gw-border-b gw-border-nav-black gw-bg-nav-dark-gray gw-hover:gw-bg-nav-translucent-gray gw-text-nav-light-gray gw-hover:gw-text-white gw-text-nowrap gw-font-semibold gw-px-3 gw-py-2 gw-bg-none"
>
{grandChild.text}
</a>
</Menu.Item>
);
})}
</Menu.Items>
</div>
) : (
<a
href={child.href}
className="gw-block gw-text-sm gw-border-b gw-border-nav-black gw-bg-nav-dark-gray gw-hover:gw-bg-nav-translucent-gray gw-text-nav-light-gray gw-hover:gw-text-white gw-text-nowrap gw-font-semibold gw-px-[16px] gw-py-[8px] gw-bg-none"
className="gw-block gw-text-sm gw-border-b gw-border-nav-black gw-bg-nav-dark-gray gw-hover:gw-bg-nav-translucent-gray gw-text-nav-light-gray gw-hover:gw-text-white gw-text-nowrap gw-font-semibold gw-px-3 gw-py-2 gw-bg-none"
>
{child.text}
</a>
</Menu.Item>
);
})}
)}
</Menu.Item>
))}
</Menu.Items>
)}
</Menu>
Expand Down
Loading
Loading