Menu
A list of interactive options.
Installation
"use client";
import { Menu as MenuPrimitives, MenuRootProps, Portal } from "@ark-ui/react";
import { IconChevronRight } from "@tabler/icons-react";
import { forwardRef } from "react";

const Menu = (props: MenuRootProps) => {
  return (
    <MenuPrimitives.Root
      positioning={{
        placement: "bottom-start",
        gutter: 2,
      }}
      {...props}
    />
  );
};

Menu.displayName = "Menu";

const SubMenu = (props: MenuRootProps) => {
  return (
    <MenuPrimitives.Root
      positioning={{
        placement: "right-start",
        gutter: 0,
        overlap: true,
        fitViewport: true,
      }}
      {...props}
    />
  );
};

SubMenu.displayName = "SubMenu";

const MenuTrigger = MenuPrimitives.Trigger;

const MenuItem = forwardRef<
  React.ElementRef<typeof MenuPrimitives.Item>,
  React.ComponentPropsWithoutRef<typeof MenuPrimitives.Item>
>(({ children, ...props }, ref) => {
  return (
    <MenuPrimitives.Item
      ref={ref}
      className="hover:bg-base-200 flex h-8 cursor-pointer items-center gap-x-2.5 rounded-sm px-2 text-sm font-medium transition-colors"
      {...props}
    >
      {children}
    </MenuPrimitives.Item>
  );
});

MenuItem.displayName = "MenuItem";

const MenuTriggerItem = forwardRef<
  React.ElementRef<typeof MenuPrimitives.TriggerItem>,
  React.ComponentPropsWithoutRef<typeof MenuPrimitives.TriggerItem>
>(({ children, ...props }, ref) => {
  return (
    <MenuPrimitives.TriggerItem
      ref={ref}
      className="hover:bg-base-100 flex h-8 cursor-pointer items-center gap-x-2.5 rounded-sm px-2 text-sm font-medium transition-colors"
      {...props}
    >
      {children}
      <IconChevronRight className="ml-2 h-4 w-4" />
    </MenuPrimitives.TriggerItem>
  );
});

MenuTriggerItem.displayName = "MenuTriggerItem";

const MenuContent = forwardRef<
  React.ElementRef<typeof MenuPrimitives.Content>,
  React.ComponentPropsWithoutRef<typeof MenuPrimitives.Content>
>(({ children, ...props }, ref) => {
  return (
    <Portal>
      <MenuPrimitives.Positioner>
        <MenuPrimitives.Content
          ref={ref}
          className="border-base-200 text-base-500 bg-base-0 block w-max min-w-36 overflow-hidden rounded border p-1 shadow-md outline-none transition-all data-[state=open]:visible data-[state=closed]:invisible data-[state=closed]:scale-95 data-[state=open]:scale-100 data-[state=closed]:opacity-0 data-[state=open]:opacity-100"
          {...props}
        >
          {children}
        </MenuPrimitives.Content>
      </MenuPrimitives.Positioner>
    </Portal>
  );
});

MenuContent.displayName = "MenuContent";

const MenuGroup = forwardRef<
  React.ElementRef<typeof MenuPrimitives.ItemGroup>,
  React.ComponentPropsWithoutRef<typeof MenuPrimitives.ItemGroup>
>((props, ref) => {
  return <MenuPrimitives.ItemGroup ref={ref} {...props} />;
});

MenuGroup.displayName = "MenuGroup";

const MenuGroupLabel = forwardRef<
  React.ElementRef<typeof MenuPrimitives.ItemGroupLabel>,
  React.ComponentPropsWithoutRef<typeof MenuPrimitives.ItemGroupLabel>
>((props, ref) => {
  return (
    <MenuPrimitives.ItemGroupLabel
      ref={ref}
      className="text-base-500 mb-1 mt-2 px-2 text-xs font-medium"
      {...props}
    />
  );
});

MenuGroupLabel.displayName = "MenuGroupLabel";

const MenuSeparator = forwardRef<
  React.ElementRef<typeof MenuPrimitives.Separator>,
  React.ComponentPropsWithoutRef<typeof MenuPrimitives.Separator>
>(({ children, ...props }, ref) => {
  return (
    <MenuPrimitives.Separator
      ref={ref}
      className="bg-base-400 my-1 h-px scale-x-125"
      {...props}
    />
  );
});

MenuSeparator.displayName = "MenuSeparator";

export {
  Menu,
  MenuContent,
  MenuGroup,
  MenuGroupLabel,
  MenuItem,
  MenuSeparator,
  MenuTrigger,
  MenuTriggerItem,
  SubMenu,
};
Example
import {
  IconBasket,
  IconHeart,
  IconLogout,
  IconUser,
} from "@tabler/icons-react";
import { Button } from "../core/button";
import {
  Menu,
  MenuContent,
  MenuGroup,
  MenuGroupLabel,
  MenuItem,
  MenuSeparator,
  MenuTrigger,
  MenuTriggerItem,
  SubMenu,
} from "../core/menu";

export const MenuExample = () => {
  return (
    <Menu>
      <MenuTrigger asChild>
        <Button>Menu</Button>
      </MenuTrigger>

      <MenuContent>
        <SubMenu>
          <MenuTriggerItem>
            <IconUser className="h-4 w-4" />
            Has sub menu
          </MenuTriggerItem>
          <MenuContent>
            <MenuItem value="item-1">Item 1</MenuItem>
            <MenuItem value="item-2">Item 2</MenuItem>
            <MenuItem value="item-3">Item 3</MenuItem>
          </MenuContent>
        </SubMenu>
        <MenuSeparator />
        <MenuGroup>
          <MenuGroupLabel>Labs</MenuGroupLabel>

          <MenuItem value="cart">
            <IconBasket className="h-4 w-4" />
            Cart
          </MenuItem>
          <MenuItem value="wishes">
            <IconHeart className="h-4 w-4" />
            Wishes
          </MenuItem>
        </MenuGroup>
        <MenuItem value="logout">
          <IconLogout className="h-4 w-4" />
          Logout
        </MenuItem>
      </MenuContent>
    </Menu>
  );
};