Create ContextMenu component
This commit is contained in:
parent
e3088133f3
commit
60370fc39f
8
src/renderer/src/components/ContextMenu/index.css
Normal file
8
src/renderer/src/components/ContextMenu/index.css
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.contextMenu {
|
||||||
|
position: absolute;
|
||||||
|
width: 200px;
|
||||||
|
background-color: #383838;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
48
src/renderer/src/components/ContextMenu/index.tsx
Normal file
48
src/renderer/src/components/ContextMenu/index.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { ReactNode, MouseEvent, memo } from 'react';
|
||||||
|
import Menu from '../Menu';
|
||||||
|
import useContextMenu from '@renderer/hooks/useContextMenu';
|
||||||
|
import MenuItem from '../MenuItem';
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
label: string
|
||||||
|
onClick: () => void
|
||||||
|
show: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode
|
||||||
|
items: Item[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO only one context menu can be opened
|
||||||
|
const ContextMenu = ({ children, items }: Props) => {
|
||||||
|
const { menuVisible, menuItems, menuPosition, showMenu, hideMenu } = useContextMenu();
|
||||||
|
const handleContextMenu = (event: MouseEvent) => {
|
||||||
|
showMenu(event, items);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onContextMenu={handleContextMenu} onClick={hideMenu}>
|
||||||
|
{children}
|
||||||
|
<Menu
|
||||||
|
open={menuVisible}
|
||||||
|
onClose={hideMenu}
|
||||||
|
anchorReference="anchorPosition"
|
||||||
|
className="contextMenu"
|
||||||
|
anchorPosition={
|
||||||
|
menuVisible
|
||||||
|
? { top: menuPosition.y, left: menuPosition.x }
|
||||||
|
: { top: 0, left: 0 }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{menuItems.map(({ label, onClick, show }, i) => show && (
|
||||||
|
<MenuItem key={i} onClick={onClick}>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ContextMenu);
|
||||||
0
src/renderer/src/components/Menu/index.css
Normal file
0
src/renderer/src/components/Menu/index.css
Normal file
50
src/renderer/src/components/Menu/index.tsx
Normal file
50
src/renderer/src/components/Menu/index.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { ReactNode } from "react"
|
||||||
|
|
||||||
|
interface AnchorPosition {
|
||||||
|
top: number
|
||||||
|
left: number
|
||||||
|
}
|
||||||
|
interface Props {
|
||||||
|
autoFocus?: boolean
|
||||||
|
children?: ReactNode
|
||||||
|
open: boolean
|
||||||
|
onClose?: (event, reason) => void
|
||||||
|
anchorReference: string
|
||||||
|
anchorPosition: AnchorPosition
|
||||||
|
className: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Menu = ({ autoFocus = true, children, open, onClose, anchorReference, anchorPosition, ...rest }: Props) => {
|
||||||
|
const handleListKeyDown = event => {
|
||||||
|
if (event.key === 'Tab') {
|
||||||
|
event.preventDefault();
|
||||||
|
if (onClose) {
|
||||||
|
onClose(event, 'tabKeyDown');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
if (onClose) {
|
||||||
|
onClose(event, 'escapeKeyDown');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{open &&
|
||||||
|
<ul
|
||||||
|
style={{ position: 'absolute', top: anchorPosition.top, left: anchorPosition.left, zIndex: 1000, backgroundColor: '#fff', color: 'black', border: '1px solid #ccc', boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)' }}
|
||||||
|
role="menu"
|
||||||
|
aria-labelledby="contextmenu-label"
|
||||||
|
onKeyDown={handleListKeyDown}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Menu;
|
||||||
0
src/renderer/src/components/MenuItem/index.css
Normal file
0
src/renderer/src/components/MenuItem/index.css
Normal file
14
src/renderer/src/components/MenuItem/index.tsx
Normal file
14
src/renderer/src/components/MenuItem/index.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode
|
||||||
|
onClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuItem = ({ children, ...rest }: Props) => {
|
||||||
|
return (
|
||||||
|
<li role="menuitem" tabIndex={-1} {...rest}>{children}</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MenuItem;
|
||||||
37
src/renderer/src/hooks/useContextMenu.ts
Normal file
37
src/renderer/src/hooks/useContextMenu.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
const useContextMenu = () => {
|
||||||
|
const [menuVisible, setMenuVisible] = useState(false);
|
||||||
|
const [menuItems, setMenuItems] = useState([]);
|
||||||
|
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
const showMenu = useCallback((event, items) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setMenuPosition({ x: event.pageX, y: event.pageY });
|
||||||
|
setMenuItems(items);
|
||||||
|
setMenuVisible(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hideMenu = useCallback(() => {
|
||||||
|
setMenuVisible(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleDocumentClick = (event) => {
|
||||||
|
// Logic to hide the context menu
|
||||||
|
hideMenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the event listener
|
||||||
|
document.addEventListener('click', handleDocumentClick);
|
||||||
|
|
||||||
|
// Cleanup function to remove the event listener
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('click', handleDocumentClick);
|
||||||
|
};
|
||||||
|
}, [hideMenu]);
|
||||||
|
|
||||||
|
return { menuVisible, menuItems, menuPosition, showMenu, hideMenu };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useContextMenu;
|
||||||
Reference in a new issue