All files / src/components ContextMenu.jsx

0% Statements 0/26
0% Branches 0/17
0% Functions 0/11
0% Lines 0/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                                                                                                                                                                                                 
/**
 * @fileoverview Context Menu Component
 * @description Right-click context menu for atoms
 * @module components/ContextMenu
 */
 
import { createContext, useContext, useState, useCallback, useEffect } from 'react';
import './ContextMenu.css';
 
const ContextMenuContext = createContext();
 
/**
 * Context Menu Provider
 */
export function ContextMenuProvider({ children }) {
  const [menu, setMenu] = useState(null);
 
  const showContextMenu = useCallback((x, y, items, atom = null) => {
    setMenu({ x, y, items, atom });
  }, []);
 
  const hideContextMenu = useCallback(() => {
    setMenu(null);
  }, []);
 
  // Hide on click outside
  useEffect(() => {
    const handleClick = () => hideContextMenu();
    const handleKeyDown = (e) => {
      if (e.key === 'Escape') hideContextMenu();
    };
 
    document.addEventListener('click', handleClick);
    document.addEventListener('keydown', handleKeyDown);
    return () => {
      document.removeEventListener('click', handleClick);
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [hideContextMenu]);
 
  return (
    <ContextMenuContext.Provider value={{ showContextMenu, hideContextMenu }}>
      {children}
      {menu && (
        <div 
          className="context-menu"
          style={{ 
            left: Math.min(menu.x, window.innerWidth - 200), 
            top: Math.min(menu.y, window.innerHeight - 300)
          }}
          onClick={(e) => e.stopPropagation()}
        >
          {menu.atom && (
            <div className="context-menu-header">
              {menu.atom.symbol} Atom
            </div>
          )}
          {menu.items.map((item, index) => (
            item.divider ? (
              <div key={index} className="context-menu-divider" />
            ) : (
              <button
                key={index}
                className={`context-menu-item ${item.danger ? 'danger' : ''}`}
                onClick={() => {
                  item.action();
                  hideContextMenu();
                }}
                disabled={item.disabled}
              >
                {item.icon && <span className="context-menu-icon">{item.icon}</span>}
                <span className="context-menu-label">{item.label}</span>
                {item.shortcut && (
                  <span className="context-menu-shortcut">{item.shortcut}</span>
                )}
              </button>
            )
          ))}
        </div>
      )}
    </ContextMenuContext.Provider>
  );
}
 
/**
 * Hook to use context menu
 */
export function useContextMenu() {
  const context = useContext(ContextMenuContext);
  if (!context) {
    throw new Error('useContextMenu must be used within a ContextMenuProvider');
  }
  return context;
}
 
export default ContextMenuProvider;