From 573ad57b19467e50cc39a407a306a63a2f16e42a Mon Sep 17 00:00:00 2001 From: Malthe Hartmann Date: Tue, 24 Feb 2026 14:06:59 +0100 Subject: [PATCH] feat(projects): add right-click context menu to project cards - Add context menu with Rename, Duplicate, Info, Delete actions - Works in both grid and list view - Uses existing Radix UI ContextMenu components - Mirrors actions from the existing ProjectMenu dropdown Co-authored-by: Cursor --- apps/web/src/app/projects/page.tsx | 164 ++++++++++++++++++++++------- 1 file changed, 128 insertions(+), 36 deletions(-) diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index b855cefc..677f109b 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -46,6 +46,13 @@ import { } from "@hugeicons/core-free-icons"; import { OcVideoIcon } from "@opencut/ui/icons"; import { Label } from "@/components/ui/label"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; import { DropdownMenu, DropdownMenuCheckboxItem, @@ -622,9 +629,8 @@ function ProjectItem({ const listContent = (
{}} + onCheckedChange={() => { }} className="size-5 shrink-0" /> @@ -657,44 +663,130 @@ function ProjectItem({ const cardContent = isGridView ? gridContent : listContent; if (!isGridView) { - return
{listContent}
; + return ( + + +
{listContent}
+
+ +
+ ); } return ( -
- - {cardContent} - + + +
+ + {cardContent} + - {isGridView && ( - <> - event.preventDefault()} - onClick={(event) => { - handleCheckboxChange({ - checked: !isSelected, - shiftKey: event.shiftKey, - }); - }} - onCheckedChange={() => {}} - className={`absolute z-10 size-5 top-3 left-3 ${ - isSelected || isDropdownOpen - ? "opacity-100" - : "opacity-0 group-hover:opacity-100" - }`} - /> + {isGridView && ( + <> + event.preventDefault()} + onClick={(event) => { + handleCheckboxChange({ + checked: !isSelected, + shiftKey: event.shiftKey, + }); + }} + onCheckedChange={() => { }} + className={`absolute z-10 size-5 top-3 left-3 ${isSelected || isDropdownOpen + ? "opacity-100" + : "opacity-0 group-hover:opacity-100" + }`} + /> - {!isMultiSelect && ( - + {!isMultiSelect && ( + + )} + )} - - )} -
+
+ + + + ); +} + +function ProjectContextMenuContent({ project }: { project: TProjectMetadata }) { + const editor = useEditor(); + const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false); + + const handleRename = () => setIsRenameDialogOpen(true); + const handleDuplicate = async () => { + await duplicateProjects({ editor, ids: [project.id] }); + }; + const handleDeleteClick = () => setIsDeleteDialogOpen(true); + const handleInfoClick = () => setIsInfoDialogOpen(true); + + const handleDeleteConfirm = async () => { + await deleteProjects({ editor, ids: [project.id] }); + setIsDeleteDialogOpen(false); + }; + + return ( + <> + + } + onClick={handleRename} + > + Rename + + } + onClick={handleDuplicate} + > + Duplicate + + } + onClick={handleInfoClick} + > + Info + + + } + onClick={handleDeleteClick} + > + Delete + + + + { + await renameProject({ editor, id: project.id, name: newName }); + setIsRenameDialogOpen(false); + }} + /> + + + + + ); }