[WEB-2390] fix: Clickable Area for Issue List Layout Item (#5536)

* Updated control block to cover the whole element

* Updated the control link to cover the whole issues and relation blocks

* updated word wrap in notifications

* Reverted break words as its a different issue.
This commit is contained in:
Mihir 2024-09-23 16:36:58 +05:30 committed by GitHub
parent 45fded9842
commit ed39f2dc37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 362 additions and 361 deletions

View file

@ -17,7 +17,6 @@ import { IssueProperties } from "@/components/issues/issue-layouts/properties";
import { cn } from "@/helpers/common.helper";
// hooks
import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store";
import useIssuePeekOverviewRedirection from "@/hooks/use-issue-peek-overview-redirection";
import { TSelectionHelper } from "@/hooks/use-multiple-select";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components
@ -70,16 +69,21 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
// hooks
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
const { getProjectIdentifierById } = useProject();
const { getIsIssuePeeked, peekIssue, subIssues: subIssuesStore } = useIssueDetail();
const { handleRedirection } = useIssuePeekOverviewRedirection();
const { isMobile } = usePlatformOS();
const { getIsIssuePeeked, peekIssue, setPeekIssue, subIssues: subIssuesStore } = useIssueDetail();
// handlers
const handleIssuePeekOverview = (issue: TIssue) => handleRedirection(workspaceSlug, issue, isMobile, nestingLevel);
const handleIssuePeekOverview = (issue: TIssue) =>
workspaceSlug &&
issue &&
issue.project_id &&
issue.id &&
!getIsIssuePeeked(issue.id) &&
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id, nestingLevel: nestingLevel });
const issue = issuesMap[issueId];
const subIssuesCount = issue?.sub_issues_count ?? 0;
const { isMobile } = usePlatformOS();
useEffect(() => {
const element = issueRef.current;
@ -126,155 +130,128 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
};
//TODO: add better logic. This is to have a min width for ID/Key based on the length of project identifier
const keyMinWidth = displayProperties?.key ? (projectIdentifier?.length ?? 0) * 7 : 0;
const keyMinWidth = (projectIdentifier?.length ?? 0) * 7;
return (
<Row
ref={issueRef}
className={cn(
"group/list-block min-h-11 relative flex flex-col gap-3 bg-custom-background-100 hover:bg-custom-background-90 py-3 text-sm transition-colors border border-transparent",
{
"border-custom-primary-70": getIsIssuePeeked(issue.id) && peekIssue?.nestingLevel === nestingLevel,
"border-custom-border-400": isIssueActive,
"last:border-b-transparent": !getIsIssuePeeked(issue.id) && !isIssueActive,
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
"bg-custom-background-80": isCurrentBlockDragging,
"md:flex-row md:items-center": isSidebarCollapsed,
"lg:flex-row lg:items-center": !isSidebarCollapsed,
}
)}
onDragStart={() => {
if (!canDrag) {
setToast({
type: TOAST_TYPE.WARNING,
title: "Cannot move issue",
message: "Drag and drop is disabled for the current grouping",
});
}
}}
<ControlLink
id={`issue-${issue.id}`}
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${issue.id}`}
onClick={() => handleIssuePeekOverview(issue)}
className="w-full truncate cursor-pointer text-sm text-custom-text-100"
disabled={!!issue?.tempId}
>
<div className="flex w-full truncate">
<div className="flex flex-grow items-center gap-0.5 truncate">
<div className="flex items-center gap-1" style={isSubIssue ? { marginLeft } : {}}>
{/* select checkbox */}
{projectId && canSelectIssues && (
<Tooltip
tooltipContent={
<>
Only issues within the current
<br />
project can be selected.
</>
}
disabled={issue.project_id === projectId}
>
<div className="flex-shrink-0 grid place-items-center w-3.5 absolute left-1">
<MultipleSelectEntityAction
className={cn(
"opacity-0 pointer-events-none group-hover/list-block:opacity-100 group-hover/list-block:pointer-events-auto transition-opacity",
{
"opacity-100 pointer-events-auto": isIssueSelected,
}
)}
groupId={groupId}
id={issue.id}
selectionHelpers={selectionHelpers}
disabled={issue.project_id !== projectId}
/>
<Row
ref={issueRef}
className={cn(
"group/list-block min-h-11 relative flex flex-col gap-3 bg-custom-background-100 hover:bg-custom-background-90 py-3 text-sm transition-colors border border-transparent",
{
"border-custom-primary-70": getIsIssuePeeked(issue.id) && peekIssue?.nestingLevel === nestingLevel,
"border-custom-border-400": isIssueActive,
"last:border-b-transparent": !getIsIssuePeeked(issue.id) && !isIssueActive,
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
"bg-custom-background-80": isCurrentBlockDragging,
"md:flex-row md:items-center": isSidebarCollapsed,
"lg:flex-row lg:items-center": !isSidebarCollapsed,
}
)}
onDragStart={() => {
if (!canDrag) {
setToast({
type: TOAST_TYPE.WARNING,
title: "Cannot move issue",
message: "Drag and drop is disabled for the current grouping",
});
}
}}
>
<div className="flex w-full truncate">
<div className="flex flex-grow items-center gap-0.5 truncate">
<div className="flex items-center gap-1" style={isSubIssue ? { marginLeft } : {}}>
{/* select checkbox */}
{projectId && canSelectIssues && (
<Tooltip
tooltipContent={
<>
Only issues within the current
<br />
project can be selected.
</>
}
disabled={issue.project_id === projectId}
renderByDefault={false}
>
<div className="flex-shrink-0 grid place-items-center w-3.5 absolute left-1">
<MultipleSelectEntityAction
className={cn(
"opacity-0 pointer-events-none group-hover/list-block:opacity-100 group-hover/list-block:pointer-events-auto transition-opacity",
{
"opacity-100 pointer-events-auto": isIssueSelected,
}
)}
groupId={groupId}
id={issue.id}
selectionHelpers={selectionHelpers}
disabled={issue.project_id !== projectId}
/>
</div>
</Tooltip>
)}
{displayProperties && displayProperties?.key && (
<div className="flex-shrink-0" style={{ minWidth: `${keyMinWidth}px` }}>
{issue.project_id && (
<IssueIdentifier
issueId={issueId}
projectId={issue.project_id}
textContainerClassName="text-xs font-medium text-custom-text-300"
/>
)}
</div>
</Tooltip>
)}
{displayProperties && (displayProperties.key || displayProperties.issue_type) && (
<div className="flex-shrink-0" style={{ minWidth: `${keyMinWidth}px` }}>
{issue.project_id && (
<IssueIdentifier
issueId={issueId}
projectId={issue.project_id}
textContainerClassName="text-xs font-medium text-custom-text-300"
displayProperties={displayProperties}
/>
)}
{/* sub-issues chevron */}
<div className="size-4 grid place-items-center flex-shrink-0">
{subIssuesCount > 0 && (
<button
type="button"
className="size-4 grid place-items-center rounded-sm text-custom-text-400 hover:text-custom-text-300"
onClick={handleToggleExpand}
>
<ChevronRight
className={cn("size-4", {
"rotate-90": isExpanded,
})}
strokeWidth={2.5}
/>
</button>
)}
</div>
)}
{/* sub-issues chevron */}
<div className="size-4 grid place-items-center flex-shrink-0">
{subIssuesCount > 0 && (
<button
type="button"
className="size-4 grid place-items-center rounded-sm text-custom-text-400 hover:text-custom-text-300"
onClick={handleToggleExpand}
>
<ChevronRight
className={cn("size-4", {
"rotate-90": isExpanded,
})}
strokeWidth={2.5}
/>
</button>
{issue?.tempId !== undefined && (
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
)}
</div>
{issue?.tempId !== undefined && (
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
)}
</div>
{issue?.is_draft ? (
<Tooltip
tooltipContent={issue.name}
isMobile={isMobile}
position="top-left"
disabled={isCurrentBlockDragging}
renderByDefault={false}
>
<p className="truncate">{issue.name}</p>
</Tooltip>
) : (
<ControlLink
id={`issue-${issue.id}`}
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
issue.id
}`}
onClick={() => handleIssuePeekOverview(issue)}
className="w-full truncate cursor-pointer text-sm text-custom-text-100"
disabled={!!issue?.tempId}
>
{issue?.is_draft ? (
<Tooltip
tooltipContent={issue.name}
isMobile={isMobile}
position="top-left"
disabled={isCurrentBlockDragging}
renderByDefault={false}
>
<p className="truncate">{issue.name}</p>
</Tooltip>
) : (
<Tooltip tooltipContent={issue.name} isMobile={isMobile} position="top-left" renderByDefault={false}>
<p className="truncate">{issue.name}</p>
</Tooltip>
</ControlLink>
)}
</div>
{!issue?.tempId && (
<div
className={cn("block border border-custom-border-300 rounded", {
"md:hidden": isSidebarCollapsed,
"lg:hidden": !isSidebarCollapsed,
})}
>
{quickActions({
issue,
parentRef: issueRef,
})}
)}
</div>
)}
</div>
<div className="flex flex-shrink-0 items-center gap-2">
{!issue?.tempId ? (
<>
<IssueProperties
className={`relative flex flex-wrap ${isSidebarCollapsed ? "md:flex-grow md:flex-shrink-0" : "lg:flex-grow lg:flex-shrink-0"} items-center gap-2 whitespace-nowrap`}
issue={issue}
isReadOnly={!canEditIssueProperties}
updateIssue={updateIssue}
displayProperties={displayProperties}
activeLayout="List"
/>
{!issue?.tempId && (
<div
className={cn("hidden", {
"md:flex": isSidebarCollapsed,
"lg:flex": !isSidebarCollapsed,
className={cn("block border border-custom-border-300 rounded", {
"md:hidden": isSidebarCollapsed,
"lg:hidden": !isSidebarCollapsed,
})}
>
{quickActions({
@ -282,13 +259,38 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
parentRef: issueRef,
})}
</div>
</>
) : (
<div className="h-4 w-4">
<Spinner className="h-4 w-4" />
</div>
)}
</div>
</Row>
)}
</div>
<div className="flex flex-shrink-0 items-center gap-2">
{!issue?.tempId ? (
<>
<IssueProperties
className={`relative flex flex-wrap ${isSidebarCollapsed ? "md:flex-grow md:flex-shrink-0" : "lg:flex-grow lg:flex-shrink-0"} items-center gap-2 whitespace-nowrap`}
issue={issue}
isReadOnly={!canEditIssueProperties}
updateIssue={updateIssue}
displayProperties={displayProperties}
activeLayout="List"
/>
<div
className={cn("hidden", {
"md:flex": isSidebarCollapsed,
"lg:flex": !isSidebarCollapsed,
})}
>
{quickActions({
issue,
parentRef: issueRef,
})}
</div>
</>
) : (
<div className="h-4 w-4">
<Spinner className="h-4 w-4" />
</div>
)}
</div>
</Row>
</ControlLink>
);
});
});

