176 lines
5.7 KiB
Markdown
176 lines
5.7 KiB
Markdown
# 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:
|
|
|
|
1. **Missing Trigger Element**: DropdownMenu requires a DropdownMenuTrigger component. Our implementation used `v-model:open` to control the menu programmatically, but DropdownMenu expects to be triggered by a trigger element.
|
|
|
|
2. **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.
|
|
|
|
3. **Programmatic Control Limitations**: The `v-model:open` pattern doesn't work well with DropdownMenu without a proper trigger element.
|
|
|
|
4. **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:
|
|
|
|
1. ✅ **Programmatic Control**: Supports `v-model:open` for programmatic control
|
|
2. ✅ **Custom Positioning**: Works with PopoverAnchor for custom positioning
|
|
3. ✅ **Flexible Triggers**: Works well with custom triggers like right-click events
|
|
4. ✅ **Dynamic Positioning**: Can position based on mouse coordinates using a fixed-position anchor
|
|
5. ✅ **No Trigger Required**: Doesn't require wrapping trigger elements
|
|
|
|
## Implementation Changes
|
|
|
|
### TaskBulkActionsMenu.vue
|
|
|
|
**Before (DropdownMenu):**
|
|
```vue
|
|
<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):**
|
|
```vue
|
|
<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:
|
|
|
|
1. **Replaced DropdownMenu with Popover**: Changed the wrapper component
|
|
2. **Added PopoverAnchor**: Creates a 1px invisible anchor point at the mouse cursor position
|
|
3. **Updated PopoverContent**: Replaced DropdownMenuContent with PopoverContent
|
|
4. **Removed Manual Positioning Logic**: Removed the `adjustedPosition` computed property since Popover handles positioning automatically
|
|
5. **Updated Imports**: Changed from DropdownMenu imports to Popover imports
|
|
|
|
### How It Works:
|
|
|
|
1. User right-clicks on a table row
|
|
2. `handleContextMenu` in TaskBrowser captures the mouse coordinates
|
|
3. `contextMenuPosition` is updated with `{ x: event.clientX, y: event.clientY }`
|
|
4. `showContextMenu` is set to `true`
|
|
5. PopoverAnchor creates an invisible 1px element at the cursor position
|
|
6. PopoverContent appears relative to the anchor
|
|
7. 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:
|
|
|
|
1. Open TaskBrowser in the application
|
|
2. Select one or more tasks using checkboxes
|
|
3. Right-click on a selected task
|
|
4. Context menu should appear at the cursor position
|
|
5. Verify "Set Status" submenu works
|
|
6. Verify "Assign To" submenu works
|
|
7. Verify menu closes when clicking outside
|
|
8. 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:**
|
|
```vue
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger>Set Status</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent>
|
|
<DropdownMenuItem @click="...">...</DropdownMenuItem>
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
```
|
|
|
|
**After:**
|
|
```vue
|
|
<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
|
|
|
|
1. ✅ No dependency on DropdownMenu context
|
|
2. ✅ Simpler, more maintainable code
|
|
3. ✅ Better performance (fewer components)
|
|
4. ✅ Same visual appearance
|
|
5. ✅ Full control over styling and behavior
|