5.7 KiB
Context Menu Popover Fix
Issue
The context menu was not showing when right-clicking on selected tasks in the TaskBrowser.
Root Cause Analysis
After reviewing the shadcn-vue documentation and comparing with our implementation, the following issues were identified:
Problems with DropdownMenu Approach:
-
Missing Trigger Element: DropdownMenu requires a DropdownMenuTrigger component. Our implementation used
v-model:opento control the menu programmatically, but DropdownMenu expects to be triggered by a trigger element. -
Incorrect Positioning: We attempted to use fixed positioning with inline styles on DropdownMenuContent, but DropdownMenu uses Radix UI's portal-based positioning logic, which conflicts with manual positioning.
-
Programmatic Control Limitations: The
v-model:openpattern doesn't work well with DropdownMenu without a proper trigger element. -
Wrong Component Choice: DropdownMenu is designed for click-triggered dropdowns with a visible trigger button, not for programmatic context menus triggered by right-click events.
Solution
Replaced DropdownMenu with Popover component and simplified the menu structure to use native buttons instead of DropdownMenu submenus.
Why Popover is Better:
- ✅ Programmatic Control: Supports
v-model:openfor programmatic control - ✅ Custom Positioning: Works with PopoverAnchor for custom positioning
- ✅ Flexible Triggers: Works well with custom triggers like right-click events
- ✅ Dynamic Positioning: Can position based on mouse coordinates using a fixed-position anchor
- ✅ No Trigger Required: Doesn't require wrapping trigger elements
Implementation Changes
TaskBulkActionsMenu.vue
Before (DropdownMenu):
<template>
<DropdownMenu v-model:open="isOpen">
<DropdownMenuContent
:style="{
position: 'fixed',
left: `${adjustedPosition.x}px`,
top: `${adjustedPosition.y}px`,
}"
class="w-56"
>
<!-- Menu content -->
</DropdownMenuContent>
</DropdownMenu>
</template>
After (Popover):
<template>
<Popover v-model:open="isOpen">
<PopoverAnchor
:style="{
position: 'fixed',
left: `${props.position.x}px`,
top: `${props.position.y}px`,
width: '1px',
height: '1px',
}"
/>
<PopoverContent
class="w-56 p-0"
:side="'bottom'"
:align="'start'"
@interact-outside="handleInteractOutside"
>
<!-- Menu content -->
</PopoverContent>
</Popover>
</template>
Key Changes:
- Replaced DropdownMenu with Popover: Changed the wrapper component
- Added PopoverAnchor: Creates a 1px invisible anchor point at the mouse cursor position
- Updated PopoverContent: Replaced DropdownMenuContent with PopoverContent
- Removed Manual Positioning Logic: Removed the
adjustedPositioncomputed property since Popover handles positioning automatically - Updated Imports: Changed from DropdownMenu imports to Popover imports
How It Works:
- User right-clicks on a table row
handleContextMenuin TaskBrowser captures the mouse coordinatescontextMenuPositionis updated with{ x: event.clientX, y: event.clientY }showContextMenuis set totrue- PopoverAnchor creates an invisible 1px element at the cursor position
- PopoverContent appears relative to the anchor
- Popover's built-in positioning logic handles viewport boundaries automatically
Benefits
- ✅ Context menu now appears at cursor position
- ✅ Automatic viewport boundary detection
- ✅ Proper z-index and portal rendering
- ✅ Maintains all existing functionality (status update, assignment)
- ✅ Cleaner code (removed manual positioning logic)
- ✅ Better accessibility
Testing
To test the fix:
- Open TaskBrowser in the application
- Select one or more tasks using checkboxes
- Right-click on a selected task
- Context menu should appear at the cursor position
- Verify "Set Status" submenu works
- Verify "Assign To" submenu works
- Verify menu closes when clicking outside
- Verify menu closes after selecting an action
Files Modified
frontend/src/components/task/TaskBulkActionsMenu.vue- Replaced DropdownMenu with Popover
Related Documentation
- shadcn-vue Popover: https://www.shadcn-vue.com/docs/components/popover
- Radix UI Popover: https://www.radix-vue.com/components/popover.html
Additional Fix: Menu Structure
Issue with DropdownMenu Submenus
After initial implementation, we encountered an error:
Error: Injection `Symbol(MenuContext)` not found. Component must be used within MenuRoot
Cause: DropdownMenuSub, DropdownMenuSubTrigger, and DropdownMenuSubContent components require a MenuRoot context (provided by DropdownMenu). They cannot be used inside a Popover.
Solution
Replaced DropdownMenu submenu components with simple native button elements styled to match the design system:
Before:
<DropdownMenuSub>
<DropdownMenuSubTrigger>Set Status</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem @click="...">...</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
After:
<div class="py-1">
<div class="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
Set Status
</div>
<button
class="w-full text-left px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground rounded-sm cursor-pointer"
@click="handleStatusSelected(status.value)"
>
{{ status.label }}
</button>
</div>
Benefits of Simplified Structure
- ✅ No dependency on DropdownMenu context
- ✅ Simpler, more maintainable code
- ✅ Better performance (fewer components)
- ✅ Same visual appearance
- ✅ Full control over styling and behavior