Implementing a Context Menu in React with TypeScript
Context menus, also known as right-click menus, provide a convenient way to offer actions related to a specific element on a webpage. This challenge asks you to build a reusable context menu component in React using TypeScript, allowing users to customize the menu items and actions triggered upon selection. This is a common UI pattern used in applications like file explorers, image editors, and more.
Problem Description
You need to create a ContextMenu component that appears when a user right-clicks on a designated element. The component should:
- Display a Menu: Render a list of menu items, each with a label and an optional icon.
- Customizable Items: Accept a
menuItemsprop, which is an array of objects, each defining a menu item. Each item should have at least alabelproperty (string) and can optionally have aniconproperty (React Node). - Click Handling: When a menu item is clicked, it should trigger a callback function provided through the
onItemClickprop. This callback should receive the clicked item as an argument. - Positioning: The menu should appear positioned near the mouse cursor when the right-click event occurs.
- Visibility Control: The menu should be hidden when the user clicks outside the menu or presses the Escape key.
- Accessibility: Ensure the context menu is accessible by using appropriate ARIA attributes.
Key Requirements:
- The component should be reusable and configurable.
- The component should handle right-click events and display the menu accordingly.
- The component should correctly position the menu near the mouse cursor.
- The component should hide the menu when the user clicks outside or presses Escape.
- The component should be written in TypeScript.
Expected Behavior:
- When a right-click event occurs on the element the
ContextMenuis attached to, the menu should appear near the cursor. - Clicking a menu item should call the
onItemClickcallback with the corresponding item data. - Clicking outside the menu or pressing Escape should hide the menu.
- The menu should visually display the provided
menuItemscorrectly.
Edge Cases to Consider:
- What happens if
menuItemsis empty? The menu should not render. - How to handle overlapping with other elements on the page? (Basic positioning is sufficient for this challenge, but consider this for future improvements).
- What happens if the user right-clicks very quickly? Ensure the menu doesn't flicker or behave unexpectedly.
Examples
Example 1:
Input: menuItems: [{ label: "Copy", icon: <CopyIcon /> }, { label: "Paste", icon: <PasteIcon /> }, { label: "Delete" }]
Output: A context menu displaying "Copy" with a CopyIcon, "Paste" with a PasteIcon, and "Delete". Clicking "Copy" calls onItemClick with the "Copy" item.
Explanation: The component renders the provided menu items and handles click events to trigger the onItemClick callback.
Example 2:
Input: menuItems: []
Output: The context menu is not rendered.
Explanation: An empty menuItems array should result in no menu being displayed.
Example 3: (Edge Case)
Input: menuItems: [{ label: "Save", icon: <SaveIcon /> }, { label: "Save As...", icon: <SaveAsIcon /> }] and a right-click event occurs near the top edge of the window.
Output: The context menu appears positioned as close as possible to the cursor, potentially clipped by the window edge.
Explanation: The component attempts to position the menu near the cursor, even if it results in clipping.
Constraints
- The component should be implemented using functional components and React hooks.
- The
menuItemsarray should contain objects with at least alabelproperty (string). Aniconproperty (React Node) is optional. - The
onItemClickcallback should be a function that accepts a single argument (the clicked menu item object). - The component should be performant enough to handle a reasonable number of menu items (up to 20).
- The component should be compatible with modern browsers.
Notes
- Consider using the
useStatehook to manage the visibility of the menu. - Use event listeners (e.g.,
onClick,onContextMenu) to handle user interactions. - You can use CSS to style the menu and its items.
- Think about how to position the menu dynamically based on the mouse cursor position.
getBoundingClientRect()can be helpful. - Focus on the core functionality of displaying and handling clicks on the menu items. Advanced features like submenu support or complex animations are not required for this challenge.