Skip to content
Open
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
130 changes: 86 additions & 44 deletions frontend/src/plugins/layout/NavigationMenuPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import React from "react";
import { z } from "zod";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Tooltip } from "@/components/ui/tooltip";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { renderHTML } from "@/plugins/core/RenderHTML";
import { cn } from "@/utils/cn";
import { appendQueryParams } from "@/utils/urls";
Expand Down Expand Up @@ -77,6 +81,22 @@ const NavMenuComponent = ({
items,
orientation,
}: PropsWithChildren<NavMenuComponentProps>): JSX.Element => {
const [openMenu, setOpenMenu] = React.useState<string | null>(null);
const timeoutRef = React.useRef<NodeJS.Timeout>(null);

const handleMouseEnter = (label: string) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setOpenMenu(label);
};

const handleMouseLeave = () => {
timeoutRef.current = setTimeout(() => {
setOpenMenu(null);
}, 200);
};

const maybeWithTooltip = (
component: JSX.Element,
description?: string | null,
Expand Down Expand Up @@ -109,30 +129,54 @@ const NavMenuComponent = ({
const renderMenuItem = (item: MenuItem | MenuItemGroup) => {
if ("items" in item) {
return orientation === "horizontal" ? (
<NavigationMenu orientation="horizontal" key={item.label}>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>
<NavigationMenuItem key={item.label}>
<Popover
open={openMenu === item.label}
onOpenChange={(open) => setOpenMenu(open ? item.label : null)}
>
<PopoverTrigger
asChild={true}
onMouseEnter={() => handleMouseEnter(item.label)}
onMouseLeave={handleMouseLeave}
>
<button
className={cn(
navigationMenuTriggerStyle(),
"flex items-center",
)}
>
{renderHTML({ html: item.label })}
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{item.items.map((subItem) => (
<ListItem
key={subItem.label}
label={subItem.label}
href={preserveQueryParams(subItem.href)}
target={target(subItem.href)}
>
{subItem.description &&
renderHTML({ html: subItem.description })}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<ChevronDownIcon
className={cn(
"relative top-px ml-1 h-3 w-3 transition duration-300",
openMenu === item.label && "rotate-180",
)}
aria-hidden="true"
/>
</button>
</PopoverTrigger>
<PopoverContent
className="w-auto p-0"
align="start"
onMouseEnter={() => handleMouseEnter(item.label)}
onMouseLeave={handleMouseLeave}
>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{item.items.map((subItem) => (
<ListItem
key={subItem.label}
label={subItem.label}
href={preserveQueryParams(subItem.href)}
target={target(subItem.href)}
>
{subItem.description &&
renderHTML({ html: subItem.description })}
</ListItem>
))}
</ul>
</PopoverContent>
</Popover>
</NavigationMenuItem>
) : (
<NavigationMenuItem key={item.label}>
<div
Expand Down Expand Up @@ -203,25 +247,23 @@ const ListItem = React.forwardRef<
>(({ className, label, children, ...props }, ref) => {
return (
<li>
<NavigationMenuLink asChild={true}>
<a
ref={ref}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-hidden transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className,
)}
{...props}
>
<div className="text-base font-medium leading-none">
{renderHTML({ html: label })}
</div>
{children && (
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
{children}
</p>
)}
</a>
</NavigationMenuLink>
<a
ref={ref}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-hidden transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className,
)}
{...props}
>
<div className="text-base font-medium leading-none">
{renderHTML({ html: label })}
</div>
{children && (
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
{children}
</p>
)}
</a>
</li>
);
});
Expand Down
Loading