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