import toast from 'react-hot-toast' import { ActionIcon, ActionIconProps } from './ActionIcon' import { RefObject, useEffect, useMemo, useRef, useState } from 'react' import { useHotkeys } from 'react-hotkeys-hook' import { Dialog } from '@headlessui/react' interface ContextMenuProps extends Omit, 'children'> { items?: React.ReactElement[] menuTargetElement?: RefObject } const DefaultContextMenuItems = [ , , // add more default context menu items here ] export function ContextMenu({ items = DefaultContextMenuItems, menuTargetElement, className, ...props }: ContextMenuProps) { const dialogRef = useRef(null) const [open, setOpen] = useState(false) const [windowSize, setWindowSize] = useState({ width: globalThis?.window?.innerWidth, height: globalThis?.window?.innerHeight, }) const [position, setPosition] = useState({ x: 0, y: 0 }) useHotkeys('esc', () => setOpen(false), { enabled: open, }) const dialogPositionStyle = useMemo(() => { if (!dialogRef.current) return { top: 0, left: 0, right: 'auto', bottom: 'auto', } return { top: position.y + dialogRef.current.clientHeight > windowSize.height ? 'auto' : position.y, left: position.x + dialogRef.current.clientWidth > windowSize.width ? 'auto' : position.x, right: position.x + dialogRef.current.clientWidth > windowSize.width ? windowSize.width - position.x : 'auto', bottom: position.y + dialogRef.current.clientHeight > windowSize.height ? windowSize.height - position.y : 'auto', } }, [position, windowSize, dialogRef.current]) // Listen for window resize to update context menu position useEffect(() => { const handleResize = () => { setWindowSize({ width: globalThis?.window?.innerWidth, height: globalThis?.window?.innerHeight, }) } globalThis?.window?.addEventListener('resize', handleResize) return () => { globalThis?.window?.removeEventListener('resize', handleResize) } }, []) // Add context menu listener to target once mounted useEffect(() => { const handleContextMenu = (e: MouseEvent) => { console.log('context menu', e) e.preventDefault() setPosition({ x: e.x, y: e.y }) setOpen(true) } menuTargetElement?.current?.addEventListener( 'contextmenu', handleContextMenu ) return () => { menuTargetElement?.current?.removeEventListener( 'contextmenu', handleContextMenu ) } }, [menuTargetElement?.current]) return ( setOpen(false)}>
e.preventDefault()} >
    setOpen(false)} > {...items}
) } export function ContextMenuDivider() { return
} interface ContextMenuItemProps { children: React.ReactNode icon?: ActionIconProps['icon'] onClick?: () => void hotkey?: string } export function ContextMenuItem({ children, icon, onClick, hotkey, }: ContextMenuItemProps) { return ( ) } export function ContextMenuItemRefresh() { return ( globalThis?.window?.location.reload()} > Refresh ) } interface ContextMenuItemCopyProps { toBeCopiedContent?: string toBeCopiedLabel?: string } export function ContextMenuItemCopy({ toBeCopiedContent = globalThis.window?.getSelection()?.toString(), toBeCopiedLabel = 'selection', }: ContextMenuItemCopyProps) { return ( { if (toBeCopiedContent) { globalThis?.navigator?.clipboard .writeText(toBeCopiedContent) .then(() => toast.success(`Copied ${toBeCopiedLabel} to clipboard`)) .catch(() => toast.error(`Failed to copy ${toBeCopiedLabel} to clipboard`) ) } }} > Copy ) }