From eac11c12e051498a0175c6fcd911c4be0486d8f0 Mon Sep 17 00:00:00 2001 From: heyseth Date: Thu, 6 Nov 2025 16:08:32 -0800 Subject: [PATCH] fix: distinguish hover and selection states in context menu Improve the context menu UX by separating hover and selection visual states. Previously, hovering would immediately change the selected index, making it difficult to distinguish between the actively selected item and the item being hovered over. Changes: - Add separate hoveredIndex state to track mouse hover independently from selection - Apply different background colors for hover vs selection states - Update mouse event handlers to properly manage hover state - Reset hover state when search query changes --- .../src/components/chat/ContextMenu.tsx | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx index cf4b10a981..45759026e5 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/webview-ui/src/components/chat/ContextMenu.tsx @@ -46,6 +46,7 @@ const ContextMenu: React.FC = ({ }) => { const [materialIconsBaseUri, setMaterialIconsBaseUri] = useState("") const menuRef = useRef(null) + const [hoveredIndex, setHoveredIndex] = useState(null) const filteredOptions = useMemo(() => { return getContextMenuOptions(searchQuery, selectedType, queryItems, dynamicSearchResults, modes, commands) @@ -73,6 +74,11 @@ const ContextMenu: React.FC = ({ setMaterialIconsBaseUri(w.MATERIAL_ICONS_BASE_URI) }, []) + // Reset hover state when menu opens/closes + useEffect(() => { + setHoveredIndex(null) + }, [searchQuery]) + const renderOptionContent = (option: ContextMenuQueryItem) => { switch (option.type) { case ContextMenuOptionType.SectionHeader: @@ -340,14 +346,18 @@ const ContextMenu: React.FC = ({ filteredOptions.map((option, index) => (
isOptionSelectable(option) && onSelect(option.type, option.value)} + onClick={() => { + if (isOptionSelectable(option)) { + setSelectedIndex(index) + onSelect(option.type, option.value) + } + }} style={{ padding: option.type === ContextMenuOptionType.SectionHeader ? "16px 8px 4px 8px" : "4px 8px", cursor: isOptionSelectable(option) ? "pointer" : "default", - color: "var(--vscode-dropdown-foreground)", display: "flex", alignItems: "center", justifyContent: "space-between", @@ -358,14 +368,31 @@ const ContextMenu: React.FC = ({ marginBottom: "2px", } : {}), + // Show different styles for selection vs hover ...(index === selectedIndex && isOptionSelectable(option) ? { backgroundColor: "var(--vscode-list-activeSelectionBackground)", color: "var(--vscode-list-activeSelectionForeground)", } - : {}), + : index === hoveredIndex && isOptionSelectable(option) + ? { + backgroundColor: "var(--vscode-list-hoverBackground)", + color: "var(--vscode-dropdown-foreground)", + } + : { + color: "var(--vscode-dropdown-foreground)", + }), }} - onMouseEnter={() => isOptionSelectable(option) && setSelectedIndex(index)}> + onMouseEnter={() => { + if (isOptionSelectable(option)) { + setHoveredIndex(index) + } + }} + onMouseLeave={() => { + if (index === hoveredIndex) { + setHoveredIndex(null) + } + }}>