feat: add right-click context menu to messages#706
Conversation
Right-clicking a message bubble now shows a context menu with a 'Copy message' action that copies the full content to clipboard. Changes: - MessageRow: add onContextMenu handler, context menu state, fixed-position menu with click-outside/Escape dismissal - main.css: .chat-context-menu (fixed, elevated with shadow) + .chat-context-menu-item (flex row with hover) - i18n: add copyMessage to all 10 locales
| // Close context menu on click outside or Escape | ||
| useEffect(() => { | ||
| if (!contextMenu) return; | ||
| function onClick(e: MouseEvent): void { | ||
| if (menuRef.current && !menuRef.current.contains(e.target as Node)) { | ||
| closeContextMenu(); | ||
| } | ||
| } | ||
| function onKey(e: KeyboardEvent): void { | ||
| if (e.key === "Escape") closeContextMenu(); | ||
| } | ||
| document.addEventListener("click", onClick, true); | ||
| document.addEventListener("keydown", onKey); | ||
| return () => { | ||
| document.removeEventListener("click", onClick, true); | ||
| document.removeEventListener("keydown", onKey); | ||
| }; | ||
| }, [contextMenu, closeContextMenu]); |
There was a problem hiding this comment.
Multiple context menus can stack simultaneously
The dismiss effect only listens for click events, so a right-click (contextmenu event) on a different message row does not fire onClick and therefore never closes the current menu. Because every MessageRow manages its own state independently, the second right-click opens a second chat-context-menu alongside the first. The user ends up with two (or more) stacked menus that cannot be closed without an extra regular click.
The fix is to also listen for contextmenu on the document and close any open menu when it fires (before the targeted row's handleContextMenu re-opens one for the new position).
| {contextMenu && ( | ||
| <div | ||
| ref={menuRef} | ||
| className="chat-context-menu" | ||
| style={{ left: contextMenu.x, top: contextMenu.y }} | ||
| > |
There was a problem hiding this comment.
Right-clicking directly on the open context menu bubbles the
contextmenu event up to the outer div's onContextMenu={handleContextMenu}, which calls setContextMenu with new coordinates and re-positions the menu. Adding onContextMenu={e => e.stopPropagation()} on the menu container prevents this.
| {contextMenu && ( | |
| <div | |
| ref={menuRef} | |
| className="chat-context-menu" | |
| style={{ left: contextMenu.x, top: contextMenu.y }} | |
| > | |
| {contextMenu && ( | |
| <div | |
| ref={menuRef} | |
| className="chat-context-menu" | |
| style={{ left: contextMenu.x, top: contextMenu.y }} | |
| onContextMenu={(e) => e.stopPropagation()} | |
| > |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| <div | ||
| ref={menuRef} | ||
| className="chat-context-menu" | ||
| style={{ left: contextMenu.x, top: contextMenu.y }} |
There was a problem hiding this comment.
Menu can render off-screen near viewport edges
contextMenu.x and contextMenu.y are raw cursor coordinates. When the cursor is in the lower-right corner of the viewport the 160 px-wide, ~40 px-tall menu overflows outside the window with no way to scroll it back into view. A boundary check clamping left to Math.min(x, window.innerWidth - menuWidth) and top to Math.min(y, window.innerHeight - menuHeight) would keep it fully visible.
Feature
Add right-click context menu to message bubbles. Right-clicking any chat message shows a context menu with a 'Copy message' action that copies the full message content to clipboard.
Changes