Hone logo
Hone
Problems

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:

  1. Display a Menu: Render a list of menu items, each with a label and an optional icon.
  2. Customizable Items: Accept a menuItems prop, which is an array of objects, each defining a menu item. Each item should have at least a label property (string) and can optionally have an icon property (React Node).
  3. Click Handling: When a menu item is clicked, it should trigger a callback function provided through the onItemClick prop. This callback should receive the clicked item as an argument.
  4. Positioning: The menu should appear positioned near the mouse cursor when the right-click event occurs.
  5. Visibility Control: The menu should be hidden when the user clicks outside the menu or presses the Escape key.
  6. 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 ContextMenu is attached to, the menu should appear near the cursor.
  • Clicking a menu item should call the onItemClick callback with the corresponding item data.
  • Clicking outside the menu or pressing Escape should hide the menu.
  • The menu should visually display the provided menuItems correctly.

Edge Cases to Consider:

  • What happens if menuItems is 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 menuItems array should contain objects with at least a label property (string). An icon property (React Node) is optional.
  • The onItemClick callback 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 useState hook 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.
Loading editor...
typescript