103
common/resources/client/ui/navigation/menu/context-menu.tsx
Executable file
103
common/resources/client/ui/navigation/menu/context-menu.tsx
Executable file
@@ -0,0 +1,103 @@
|
||||
import React, {ReactElement, useEffect} from 'react';
|
||||
import {useListbox} from '../../forms/listbox/use-listbox';
|
||||
import {Listbox} from '../../forms/listbox/listbox';
|
||||
import {Menu} from './menu-trigger';
|
||||
import {useListboxKeyboardNavigation} from '../../forms/listbox/use-listbox-keyboard-navigation';
|
||||
import {useTypeSelect} from '../../forms/listbox/use-type-select';
|
||||
import {ListBoxChildren, ListboxProps} from '../../forms/listbox/types';
|
||||
import {VirtualElement} from '@floating-ui/react-dom';
|
||||
|
||||
const preventContextOnMenu = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
type Props = ListboxProps &
|
||||
ListBoxChildren<any> & {
|
||||
position?: {x: number; y: number} | null;
|
||||
};
|
||||
|
||||
export function ContextMenu({position, children, ...props}: Props) {
|
||||
const listbox = useListbox({
|
||||
...props,
|
||||
isOpen: props.isOpen && !!position,
|
||||
placement: 'right-start',
|
||||
floatingWidth: 'auto',
|
||||
role: 'menu',
|
||||
loopFocus: true,
|
||||
children:
|
||||
(children as ReactElement)?.type === Menu
|
||||
? (children as ReactElement).props.children
|
||||
: children,
|
||||
});
|
||||
const {
|
||||
reference,
|
||||
refs,
|
||||
state: {isOpen, setIsOpen, activeIndex},
|
||||
focusItem,
|
||||
listContent,
|
||||
} = listbox;
|
||||
|
||||
useEffect(() => {
|
||||
if (refs.floating.current) {
|
||||
refs.floating.current.addEventListener(
|
||||
'contextmenu',
|
||||
preventContextOnMenu,
|
||||
);
|
||||
return () => {
|
||||
refs.floating.current?.removeEventListener(
|
||||
'contextmenu',
|
||||
preventContextOnMenu,
|
||||
);
|
||||
};
|
||||
}
|
||||
}, [refs.floating]);
|
||||
|
||||
useEffect(() => {
|
||||
if (position) {
|
||||
reference(pointToVirtualElement(position));
|
||||
setIsOpen(true);
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}, [position, reference, setIsOpen]);
|
||||
|
||||
const {handleListboxKeyboardNavigation} =
|
||||
useListboxKeyboardNavigation(listbox);
|
||||
|
||||
const {findMatchingItem} = useTypeSelect();
|
||||
|
||||
return (
|
||||
<Listbox
|
||||
listbox={listbox}
|
||||
onKeyDownCapture={e => {
|
||||
if (!isOpen) return;
|
||||
const i = findMatchingItem(e, listContent, activeIndex);
|
||||
if (i) {
|
||||
focusItem('increment', i);
|
||||
}
|
||||
}}
|
||||
onKeyDown={handleListboxKeyboardNavigation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function pointToVirtualElement(
|
||||
{x, y}: {x: number; y: number},
|
||||
contextElement?: Element,
|
||||
): VirtualElement {
|
||||
return {
|
||||
getBoundingClientRect() {
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: y,
|
||||
right: x,
|
||||
bottom: y,
|
||||
left: x,
|
||||
};
|
||||
},
|
||||
contextElement,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user