From 6cb45f54c127896f6944251dddfb142ca8602977 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Mon, 12 Jan 2026 15:16:33 +0000 Subject: [PATCH] Fix search input losing focus when hovering over dropdown items (Vibe Kanban) (#1966) * Done! Here's a summary of the changes: ## Changes Made ### 1. `Dropdown.tsx` (lines 140-190) - Added `preventFocusOnHover?: boolean` prop to `DropdownMenuItemProps` interface - Added `onPointerMove` handler that calls `e.preventDefault()` when `preventFocusOnHover` is true, which stops Radix from stealing focus on hover ### 2. `SearchableDropdown.tsx` (line 110) - Added `preventFocusOnHover` prop to `DropdownMenuItem` ### 3. `ProjectSelectorContainer.tsx` (lines 181, 208) - Added `preventFocusOnHover` prop to both `DropdownMenuItem` components (the "Create new project" button and the virtualized project items) The fix uses `e.preventDefault()` on `onPointerMove` to disable Radix's hover-to-focus behavior, as documented in [Radix UI Issue #2193](https://github.com/radix-ui/primitives/issues/2193). This preserves all other functionality (click, keyboard navigation, selection) while allowing the search input to retain focus when hovering over items. * Done! I've added the `onPointerLeave` handler to `DropdownMenuItem` alongside the existing `onPointerMove` handler. Both now call `e.preventDefault()` when `preventFocusOnHover` is true. **Summary of the fix:** - Added `onPointerLeave` destructuring from props (line 161) - Added `onPointerLeave` handler that calls `e.preventDefault()` when `preventFocusOnHover` is true (lines 187-194) Per the [Radix UI Issue #2193](https://github.com/radix-ui/primitives/issues/2193), **both** `onPointerMove` and `onPointerLeave` need `preventDefault()` to fully disable hover-to-focus behavior. --- .../containers/ProjectSelectorContainer.tsx | 2 ++ .../components/ui-new/primitives/Dropdown.tsx | 21 +++++++++++++++++++ .../ui-new/primitives/SearchableDropdown.tsx | 1 + 3 files changed, 24 insertions(+) diff --git a/frontend/src/components/ui-new/containers/ProjectSelectorContainer.tsx b/frontend/src/components/ui-new/containers/ProjectSelectorContainer.tsx index d627720f..e9217ae8 100644 --- a/frontend/src/components/ui-new/containers/ProjectSelectorContainer.tsx +++ b/frontend/src/components/ui-new/containers/ProjectSelectorContainer.tsx @@ -180,6 +180,7 @@ export function ProjectSelectorContainer({ setHighlightedIndex(0)} + preventFocusOnHover icon={PlusIcon} className={cn( 'text-accent', @@ -208,6 +209,7 @@ export function ProjectSelectorContainer({ handleSelect(item)} onMouseEnter={() => setHighlightedIndex(idx + 1)} + preventFocusOnHover className={cn( isSelected && 'bg-secondary', isHighlighted && 'bg-secondary' diff --git a/frontend/src/components/ui-new/primitives/Dropdown.tsx b/frontend/src/components/ui-new/primitives/Dropdown.tsx index eae530cf..717e34e6 100644 --- a/frontend/src/components/ui-new/primitives/Dropdown.tsx +++ b/frontend/src/components/ui-new/primitives/Dropdown.tsx @@ -142,6 +142,8 @@ interface DropdownMenuItemProps icon?: Icon; badge?: string; variant?: 'default' | 'destructive'; + /** When true, prevents hover from stealing focus (useful for searchable dropdowns) */ + preventFocusOnHover?: boolean; } const DropdownMenuItem = React.forwardRef< @@ -154,6 +156,9 @@ const DropdownMenuItem = React.forwardRef< icon: IconComponent, badge, variant = 'default', + preventFocusOnHover = false, + onPointerMove, + onPointerLeave, children, ...props }, @@ -171,6 +176,22 @@ const DropdownMenuItem = React.forwardRef< variant === 'destructive' && 'text-error', className )} + onPointerMove={ + preventFocusOnHover + ? (e) => { + e.preventDefault(); + onPointerMove?.(e); + } + : onPointerMove + } + onPointerLeave={ + preventFocusOnHover + ? (e) => { + e.preventDefault(); + onPointerLeave?.(e); + } + : onPointerLeave + } {...props} > {IconComponent && } diff --git a/frontend/src/components/ui-new/primitives/SearchableDropdown.tsx b/frontend/src/components/ui-new/primitives/SearchableDropdown.tsx index f5ab00e7..8e887311 100644 --- a/frontend/src/components/ui-new/primitives/SearchableDropdown.tsx +++ b/frontend/src/components/ui-new/primitives/SearchableDropdown.tsx @@ -108,6 +108,7 @@ export function SearchableDropdown({ onSelect(item)} onMouseEnter={() => onHighlightedIndexChange(idx)} + preventFocusOnHover badge={getItemBadge?.(item)} className={cn( isSelected && 'bg-secondary',