From 60370fc39fd465677f1452e04304d475a02cbe4f Mon Sep 17 00:00:00 2001 From: TheThomaas Date: Mon, 23 Dec 2024 12:50:36 +0100 Subject: [PATCH] Create ContextMenu component --- .../src/components/ContextMenu/index.css | 8 +++ .../src/components/ContextMenu/index.tsx | 48 ++++++++++++++++++ src/renderer/src/components/Menu/index.css | 0 src/renderer/src/components/Menu/index.tsx | 50 +++++++++++++++++++ .../src/components/MenuItem/index.css | 0 .../src/components/MenuItem/index.tsx | 14 ++++++ src/renderer/src/hooks/useContextMenu.ts | 37 ++++++++++++++ 7 files changed, 157 insertions(+) create mode 100644 src/renderer/src/components/ContextMenu/index.css create mode 100644 src/renderer/src/components/ContextMenu/index.tsx create mode 100644 src/renderer/src/components/Menu/index.css create mode 100644 src/renderer/src/components/Menu/index.tsx create mode 100644 src/renderer/src/components/MenuItem/index.css create mode 100644 src/renderer/src/components/MenuItem/index.tsx create mode 100644 src/renderer/src/hooks/useContextMenu.ts diff --git a/src/renderer/src/components/ContextMenu/index.css b/src/renderer/src/components/ContextMenu/index.css new file mode 100644 index 0000000..e1cd521 --- /dev/null +++ b/src/renderer/src/components/ContextMenu/index.css @@ -0,0 +1,8 @@ +.contextMenu { + position: absolute; + width: 200px; + background-color: #383838; + border-radius: 5px; + box-sizing: border-box; + padding: 0; +} \ No newline at end of file diff --git a/src/renderer/src/components/ContextMenu/index.tsx b/src/renderer/src/components/ContextMenu/index.tsx new file mode 100644 index 0000000..13fcc6e --- /dev/null +++ b/src/renderer/src/components/ContextMenu/index.tsx @@ -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 ( +
+ {children} + + {menuItems.map(({ label, onClick, show }, i) => show && ( + + {label} + + ))} + +
+ ); +}; + +export default memo(ContextMenu); \ No newline at end of file diff --git a/src/renderer/src/components/Menu/index.css b/src/renderer/src/components/Menu/index.css new file mode 100644 index 0000000..e69de29 diff --git a/src/renderer/src/components/Menu/index.tsx b/src/renderer/src/components/Menu/index.tsx new file mode 100644 index 0000000..69b2e26 --- /dev/null +++ b/src/renderer/src/components/Menu/index.tsx @@ -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 && + + } + + ); +}; + +export default Menu; \ No newline at end of file diff --git a/src/renderer/src/components/MenuItem/index.css b/src/renderer/src/components/MenuItem/index.css new file mode 100644 index 0000000..e69de29 diff --git a/src/renderer/src/components/MenuItem/index.tsx b/src/renderer/src/components/MenuItem/index.tsx new file mode 100644 index 0000000..55b4014 --- /dev/null +++ b/src/renderer/src/components/MenuItem/index.tsx @@ -0,0 +1,14 @@ +import { ReactNode } from 'react'; + +interface Props { + children: ReactNode + onClick: () => void +} + +const MenuItem = ({ children, ...rest }: Props) => { + return ( +
  • {children}
  • + ); +}; + +export default MenuItem; \ No newline at end of file diff --git a/src/renderer/src/hooks/useContextMenu.ts b/src/renderer/src/hooks/useContextMenu.ts new file mode 100644 index 0000000..5ca6baf --- /dev/null +++ b/src/renderer/src/hooks/useContextMenu.ts @@ -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; \ No newline at end of file