forked from github/plane
[WEB-905] chore: issue peek overview and kanban layout improvement (#4135)
* chore: peek overview and kanban card improvement * chore: peek overview improvement
This commit is contained in:
parent
fd2cacb0cd
commit
986f81e3ae
@ -41,6 +41,7 @@ export const CalendarIssueBlock: React.FC<Props> = observer((props) => {
|
|||||||
issue &&
|
issue &&
|
||||||
issue.project_id &&
|
issue.project_id &&
|
||||||
issue.id &&
|
issue.id &&
|
||||||
|
peekIssue?.issueId !== issue.id &&
|
||||||
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
||||||
|
|
||||||
useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false));
|
useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false));
|
||||||
@ -64,6 +65,7 @@ export const CalendarIssueBlock: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ControlLink
|
<ControlLink
|
||||||
|
id={`issue-${issue.id}`}
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
|
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={() => handleIssuePeekOverview(issue)}
|
onClick={() => handleIssuePeekOverview(issue)}
|
||||||
|
@ -20,6 +20,7 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
|
|||||||
const { getProjectStates } = useProjectState();
|
const { getProjectStates } = useProjectState();
|
||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
|
peekIssue,
|
||||||
setPeekIssue,
|
setPeekIssue,
|
||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
// derived values
|
// derived values
|
||||||
@ -31,11 +32,13 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
|
|||||||
workspaceSlug &&
|
workspaceSlug &&
|
||||||
issueDetails &&
|
issueDetails &&
|
||||||
!issueDetails.tempId &&
|
!issueDetails.tempId &&
|
||||||
|
peekIssue?.issueId !== issueDetails.id &&
|
||||||
setPeekIssue({ workspaceSlug, projectId: issueDetails.project_id, issueId: issueDetails.id });
|
setPeekIssue({ workspaceSlug, projectId: issueDetails.project_id, issueId: issueDetails.id });
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
id={`issue-${issueId}`}
|
||||||
className="relative flex h-full w-full cursor-pointer items-center rounded"
|
className="relative flex h-full w-full cursor-pointer items-center rounded"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: stateDetails?.color,
|
backgroundColor: stateDetails?.color,
|
||||||
|
@ -47,13 +47,14 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
|
|||||||
const {
|
const {
|
||||||
router: { workspaceSlug },
|
router: { workspaceSlug },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
const { setPeekIssue } = useIssueDetail();
|
const { peekIssue, setPeekIssue } = useIssueDetail();
|
||||||
|
|
||||||
const handleIssuePeekOverview = (issue: TIssue) =>
|
const handleIssuePeekOverview = (issue: TIssue) =>
|
||||||
workspaceSlug &&
|
workspaceSlug &&
|
||||||
issue &&
|
issue &&
|
||||||
issue.project_id &&
|
issue.project_id &&
|
||||||
issue.id &&
|
issue.id &&
|
||||||
|
peekIssue?.issueId !== issue.id &&
|
||||||
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -69,16 +70,17 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
|
|||||||
|
|
||||||
{issue?.is_draft ? (
|
{issue?.is_draft ? (
|
||||||
<Tooltip tooltipContent={issue.name} isMobile={isMobile}>
|
<Tooltip tooltipContent={issue.name} isMobile={isMobile}>
|
||||||
<span>{issue.name}</span>
|
<span className="pb-1.5">{issue.name}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<ControlLink
|
<ControlLink
|
||||||
|
id={`issue-${issue.id}`}
|
||||||
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
||||||
issue.id
|
issue.id
|
||||||
}`}
|
}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={() => handleIssuePeekOverview(issue)}
|
onClick={() => handleIssuePeekOverview(issue)}
|
||||||
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
|
className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100 pb-1.5"
|
||||||
disabled={!!issue?.tempId}
|
disabled={!!issue?.tempId}
|
||||||
>
|
>
|
||||||
<Tooltip tooltipContent={issue.name} isMobile={isMobile}>
|
<Tooltip tooltipContent={issue.name} isMobile={isMobile}>
|
||||||
@ -138,7 +140,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded border-[0.5px] w-full border-custom-border-200 bg-custom-background-100 text-sm transition-all hover:border-custom-border-400",
|
"rounded border-[0.5px] outline-[0.5px] outline-transparent w-full border-custom-border-200 bg-custom-background-100 text-sm transition-all hover:border-custom-border-400",
|
||||||
{ "hover:cursor-grab": !isDragDisabled },
|
{ "hover:cursor-grab": !isDragDisabled },
|
||||||
{ "border-custom-primary-100": snapshot.isDragging },
|
{ "border-custom-primary-100": snapshot.isDragging },
|
||||||
{ "border border-custom-primary-70 hover:border-custom-primary-70": peekIssueId === issue.id }
|
{ "border border-custom-primary-70 hover:border-custom-primary-70": peekIssueId === issue.id }
|
||||||
|
@ -34,6 +34,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlock
|
|||||||
issue &&
|
issue &&
|
||||||
issue.project_id &&
|
issue.project_id &&
|
||||||
issue.id &&
|
issue.id &&
|
||||||
|
peekIssue?.issueId !== issue.id &&
|
||||||
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
||||||
|
|
||||||
const issue = issuesMap[issueId];
|
const issue = issuesMap[issueId];
|
||||||
@ -71,6 +72,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = observer((props: IssueBlock
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<ControlLink
|
<ControlLink
|
||||||
|
id={`issue-${issue.id}`}
|
||||||
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
||||||
issue.id
|
issue.id
|
||||||
}`}
|
}`}
|
||||||
|
@ -154,10 +154,13 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
|
|||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
const menuActionRef = useRef<HTMLDivElement | null>(null);
|
const menuActionRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const handleIssuePeekOverview = (issue: TIssue) => {
|
const handleIssuePeekOverview = (issue: TIssue) =>
|
||||||
if (workspaceSlug && issue && issue.project_id && issue.id)
|
workspaceSlug &&
|
||||||
|
issue &&
|
||||||
|
issue.project_id &&
|
||||||
|
issue.id &&
|
||||||
|
peekIssue?.issueId !== issue.id &&
|
||||||
setPeekIssue({ workspaceSlug: workspaceSlug.toString(), projectId: issue.project_id, issueId: issue.id });
|
setPeekIssue({ workspaceSlug: workspaceSlug.toString(), projectId: issue.project_id, issueId: issue.id });
|
||||||
};
|
|
||||||
|
|
||||||
const { subIssues: subIssuesStore, issue } = useIssueDetail();
|
const { subIssues: subIssuesStore, issue } = useIssueDetail();
|
||||||
|
|
||||||
@ -240,6 +243,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</WithDisplayPropertiesHOC>
|
</WithDisplayPropertiesHOC>
|
||||||
<ControlLink
|
<ControlLink
|
||||||
|
id={`issue-${issueId}`}
|
||||||
href={`/${workspaceSlug}/projects/${issueDetail.project_id}/issues/${issueId}`}
|
href={`/${workspaceSlug}/projects/${issueDetail.project_id}/issues/${issueId}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={() => handleIssuePeekOverview(issueDetail)}
|
onClick={() => handleIssuePeekOverview(issueDetail)}
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail, useUser } from "@/hooks/store";
|
import { useIssueDetail, useUser } from "@/hooks/store";
|
||||||
import useKeypress from "@/hooks/use-keypress";
|
import useKeypress from "@/hooks/use-keypress";
|
||||||
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
|
import usePeekOverviewOutsideClickDetector from "@/hooks/use-peek-overview-outside-click";
|
||||||
// store hooks
|
// store hooks
|
||||||
import { IssueActivity } from "../issue-detail/issue-activity";
|
import { IssueActivity } from "../issue-detail/issue-activity";
|
||||||
import { SubIssuesRoot } from "../sub-issues";
|
import { SubIssuesRoot } from "../sub-issues";
|
||||||
@ -55,11 +55,15 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
|||||||
setPeekIssue(undefined);
|
setPeekIssue(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
useOutsideClickDetector(issuePeekOverviewRef, () => {
|
usePeekOverviewOutsideClickDetector(
|
||||||
|
issuePeekOverviewRef,
|
||||||
|
() => {
|
||||||
if (!isAnyModalOpen) {
|
if (!isAnyModalOpen) {
|
||||||
removeRoutePeekId();
|
removeRoutePeekId();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
issueId
|
||||||
|
);
|
||||||
const handleKeyDown = () => {
|
const handleKeyDown = () => {
|
||||||
const slashCommandDropdownElement = document.querySelector("#slash-command");
|
const slashCommandDropdownElement = document.querySelector("#slash-command");
|
||||||
const dropdownElement = document.activeElement?.tagName === "INPUT";
|
const dropdownElement = document.activeElement?.tagName === "INPUT";
|
||||||
|
@ -42,6 +42,7 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
peekIssue,
|
||||||
setPeekIssue,
|
setPeekIssue,
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
subIssues: { subIssueHelpersByIssueId, setSubIssueHelpers },
|
subIssues: { subIssueHelpersByIssueId, setSubIssueHelpers },
|
||||||
@ -64,6 +65,7 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
|
|||||||
issue &&
|
issue &&
|
||||||
issue.project_id &&
|
issue.project_id &&
|
||||||
issue.id &&
|
issue.id &&
|
||||||
|
peekIssue?.issueId !== issue.id &&
|
||||||
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
||||||
|
|
||||||
if (!issue) return <></>;
|
if (!issue) return <></>;
|
||||||
@ -117,6 +119,7 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ControlLink
|
<ControlLink
|
||||||
|
id={`issue-${issue.id}`}
|
||||||
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={() => handleIssuePeekOverview(issue)}
|
onClick={() => handleIssuePeekOverview(issue)}
|
||||||
|
29
web/hooks/use-peek-overview-outside-click.tsx
Normal file
29
web/hooks/use-peek-overview-outside-click.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
const usePeekOverviewOutsideClickDetector = (
|
||||||
|
ref: React.RefObject<HTMLElement>,
|
||||||
|
callback: () => void,
|
||||||
|
issueId: string
|
||||||
|
) => {
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||||
|
let targetElement = event.target as HTMLElement | null;
|
||||||
|
while (targetElement) {
|
||||||
|
if (targetElement.id === `issue-${issueId}`) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetElement = targetElement.parentElement;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("mousedown", handleClick);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClick);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default usePeekOverviewOutsideClickDetector;
|
Loading…
Reference in New Issue
Block a user