Browse Source
✨ Implemented NavigationItem component
✨ Implemented NavigationItem component
🚀 Added the NavigationItem component to handle navigation 🎨 Improved code structure and readability 🛠 Fixed minor issues and added comments 🔗 Ready for further development!main
John Doe
1 year ago
1 changed files with 287 additions and 0 deletions
@ -0,0 +1,287 @@ |
|||||
|
"use client"; |
||||
|
|
||||
|
import { PathName } from "@/routers/types"; |
||||
|
import { Popover, Transition } from "@headlessui/react"; |
||||
|
import { ChevronDownIcon } from "@heroicons/react/24/solid"; |
||||
|
import Image from "next/image"; |
||||
|
import Link from "next/link"; |
||||
|
import { usePathname } from "next/navigation"; |
||||
|
import React, { FC, Fragment, useEffect, useState } from "react"; |
||||
|
|
||||
|
// <--- NavItemType --->
|
||||
|
export interface MegamenuItem { |
||||
|
id: string; |
||||
|
image: string; |
||||
|
title: string; |
||||
|
items: NavItemType[]; |
||||
|
} |
||||
|
export interface NavItemType { |
||||
|
id: string; |
||||
|
name: string; |
||||
|
isNew?: boolean; |
||||
|
href: PathName; |
||||
|
targetBlank?: boolean; |
||||
|
children?: NavItemType[]; |
||||
|
megaMenu?: MegamenuItem[]; |
||||
|
type?: "dropdown" | "megaMenu" | "none"; |
||||
|
} |
||||
|
|
||||
|
export interface NavigationItemProps { |
||||
|
menuItem: NavItemType; |
||||
|
} |
||||
|
|
||||
|
type NavigationItemWithRouterProps = NavigationItemProps; |
||||
|
|
||||
|
const NavigationItem: FC<NavigationItemWithRouterProps> = ({ menuItem }) => { |
||||
|
const [menuCurrentHovers, setMenuCurrentHovers] = useState<string[]>([]); |
||||
|
|
||||
|
// CLOSE ALL MENU OPENING WHEN CHANGE HISTORY
|
||||
|
const locationPathName = usePathname(); |
||||
|
useEffect(() => { |
||||
|
setMenuCurrentHovers([]); |
||||
|
}, [locationPathName]); |
||||
|
|
||||
|
const onMouseEnterMenu = (id: string) => { |
||||
|
setMenuCurrentHovers((state) => [...state, id]); |
||||
|
}; |
||||
|
|
||||
|
const onMouseLeaveMenu = (id: string) => { |
||||
|
setMenuCurrentHovers((state) => { |
||||
|
return state.filter((item, index) => { |
||||
|
return item !== id && index < state.indexOf(id); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// ===================== MENU MEGAMENU =====================
|
||||
|
const renderMegaMenu = (menu: NavItemType) => { |
||||
|
const isHover = menuCurrentHovers.includes(menu.id); |
||||
|
|
||||
|
const isFull = menu.megaMenu && menu.megaMenu?.length > 3; |
||||
|
const classPopover = isFull |
||||
|
? "menu-megamenu--large" |
||||
|
: "menu-megamenu--small relative"; |
||||
|
const classPanel = isFull ? "left-0" : "-translate-x-1/2 left-1/2"; |
||||
|
|
||||
|
return ( |
||||
|
<Popover |
||||
|
as="li" |
||||
|
className={`menu-item flex items-center menu-megamenu ${classPopover}`} |
||||
|
onMouseEnter={() => onMouseEnterMenu(menu.id)} |
||||
|
onMouseLeave={() => onMouseLeaveMenu(menu.id)} |
||||
|
> |
||||
|
{() => ( |
||||
|
<> |
||||
|
<div>{renderMainItem(menu)}</div> |
||||
|
<Transition |
||||
|
as={Fragment} |
||||
|
show={isHover} |
||||
|
enter="transition ease-out duration-150" |
||||
|
enterFrom="opacity-0 translate-y-1" |
||||
|
enterTo="opacity-100 translate-y-0" |
||||
|
leave="transition ease-in duration-150" |
||||
|
leaveFrom="opacity-100 translate-y-0" |
||||
|
leaveTo="opacity-0 translate-y-1" |
||||
|
> |
||||
|
<Popover.Panel |
||||
|
static |
||||
|
className={`will-change-transform sub-menu absolute top-full transform z-10 w-screen max-w-sm px-4 sm:px-0 lg:max-w-max ${classPanel}`} |
||||
|
> |
||||
|
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black dark:ring-white ring-opacity-5 dark:ring-opacity-10 text-sm"> |
||||
|
<div |
||||
|
className={`relative bg-white dark:bg-neutral-900 px-3 py-6 grid gap-1 grid-cols-${menu.megaMenu?.length}`} |
||||
|
> |
||||
|
{menu.megaMenu?.map((item) => ( |
||||
|
<div key={item.id}> |
||||
|
<div className="px-2"> |
||||
|
<div className="w-36 h-24 rounded-lg overflow-hidden relative flex"> |
||||
|
<Image alt="" src={item.image} fill sizes="200px" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p className="font-medium text-neutral-900 dark:text-neutral-200 py-1 px-2 my-2"> |
||||
|
{item.title} |
||||
|
</p> |
||||
|
<ul className="grid space-y-1"> |
||||
|
{item.items.map(renderMegaMenuNavlink)} |
||||
|
</ul> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</div> |
||||
|
</Popover.Panel> |
||||
|
</Transition> |
||||
|
</> |
||||
|
)} |
||||
|
</Popover> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const renderMegaMenuNavlink = (item: NavItemType) => { |
||||
|
return ( |
||||
|
<li key={item.id}> |
||||
|
<Link |
||||
|
rel="noopener noreferrer" |
||||
|
className="inline-flex items-center py-1 px-2 rounded hover:text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200 font-normal text-neutral-6000 dark:text-neutral-300" |
||||
|
href={item.href || ""} |
||||
|
> |
||||
|
{item.name} |
||||
|
</Link> |
||||
|
</li> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
// ===================== MENU DROPDOW =====================
|
||||
|
const renderDropdownMenu = (menuDropdown: NavItemType) => { |
||||
|
const isHover = menuCurrentHovers.includes(menuDropdown.id); |
||||
|
return ( |
||||
|
<Popover |
||||
|
as="li" |
||||
|
className={`menu-item flex items-center menu-dropdown relative ${ |
||||
|
menuDropdown.isNew ? "menuIsNew_lv1" : "" |
||||
|
}`}
|
||||
|
onMouseEnter={() => onMouseEnterMenu(menuDropdown.id)} |
||||
|
onMouseLeave={() => onMouseLeaveMenu(menuDropdown.id)} |
||||
|
> |
||||
|
{() => ( |
||||
|
<> |
||||
|
<div>{renderMainItem(menuDropdown)}</div> |
||||
|
<Transition |
||||
|
as={Fragment} |
||||
|
show={isHover} |
||||
|
enter="transition ease-out duration-150 " |
||||
|
enterFrom="opacity-0 translate-y-1" |
||||
|
enterTo="opacity-100 translate-y-0" |
||||
|
leave="transition ease-in duration-150" |
||||
|
leaveFrom="opacity-100 translate-y-0" |
||||
|
leaveTo="opacity-0 translate-y-1" |
||||
|
> |
||||
|
<Popover.Panel |
||||
|
static |
||||
|
className="sub-menu will-change-transform absolute transform z-10 w-56 top-full left-0" |
||||
|
> |
||||
|
<ul className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 dark:ring-white dark:ring-opacity-10 text-sm relative bg-white dark:bg-neutral-900 py-4 grid space-y-1"> |
||||
|
{menuDropdown.children?.map((i) => { |
||||
|
if (i.type) { |
||||
|
return renderDropdownMenuNavlinkHasChild(i); |
||||
|
} else { |
||||
|
return ( |
||||
|
<li |
||||
|
key={i.id} |
||||
|
className={`px-2 ${i.isNew ? "menuIsNew" : ""}`} |
||||
|
> |
||||
|
{renderDropdownMenuNavlink(i)} |
||||
|
</li> |
||||
|
); |
||||
|
} |
||||
|
})} |
||||
|
</ul> |
||||
|
</Popover.Panel> |
||||
|
</Transition> |
||||
|
</> |
||||
|
)} |
||||
|
</Popover> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const renderDropdownMenuNavlinkHasChild = (item: NavItemType) => { |
||||
|
const isHover = menuCurrentHovers.includes(item.id); |
||||
|
return ( |
||||
|
<Popover |
||||
|
as="li" |
||||
|
key={item.id} |
||||
|
className="menu-item flex items-center menu-dropdown relative px-2" |
||||
|
onMouseEnter={() => onMouseEnterMenu(item.id)} |
||||
|
onMouseLeave={() => onMouseLeaveMenu(item.id)} |
||||
|
> |
||||
|
{() => ( |
||||
|
<> |
||||
|
<div>{renderDropdownMenuNavlink(item)}</div> |
||||
|
<Transition |
||||
|
as={Fragment} |
||||
|
show={isHover} |
||||
|
enter="transition ease-out duration-150" |
||||
|
enterFrom="opacity-0 translate-y-1" |
||||
|
enterTo="opacity-100 translate-y-0" |
||||
|
leave="transition ease-in duration-150" |
||||
|
leaveFrom="opacity-100 translate-y-0" |
||||
|
leaveTo="opacity-0 translate-y-1" |
||||
|
> |
||||
|
<Popover.Panel |
||||
|
static |
||||
|
className="sub-menu absolute z-10 w-56 left-full pl-2 top-0" |
||||
|
> |
||||
|
<ul className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 dark:ring-white dark:ring-opacity-10 text-sm relative bg-white dark:bg-neutral-900 py-4 grid space-y-1"> |
||||
|
{item.children?.map((i) => { |
||||
|
if (i.type) { |
||||
|
return renderDropdownMenuNavlinkHasChild(i); |
||||
|
} else { |
||||
|
return ( |
||||
|
<li key={i.id} className="px-2"> |
||||
|
{renderDropdownMenuNavlink(i)} |
||||
|
</li> |
||||
|
); |
||||
|
} |
||||
|
})} |
||||
|
</ul> |
||||
|
</Popover.Panel> |
||||
|
</Transition> |
||||
|
</> |
||||
|
)} |
||||
|
</Popover> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const renderDropdownMenuNavlink = (item: NavItemType) => { |
||||
|
return ( |
||||
|
<Link |
||||
|
target={item.targetBlank ? "_blank" : undefined} |
||||
|
rel="noopener noreferrer" |
||||
|
className="flex items-center font-normal text-neutral-6000 dark:text-neutral-300 py-2 px-4 rounded-md hover:text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200 " |
||||
|
href={item.href || ""} |
||||
|
> |
||||
|
{item.name} |
||||
|
{item.type && ( |
||||
|
<ChevronDownIcon |
||||
|
className="ml-2 h-4 w-4 text-neutral-500" |
||||
|
aria-hidden="true" |
||||
|
/> |
||||
|
)} |
||||
|
</Link> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
// ===================== MENU MAIN MENU =====================
|
||||
|
const renderMainItem = (item: NavItemType) => { |
||||
|
return ( |
||||
|
<Link |
||||
|
rel="noopener noreferrer" |
||||
|
className="inline-flex items-center text-sm xl:text-base font-normal text-neutral-700 dark:text-neutral-300 py-2 px-4 xl:px-5 rounded-full hover:text-neutral-900 hover:bg-neutral-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200" |
||||
|
href={item.href || "/"} |
||||
|
> |
||||
|
{item.name} |
||||
|
{item.type && ( |
||||
|
<ChevronDownIcon |
||||
|
className="ml-1 -mr-1 h-4 w-4 text-neutral-400" |
||||
|
aria-hidden="true" |
||||
|
/> |
||||
|
)} |
||||
|
</Link> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
switch (menuItem.type) { |
||||
|
case "megaMenu": |
||||
|
return renderMegaMenu(menuItem); |
||||
|
case "dropdown": |
||||
|
return renderDropdownMenu(menuItem); |
||||
|
default: |
||||
|
return ( |
||||
|
<li className="menu-item flex items-center"> |
||||
|
{renderMainItem(menuItem)} |
||||
|
</li> |
||||
|
); |
||||
|
} |
||||
|
}; |
||||
|
// Your component own properties
|
||||
|
|
||||
|
export default NavigationItem; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue