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.
This commit is contained in:
committed by
GitHub
parent
8dc07ee2b2
commit
6cb45f54c1
@@ -180,6 +180,7 @@ export function ProjectSelectorContainer({
|
||||
<DropdownMenuItem
|
||||
onSelect={handleCreateClick}
|
||||
onMouseEnter={() => setHighlightedIndex(0)}
|
||||
preventFocusOnHover
|
||||
icon={PlusIcon}
|
||||
className={cn(
|
||||
'text-accent',
|
||||
@@ -208,6 +209,7 @@ export function ProjectSelectorContainer({
|
||||
<DropdownMenuItem
|
||||
onSelect={() => handleSelect(item)}
|
||||
onMouseEnter={() => setHighlightedIndex(idx + 1)}
|
||||
preventFocusOnHover
|
||||
className={cn(
|
||||
isSelected && 'bg-secondary',
|
||||
isHighlighted && 'bg-secondary'
|
||||
|
||||
@@ -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 && <IconComponent weight="bold" />}
|
||||
|
||||
@@ -108,6 +108,7 @@ export function SearchableDropdown<T>({
|
||||
<DropdownMenuItem
|
||||
onSelect={() => onSelect(item)}
|
||||
onMouseEnter={() => onHighlightedIndexChange(idx)}
|
||||
preventFocusOnHover
|
||||
badge={getItemBadge?.(item)}
|
||||
className={cn(
|
||||
isSelected && 'bg-secondary',
|
||||
|
||||
Reference in New Issue
Block a user