View file

@ -84,86 +84,86 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
return (
<div key={relationIssueId}>
{issue && (
<div className="group relative flex min-h-11 h-full w-full items-center gap-3 px-1.5 py-1 transition-all hover:bg-custom-background-90">
<span className="size-5 flex-shrink-0" />
<div className="flex w-full cursor-pointer items-center gap-3">
<div
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{
backgroundColor: currentIssueStateDetail?.color ?? "#737373",
}}
/>
<div className="flex-shrink-0">
{projectDetail && (
<IssueIdentifier
projectId={projectDetail.id}
issueTypeId={issue.type_id}
projectIdentifier={projectDetail.identifier}
issueSequenceId={issue.sequence_id}
textContainerClassName="text-xs text-custom-text-200"
/>
)}
</div>
<ControlLink
id={`issue-${issue.id}`}
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
onClick={() => handleIssuePeekOverview(issue)}
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
>
{issue && (
<div className="group relative flex min-h-11 h-full w-full items-center gap-3 px-1.5 py-1 transition-all hover:bg-custom-background-90">
<span className="size-5 flex-shrink-0" />
<div className="flex w-full cursor-pointer items-center gap-3">
<div
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{
backgroundColor: currentIssueStateDetail?.color ?? "#737373",
}}
/>
<div className="flex-shrink-0">
{projectDetail && (
<IssueIdentifier
projectId={projectDetail.id}
issueTypeId={issue.type_id}
projectIdentifier={projectDetail.identifier}
issueSequenceId={issue.sequence_id}
textContainerClassName="text-xs text-custom-text-200"
/>
)}
</div>
<ControlLink
id={`issue-${issue.id}`}
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
onClick={() => handleIssuePeekOverview(issue)}
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
>
<Tooltip tooltipContent={issue.name} isMobile={isMobile}>
<span>{issue.name}</span>
</Tooltip>
</ControlLink>
</div>
<div className="flex-shrink-0 text-sm">
<RelationIssueProperty
workspaceSlug={workspaceSlug}
issueId={relationIssueId}
disabled={disabled}
issueOperations={issueOperations}
/>
</div>
<div className="flex-shrink-0 text-sm">
<CustomMenu placement="bottom-end" ellipsis>
{!disabled && (
<CustomMenu.MenuItem onClick={handleEditIssue}>
</div>
<div className="flex-shrink-0 text-sm">
<RelationIssueProperty
workspaceSlug={workspaceSlug}
issueId={relationIssueId}
disabled={disabled}
issueOperations={issueOperations}
/>
</div>
<div className="flex-shrink-0 text-sm">
<CustomMenu placement="bottom-end" ellipsis>
{!disabled && (
<CustomMenu.MenuItem onClick={handleEditIssue}>
<div className="flex items-center gap-2">
<Pencil className="h-3.5 w-3.5" strokeWidth={2} />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem onClick={handleCopyIssueLink}>
<div className="flex items-center gap-2">
<Pencil className="h-3.5 w-3.5" strokeWidth={2} />
<span>Edit issue</span>
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
<span>Copy issue link</span>
</div>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem onClick={handleCopyIssueLink}>
<div className="flex items-center gap-2">
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
<span>Copy issue link</span>
</div>
</CustomMenu.MenuItem>
{!disabled && (
<CustomMenu.MenuItem onClick={handleRemoveRelation}>
<div className="flex items-center gap-2">
<X className="h-3.5 w-3.5" strokeWidth={2} />
<span>Remove relation</span>
</div>
</CustomMenu.MenuItem>
)}
{!disabled && (
<CustomMenu.MenuItem onClick={handleRemoveRelation}>
<div className="flex items-center gap-2">
<X className="h-3.5 w-3.5" strokeWidth={2} />
<span>Remove relation</span>
</div>
</CustomMenu.MenuItem>
)}
{!disabled && (
<CustomMenu.MenuItem onClick={handleDeleteIssue}>
<div className="flex items-center gap-2">
<Trash className="h-3.5 w-3.5" strokeWidth={2} />
<span>Delete issue</span>
</div>
</CustomMenu.MenuItem>
)}
</CustomMenu>
{!disabled && (
<CustomMenu.MenuItem onClick={handleDeleteIssue}>
<div className="flex items-center gap-2">
<Trash className="h-3.5 w-3.5" strokeWidth={2} />
<span>Delete issue</span>
</div>
</CustomMenu.MenuItem>
)}
</CustomMenu>
</div>
</div>
</div>
)}
)}
</ControlLink>
</div>
);
});

