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