Init Repo
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user