diff --git a/apps/app/components/core/views/board-view/single-issue.tsx b/apps/app/components/core/views/board-view/single-issue.tsx index 4a1f25fe0..b676e809c 100644 --- a/apps/app/components/core/views/board-view/single-issue.tsx +++ b/apps/app/components/core/views/board-view/single-issue.tsx @@ -86,7 +86,8 @@ export const SingleBoardIssue: React.FC = ({ }) => { // context menu const [contextMenu, setContextMenu] = useState(false); - const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 }); + const [contextMenuPosition, setContextMenuPosition] = useState(null); + const [isMenuActive, setIsMenuActive] = useState(false); const [isDropdownActive, setIsDropdownActive] = useState(false); @@ -201,7 +202,7 @@ export const SingleBoardIssue: React.FC = ({ return ( <> = ({ onContextMenu={(e) => { e.preventDefault(); setContextMenu(true); - setContextMenuPosition({ x: e.pageX, y: e.pageY }); + setContextMenuPosition(e); }} >
diff --git a/apps/app/components/core/views/list-view/single-issue.tsx b/apps/app/components/core/views/list-view/single-issue.tsx index 7d1cea37e..eafe74612 100644 --- a/apps/app/components/core/views/list-view/single-issue.tsx +++ b/apps/app/components/core/views/list-view/single-issue.tsx @@ -33,7 +33,7 @@ import { } from "@heroicons/react/24/outline"; import { LayerDiagonalIcon } from "components/icons"; // helpers -import { copyTextToClipboard, truncateText } from "helpers/string.helper"; +import { copyTextToClipboard } from "helpers/string.helper"; import { handleIssuesMutation } from "constants/issue"; // types import { ICurrentUserResponse, IIssue, IIssueViewProps, ISubIssueResponse, UserAuth } from "types"; @@ -71,7 +71,7 @@ export const SingleListIssue: React.FC = ({ }) => { // context menu const [contextMenu, setContextMenu] = useState(false); - const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 }); + const [contextMenuPosition, setContextMenuPosition] = useState(null); const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; @@ -167,7 +167,7 @@ export const SingleListIssue: React.FC = ({ return ( <> = ({ onContextMenu={(e) => { e.preventDefault(); setContextMenu(true); - setContextMenuPosition({ x: e.pageX, y: e.pageY }); + setContextMenuPosition(e); }} >
diff --git a/apps/app/components/ui/dropdowns/context-menu.tsx b/apps/app/components/ui/dropdowns/context-menu.tsx index d7ecb4de7..78df25ec9 100644 --- a/apps/app/components/ui/dropdowns/context-menu.tsx +++ b/apps/app/components/ui/dropdowns/context-menu.tsx @@ -1,47 +1,76 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; import Link from "next/link"; +// hooks +import useOutsideClickDetector from "hooks/use-outside-click-detector"; + type Props = { - position: { - x: number; - y: number; - }; + clickEvent: React.MouseEvent | null; children: React.ReactNode; title?: string | JSX.Element; isOpen: boolean; setIsOpen: React.Dispatch>; }; -const ContextMenu = ({ position, children, title, isOpen, setIsOpen }: Props) => { +const ContextMenu = ({ clickEvent, children, title, isOpen, setIsOpen }: Props) => { + const contextMenuRef = useRef(null); + + // Close the context menu when clicked outside + useOutsideClickDetector(contextMenuRef, () => { + if (isOpen) setIsOpen(false); + }); + useEffect(() => { const hideContextMenu = () => { if (isOpen) setIsOpen(false); }; - window.addEventListener("click", hideContextMenu); - window.addEventListener("keydown", (e: KeyboardEvent) => { + const escapeKeyEvent = (e: KeyboardEvent) => { if (e.key === "Escape") hideContextMenu(); - }); + }; + + window.addEventListener("click", hideContextMenu); + window.addEventListener("keydown", escapeKeyEvent); return () => { window.removeEventListener("click", hideContextMenu); - window.removeEventListener("keydown", hideContextMenu); + window.removeEventListener("keydown", escapeKeyEvent); }; }, [isOpen, setIsOpen]); + useEffect(() => { + const contextMenu = contextMenuRef.current; + + if (contextMenu && isOpen) { + const contextMenuWidth = contextMenu.clientWidth; + const contextMenuHeight = contextMenu.clientHeight; + + const clickX = clickEvent?.pageX || 0; + const clickY = clickEvent?.pageY || 0; + + let top = clickY; + // check if there's enough space at the bottom, otherwise show at the top + if (clickY + contextMenuHeight > window.innerHeight) top = clickY - contextMenuHeight; + + // check if there's enough space on the right, otherwise show on the left + let left = clickX; + if (clickX + contextMenuWidth > window.innerWidth) left = clickX - contextMenuWidth; + + contextMenu.style.top = `${top}px`; + contextMenu.style.left = `${left}px`; + } + }, [clickEvent, isOpen]); + return (
{title && (

diff --git a/apps/app/hooks/use-outside-click-detector.tsx b/apps/app/hooks/use-outside-click-detector.tsx index f20666f8c..5331d11c8 100644 --- a/apps/app/hooks/use-outside-click-detector.tsx +++ b/apps/app/hooks/use-outside-click-detector.tsx @@ -8,10 +8,10 @@ const useOutsideClickDetector = (ref: React.RefObject, callback: () }; useEffect(() => { - document.addEventListener("click", handleClick); + document.addEventListener("mousedown", handleClick); return () => { - document.removeEventListener("click", handleClick); + document.removeEventListener("mousedown", handleClick); }; }); };