Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import React, { useState } from "react";
import clsx from "clsx";
import { Dropdown, useDropdown } from "@humansignal/ui";
import { IconEllipsisVertical } from "@humansignal/icons";
import styles from "./ContextMenu.module.scss";
 
export interface ContextMenuContext {
  dropdown: ReturnType<typeof useDropdown>;
}
export type MenuActionOnClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, ctx: ContextMenuContext) => void;
export interface ContextMenuAction {
  label: React.ReactNode;
  onClick: MenuActionOnClick;
  icon?: React.ReactNode;
  iconClassName?: string;
  key?: string;
  separator?: boolean;
  danger?: boolean;
  enabled?: boolean;
}
export interface ContextMenuProps {
  actions: ContextMenuAction[];
  className?: string;
}
export interface ContextMenuTriggerProps {
  className?: string;
  children: React.ReactNode;
  content?: React.ReactNode;
  onToggle?: (isOpen: boolean) => void;
}
 
export const ContextMenu: React.FC<ContextMenuProps> = ({ actions, className }) => {
  const dropdown = useDropdown();
 
  return (
    <div className={clsx(styles.contextMenu, className)}>
      {actions.map(
        (action, index) =>
          action.enabled !== false && (
            <React.Fragment key={action.key ?? index}>
              {action.separator && <div className={styles.seperator} />}
              <div
                className={clsx(styles.option, action.danger && styles.danger)}
                onClick={(e) => action.onClick(e, { dropdown })}
              >
                {action.icon && <span className={clsx(styles.icon, action.iconClassName)}>{action.icon}</span>}
                {action.label}
              </div>
            </React.Fragment>
          ),
      )}
    </div>
  );
};
 
export const ContextMenuTrigger: React.FC<ContextMenuTriggerProps> = ({ children, content, onToggle, className }) => {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div className={clsx(styles.trigger, isOpen && styles.open, className)} onClick={(e) => e.stopPropagation()}>
      <Dropdown.Trigger
        content={content || undefined}
        onToggle={(isOpen) => {
          setIsOpen(isOpen);
          onToggle?.(isOpen);
        }}
      >
        {children ? children : <IconEllipsisVertical width={28} height={28} />}
      </Dropdown.Trigger>
    </div>
  );
};