import { isFunction } from 'lodash'; import React, { useEffect, useRef, useState } from 'react'; import { BoxSpace, SilkeBox } from '../silke-box'; import { ButtonContext } from '../silke-button'; import { SilkePopover, SilkePopoverProps } from '../silke-popover'; import { usePopoverRequestClose } from '../silke-popover/popover-context'; import { SilkeContextMenuItem, SilkeContextMenuItemProps } from './silke-context-menu-item'; import { SilkeDivider } from '../silke-divider'; import styles from './silke-context-menu.scss'; import { SilkeTag } from '../silke-tag'; export type SilkeContextMenuItems = | (SilkeContextMenuItemProps | React.ReactElement | 'divider')[] | (() => (SilkeContextMenuItemProps | React.ReactElement | 'divider')[]); export type SilkeContextMenuProps = { items: SilkeContextMenuItems; width?: number; trigger?: 'contextmenu' | 'click' | 'mousedown'; target?: React.RefObject | React.MutableRefObject; /** Used internally to keep track of menu and closing */ subMenu?: boolean; gap?: BoxSpace; beta?: boolean; title?: string; } & Omit; export function SilkeContextMenu({ items, target, trigger, anchor, width, subMenu, gap, onRequestClose, beta, title = '', ...rest }: SilkeContextMenuProps) { const [showContextMenu, setShowContextMenu] = useState(false); const [pos, setPos] = React.useState<[number, number]>(); const boxRef = useRef(null); const requestCloseSub = usePopoverRequestClose('contextmenu.sub'); useEffect(() => { const targetEl = target?.current; if (targetEl?.addEventListener) { const handelContextMenu = (e: MouseEvent) => { e.preventDefault(); setPos([e.pageX, e.pageY]); setShowContextMenu(true); }; targetEl.addEventListener(trigger || 'contextmenu', handelContextMenu); return () => { targetEl.removeEventListener(trigger || 'contextmenu', handelContextMenu); }; } }, [trigger, target]); if (target && !showContextMenu) return null; return ( { if (showContextMenu) setShowContextMenu(false); onRequestClose?.(); }} {...rest} > {items && ( e.stopPropagation()} vPad="xs" gap={gap} onKeyDown={(e: React.KeyboardEvent) => { if (e.key === 'ArrowLeft' && !subMenu) { e.stopPropagation(); e.preventDefault(); requestCloseSub(); } if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.stopPropagation(); e.preventDefault(); let index = -1; const buttons = e.currentTarget.querySelectorAll('button'); buttons.forEach((b, i) => { if (document.activeElement === b) index = i; }); index += e.key === 'ArrowDown' ? 1 : -1; if (index >= buttons.length) index = 0; else if (index < 0) index = buttons.length - 1; buttons[index].focus(); } }} > {(beta || !!title) && ( {title} {beta && ( )} )} {(isFunction(items) ? items() : items)?.map((item, index) => { return !item ? null : React.isValidElement(item) ? ( {item} ) : item === 'divider' ? ( ) : ( { if (showContextMenu) setShowContextMenu(false); onRequestClose?.(); (item as SilkeContextMenuItemProps).onClick?.(e); }} /> ); })} )} ); }