[WEB-1204] fix: Kanban and calendar drag and drop in mobile (#4408)

* chore: don't show context menu on mobile devices

* fix: drag and drop in mobile

* chore: default show more options in mobile

* fix: dnd in calendar layout
This commit is contained in:
Aaryan Khandelwal 2024-05-16 23:42:54 +05:30 committed by GitHub
parent 33079c826d
commit 26188f208b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 34 additions and 25 deletions

View File

@ -38,3 +38,5 @@ export const ControlLink = React.forwardRef<HTMLAnchorElement, TControlLink>((pr
</a> </a>
); );
}); });
ControlLink.displayName = "ControlLink";

View File

@ -6,6 +6,7 @@ import { ContextMenuItem } from "./item";
import { cn } from "../../../helpers"; import { cn } from "../../../helpers";
// hooks // hooks
import useOutsideClickDetector from "../../hooks/use-outside-click-detector"; import useOutsideClickDetector from "../../hooks/use-outside-click-detector";
import { usePlatformOS } from "../../hooks/use-platform-os";
export type TContextMenuItem = { export type TContextMenuItem = {
key: string; key: string;
@ -38,6 +39,7 @@ const ContextMenuWithoutPortal: React.FC<ContextMenuProps> = (props) => {
const contextMenuRef = useRef<HTMLDivElement>(null); const contextMenuRef = useRef<HTMLDivElement>(null);
// derived values // derived values
const renderedItems = items.filter((item) => item.shouldRender !== false); const renderedItems = items.filter((item) => item.shouldRender !== false);
const { isMobile } = usePlatformOS();
const handleClose = () => { const handleClose = () => {
setIsOpen(false); setIsOpen(false);
@ -51,6 +53,8 @@ const ContextMenuWithoutPortal: React.FC<ContextMenuProps> = (props) => {
if (!parentElement || !contextMenu) return; if (!parentElement || !contextMenu) return;
const handleContextMenu = (e: MouseEvent) => { const handleContextMenu = (e: MouseEvent) => {
if (isMobile) return;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -83,7 +87,7 @@ const ContextMenuWithoutPortal: React.FC<ContextMenuProps> = (props) => {
parentElement.removeEventListener("contextmenu", handleContextMenu); parentElement.removeEventListener("contextmenu", handleContextMenu);
window.removeEventListener("keydown", hideContextMenu); window.removeEventListener("keydown", hideContextMenu);
}; };
}, [contextMenuRef, isOpen, parentRef, setIsOpen, setPosition]); }, [contextMenuRef, isMobile, isOpen, parentRef, setIsOpen, setPosition]);
// handle keyboard navigation // handle keyboard navigation
useEffect(() => { useEffect(() => {

View File

@ -0,0 +1,15 @@
import { useEffect, useState } from "react";
export const usePlatformOS = () => {
// states
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const userAgent = window.navigator.userAgent;
const isMobile = /iPhone|iPad|iPod|Android/i.test(userAgent);
if (isMobile) setIsMobile(isMobile);
}, []);
return { isMobile };
};

View File

@ -71,7 +71,7 @@ export const CalendarIssueBlock = observer(
target="_blank" target="_blank"
onClick={() => handleIssuePeekOverview(issue)} onClick={() => handleIssuePeekOverview(issue)}
className="block w-full text-sm text-custom-text-100 rounded border-b md:border-[1px] border-custom-border-200 hover:border-custom-border-400" className="block w-full text-sm text-custom-text-100 rounded border-b md:border-[1px] border-custom-border-200 hover:border-custom-border-400"
disabled={!!issue?.tempId} disabled={!!issue?.tempId || isMobile}
ref={ref} ref={ref}
> >
<> <>
@ -105,9 +105,10 @@ export const CalendarIssueBlock = observer(
</Tooltip> </Tooltip>
</div> </div>
<div <div
className={`flex-shrink-0 md:hidden h-5 w-5 group-hover/calendar-block:block ${ className={cn("flex-shrink-0 size-5", {
isMenuActive ? "!block" : "" "hidden group-hover/calendar-block:block": !isMobile,
}`} block: isMenuActive,
})}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();

View File

@ -63,7 +63,9 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
{getProjectIdentifierById(issue.project_id)}-{issue.sequence_id} {getProjectIdentifierById(issue.project_id)}-{issue.sequence_id}
</div> </div>
<div <div
className="absolute -top-1 right-0 hidden group-hover/kanban-block:block" className={cn("absolute -top-1 right-0", {
"hidden group-hover/kanban-block:block": !isMobile,
})}
onClick={handleEventPropagation} onClick={handleEventPropagation}
> >
{quickActions({ {quickActions({
@ -116,6 +118,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
// hooks // hooks
const { workspaceSlug } = useAppRouter(); const { workspaceSlug } = useAppRouter();
const { getIsIssuePeeked, setPeekIssue } = useIssueDetail(); const { getIsIssuePeeked, setPeekIssue } = useIssueDetail();
const { isMobile } = usePlatformOS();
const handleIssuePeekOverview = (issue: TIssue) => const handleIssuePeekOverview = (issue: TIssue) =>
workspaceSlug && workspaceSlug &&
@ -210,7 +213,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
)} )}
target="_blank" target="_blank"
onClick={() => handleIssuePeekOverview(issue)} onClick={() => handleIssuePeekOverview(issue)}
disabled={!!issue?.tempId} disabled={!!issue?.tempId || isMobile}
> >
<RenderIfVisible <RenderIfVisible
classNames="space-y-2 px-3 py-2" classNames="space-y-2 px-3 py-2"

View File

@ -7945,16 +7945,7 @@ streamx@^2.15.0, streamx@^2.16.1:
optionalDependencies: optionalDependencies:
bare-events "^2.2.0" bare-events "^2.2.0"
"string-width-cjs@npm:string-width@^4.2.0": "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -8034,14 +8025,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1" is-obj "^1.0.1"
is-regexp "^1.0.0" is-regexp "^1.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==