ContextMenu

Displays a menu of actions triggered by a right click.

Demo

Right-click here

Dependencies

clsxtailwind-merge

Props

This is a base primitive. It accepts standard HTML attributes plus className for styling via cn().

Source

"use client";

import { useState, useRef, useEffect } from "react";
import { cn } from "@/lib/utils";

export interface ContextMenuProps {
  items: {
    label: string;
    onClick: () => void;
    disabled?: boolean;
    separator?: boolean;
  }[];
  children: React.ReactNode;
  className?: string;
}

export function ContextMenu({ items, children, className }: ContextMenuProps) {
  const [pos, setPos] = useState<{ x: number; y: number } | null>(null);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const h = () => setPos(null);
    if (pos) document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, [pos]);

  return (
    <div
      className={className}
      onContextMenu={(e) => {
        e.preventDefault();
        setPos({ x: e.clientX, y: e.clientY });
      }}
    >
      {children}
      {pos && (
        <div
          ref={ref}
          style={{ left: pos.x, top: pos.y }}
          className="fixed z-50 min-w-[10rem] rounded-lg border border-gray-200 bg-white py-1 shadow-sm"
        >
          {items.map((item, i) =>
            item.separator ? (
              <div key={i} className="my-1 border-t border-gray-200" />
            ) : (
              <button
                key={i}
                onClick={() => {
                  item.onClick();
                  setPos(null);
                }}
                disabled={item.disabled}
                className="flex w-full items-center px-3 py-1.5 text-left text-sm text-gray-700 hover:bg-gray-100 disabled:opacity-50"
              >
                {item.label}
              </button>
            ),
          )}
        </div>
      )}
    </div>
  );
}

CLI

npx genui add context-menu