View file

@ -82,159 +82,158 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
return (
<div key={issueId}>
{issue && (
<div
className="group relative flex min-h-11 h-full w-full items-center gap-3 pr-2 py-1 transition-all hover:bg-custom-background-90"
style={{ paddingLeft: `${spacingLeft}px` }}
>
<div className="flex size-5 items-center justify-center flex-shrink-0">
{/* disable the chevron when current issue is also the root issue*/}
{subIssueCount > 0 && !isCurrentIssueRoot && (
<>
{subIssueHelpers.preview_loader.includes(issue.id) ? (
<div className="flex h-full w-full cursor-not-allowed items-center justify-center rounded-sm bg-custom-background-80 transition-all">
<Loader width={14} strokeWidth={2} className="animate-spin" />
</div>
) : (
<div
className="flex h-full w-full cursor-pointer items-center justify-center text-custom-text-400 hover:text-custom-text-300"
onClick={async () => {
if (!subIssueHelpers.issue_visibility.includes(issueId)) {
setSubIssueHelpers(parentIssueId, "preview_loader", issueId);
await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, issueId);
setSubIssueHelpers(parentIssueId, "preview_loader", issueId);
}
setSubIssueHelpers(parentIssueId, "issue_visibility", issueId);
}}
>
<ChevronRight
className={cn("size-3.5 transition-all", {
"rotate-90": subIssueHelpers.issue_visibility.includes(issue.id),
})}
strokeWidth={2.5}
/>
</div>
)}
</>
)}
</div>
<div className="flex w-full cursor-pointer items-center gap-3">
<div
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{
backgroundColor: currentIssueStateDetail?.color ?? "#737373",
}}
/>
<div className="flex-shrink-0">
{projectDetail && (
<IssueIdentifier
projectId={projectDetail.id}
issueTypeId={issue.type_id}
projectIdentifier={projectDetail.identifier}
issueSequenceId={issue.sequence_id}
textContainerClassName="text-xs text-custom-text-200"
/>
<ControlLink
id={`issue-${issue.id}`}
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
onClick={() => handleIssuePeekOverview(issue)}
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
>
{issue && (
<div
className="group relative flex min-h-11 h-full w-full items-center gap-3 pr-2 py-1 transition-all hover:bg-custom-background-90"
style={{ paddingLeft: `${spacingLeft}px` }}
>
<div className="flex size-5 items-center justify-center flex-shrink-0">
{/* disable the chevron when current issue is also the root issue*/}
{subIssueCount > 0 && !isCurrentIssueRoot && (
<>
{subIssueHelpers.preview_loader.includes(issue.id) ? (
<div className="flex h-full w-full cursor-not-allowed items-center justify-center rounded-sm bg-custom-background-80 transition-all">
<Loader width={14} strokeWidth={2} className="animate-spin" />
</div>
) : (
<div
className="flex h-full w-full cursor-pointer items-center justify-center text-custom-text-400 hover:text-custom-text-300"
onClick={async () => {
if (!subIssueHelpers.issue_visibility.includes(issueId)) {
setSubIssueHelpers(parentIssueId, "preview_loader", issueId);
await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, issueId);
setSubIssueHelpers(parentIssueId, "preview_loader", issueId);
}
setSubIssueHelpers(parentIssueId, "issue_visibility", issueId);
}}
>
<ChevronRight
className={cn("size-3.5 transition-all", {
"rotate-90": subIssueHelpers.issue_visibility.includes(issue.id),
})}
strokeWidth={2.5}
/>
</div>
)}
</>
)}
</div>
<ControlLink
id={`issue-${issue.id}`}
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
onClick={() => handleIssuePeekOverview(issue)}
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
>
<div className="flex w-full cursor-pointer items-center gap-3">
<div
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{
backgroundColor: currentIssueStateDetail?.color ?? "#737373",
}}
/>
<div className="flex-shrink-0">
{projectDetail && (
<IssueIdentifier
projectId={projectDetail.id}
issueTypeId={issue.type_id}
projectIdentifier={projectDetail.identifier}
issueSequenceId={issue.sequence_id}
textContainerClassName="text-xs text-custom-text-200"
/>
)}
</div>
<Tooltip tooltipContent={issue.name} isMobile={isMobile}>
<span>{issue.name}</span>
</Tooltip>
</ControlLink>
</div>
</div>
<div className="flex-shrink-0 text-sm">
<IssueProperty
<div className="flex-shrink-0 text-sm">
<IssueProperty
workspaceSlug={workspaceSlug}
parentIssueId={parentIssueId}
issueId={issueId}
disabled={disabled}
subIssueOperations={subIssueOperations}
/>
</div>
<div className="flex-shrink-0 text-sm">
<CustomMenu placement="bottom-end" ellipsis>
{disabled && (
<CustomMenu.MenuItem
onClick={() => {
handleIssueCrudState("update", parentIssueId, { ...issue });
toggleCreateIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Pencil className="h-3.5 w-3.5" strokeWidth={2} />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem
onClick={() =>
subIssueOperations.copyText(`${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`)
}
>
<div className="flex items-center gap-2">
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
<span>Copy issue link</span>
</div>
</CustomMenu.MenuItem>
{disabled && (
<CustomMenu.MenuItem
onClick={() => {
issue.project_id &&
subIssueOperations.removeSubIssue(workspaceSlug, issue.project_id, parentIssueId, issue.id);
}}
>
<div className="flex items-center gap-2">
<X className="h-3.5 w-3.5" strokeWidth={2} />
<span>Remove parent issue</span>
</div>
</CustomMenu.MenuItem>
)}
{disabled && (
<CustomMenu.MenuItem
onClick={() => {
handleIssueCrudState("delete", parentIssueId, issue);
toggleDeleteIssueModal(issue.id);
}}
>
<div className="flex items-center gap-2">
<Trash className="h-3.5 w-3.5" strokeWidth={2} />
<span>Delete issue</span>
</div>
</CustomMenu.MenuItem>
)}
</CustomMenu>
</div>
</div>
)}
{/* should not expand the current issue if it is also the root issue*/}
{subIssueHelpers.issue_visibility.includes(issueId) &&
issue.project_id &&
subIssueCount > 0 &&
!isCurrentIssueRoot && (
<IssueList
workspaceSlug={workspaceSlug}
parentIssueId={parentIssueId}
issueId={issueId}
projectId={issue.project_id}
parentIssueId={issue.id}
rootIssueId={rootIssueId}
spacingLeft={spacingLeft + 22}
disabled={disabled}
handleIssueCrudState={handleIssueCrudState}
subIssueOperations={subIssueOperations}
/>
</div>
<div className="flex-shrink-0 text-sm">
<CustomMenu placement="bottom-end" ellipsis>
{disabled && (
<CustomMenu.MenuItem
onClick={() => {
handleIssueCrudState("update", parentIssueId, { ...issue });
toggleCreateIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Pencil className="h-3.5 w-3.5" strokeWidth={2} />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem
onClick={() =>
subIssueOperations.copyText(`${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`)
}
>
<div className="flex items-center gap-2">
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
<span>Copy issue link</span>
</div>
</CustomMenu.MenuItem>
{disabled && (
<CustomMenu.MenuItem
onClick={() => {
issue.project_id &&
subIssueOperations.removeSubIssue(workspaceSlug, issue.project_id, parentIssueId, issue.id);
}}
>
<div className="flex items-center gap-2">
<X className="h-3.5 w-3.5" strokeWidth={2} />
<span>Remove parent issue</span>
</div>
</CustomMenu.MenuItem>
)}
{disabled && (
<CustomMenu.MenuItem
onClick={() => {
handleIssueCrudState("delete", parentIssueId, issue);
toggleDeleteIssueModal(issue.id);
}}
>
<div className="flex items-center gap-2">
<Trash className="h-3.5 w-3.5" strokeWidth={2} />
<span>Delete issue</span>
</div>
</CustomMenu.MenuItem>
)}
</CustomMenu>
</div>
</div>
)}
{/* should not expand the current issue if it is also the root issue*/}
{subIssueHelpers.issue_visibility.includes(issueId) &&
issue.project_id &&
subIssueCount > 0 &&
!isCurrentIssueRoot && (
<IssueList
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
parentIssueId={issue.id}
rootIssueId={rootIssueId}
spacingLeft={spacingLeft + 22}
disabled={disabled}
handleIssueCrudState={handleIssueCrudState}
subIssueOperations={subIssueOperations}
/>
)}
)}
</ControlLink>
</div>
);
});