fix: context menu dynamic positioning, multiple context menus opening issue (#1913)

* fix: context menu positioning

* fix: close already opened context menu on outside click
This commit is contained in:
Aaryan Khandelwal 2023-08-19 18:50:12 +05:30 committed by GitHub
parent 64b5ba196f
commit cebc8bdc8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 25 deletions

View File

@ -86,7 +86,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
}) => {
// context menu
const [contextMenu, setContextMenu] = useState(false);
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
const [contextMenuPosition, setContextMenuPosition] = useState<React.MouseEvent | null>(null);
const [isMenuActive, setIsMenuActive] = useState(false);
const [isDropdownActive, setIsDropdownActive] = useState(false);
@ -201,7 +202,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
return (
<>
<ContextMenu
position={contextMenuPosition}
clickEvent={contextMenuPosition}
title="Quick actions"
isOpen={contextMenu}
setIsOpen={setContextMenu}
@ -243,7 +244,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
onContextMenu={(e) => {
e.preventDefault();
setContextMenu(true);
setContextMenuPosition({ x: e.pageX, y: e.pageY });
setContextMenuPosition(e);
}}
>
<div className="flex flex-col justify-between gap-1.5 group/card relative select-none px-3.5 py-3 h-[118px]">

View File

@ -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<Props> = ({
}) => {
// context menu
const [contextMenu, setContextMenu] = useState(false);
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
const [contextMenuPosition, setContextMenuPosition] = useState<React.MouseEvent | null>(null);
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
@ -167,7 +167,7 @@ export const SingleListIssue: React.FC<Props> = ({
return (
<>
<ContextMenu
position={contextMenuPosition}
clickEvent={contextMenuPosition}
title="Quick actions"
isOpen={contextMenu}
setIsOpen={setContextMenu}
@ -199,7 +199,7 @@ export const SingleListIssue: React.FC<Props> = ({
onContextMenu={(e) => {
e.preventDefault();
setContextMenu(true);
setContextMenuPosition({ x: e.pageX, y: e.pageY });
setContextMenuPosition(e);
}}
>
<div className="flex-grow cursor-pointer min-w-[200px] whitespace-nowrap overflow-hidden overflow-ellipsis">

View File

@ -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<React.SetStateAction<boolean>>;
};
const ContextMenu = ({ position, children, title, isOpen, setIsOpen }: Props) => {
const ContextMenu = ({ clickEvent, children, title, isOpen, setIsOpen }: Props) => {
const contextMenuRef = useRef<HTMLDivElement>(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 (
<div
className={`fixed z-20 h-full w-full ${
className={`fixed z-50 top-0 left-0 h-full w-full ${
isOpen ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
}`}
>
<div
className={`fixed z-20 flex min-w-[8rem] flex-col items-stretch gap-1 rounded-md border border-custom-border-200 bg-custom-background-90 p-2 text-xs shadow-lg`}
style={{
left: `${position.x}px`,
top: `${position.y}px`,
}}
ref={contextMenuRef}
className={`fixed z-50 flex min-w-[8rem] flex-col items-stretch gap-1 rounded-md border border-custom-border-200 bg-custom-background-90 p-2 text-xs shadow-lg`}
>
{title && (
<h4 className="border-b border-custom-border-200 px-1 py-1 pb-2 text-[0.8rem] font-medium">

View File

@ -8,10 +8,10 @@ const useOutsideClickDetector = (ref: React.RefObject<HTMLElement>, callback: ()
};
useEffect(() => {
document.addEventListener("click", handleClick);
document.addEventListener("mousedown", handleClick);
return () => {
document.removeEventListener("click", handleClick);
document.removeEventListener("mousedown", handleClick);
};
});
};