A useful headless component (hook) that gives you all the functions you need to create a multi-level menu using your own components!
yarn add react-headless-nested-menu
You can import the generated bundle to use the whole library generated by this starter:
import React from "react"; import { useNestedMenu } from "react-headless-nested-menu"; function App() { const { getToggleButtonProps, getMenuProps, getItemProps, getOpenTriggerProps, getCloseTriggerProps, getMenuOffsetStyles, isOpen, isSubMenuOpen, toggleMenu } = useNestedMenu({ items }); const [item, setItem] = useState<MenuItem>(); // your custom function to render items const renderItem = (item: MenuItem) => ( <div {...getItemProps(item)} className="relative my-1 first:mt-0 last:mb-0" {...getOpenTriggerProps("onPointerEnter", item)} onClick={(event) => { event.stopPropagation(); setItem(item); toggleMenu(); }} > <div className={classnames( "flex flex-row justify-between items-center rounded-lg flex-1 h-8 flex items-center px-2", { "text-gray-600 hover:text-gray-800 hover:bg-gray-200": !isSubMenuOpen( item ), "text-gray-800 bg-gray-200": isSubMenuOpen(item) } )} > {item.label} {item.subMenu && <Chevron />} </div> {/* Only show submenu when there's a submenu & it's open */} {item.subMenu && isSubMenuOpen(item) && renderMenu(item.subMenu, item)} </div> ); // your custom function to render menus (root menu & sub-menus) const renderMenu = (items: Items, parentItem?: MenuItem) => ( <div {...getMenuProps(parentItem)} style={{ position: "absolute", ...getMenuOffsetStyles(parentItem) }} className={classnames( "bg-white p-2 shadow-lg rounded-lg select-none border border-gray-100 relative z-10", { "ms-2": typeof parentItem === "undefined", //for root menu "-mt-3": typeof parentItem !== "undefined" //for submenus only } )} {...getCloseTriggerProps("onPointerLeave", parentItem)} > <div>{items.map((item) => renderItem(item))}</div> {/* add hit area */} {parentItem && ( <div style={{ position: "absolute", top: -8, bottom: -8, left: -8, right: -8, zIndex: -1 }} ></div> )} </div> ); return ( <div className="w-64 p-4 rounded-lg flex flex-col ms-4 mt-4"> <button className="text-gray-600 border-2 border-gray-600 rounded-lg h-10 focus:outline-none" {...getToggleButtonProps()} > {item ? item.label : "Open Menu"} </button> {isOpen && renderMenu(items)} </div> ); } const rootElement = document.getElementById("root"); React.render(<App />, rootElement);
Generated using TypeDoc