[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:
Anmol Singh Bhatia 2024-04-08 18:38:05 +05:30 committed by GitHub
parent fd2cacb0cd
commit 986f81e3ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 63 additions and 14 deletions

View File

@ -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)}

View File

@ -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,

View File

@ -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 }

View File

@ -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
}`} }`}

View File

@ -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)}

View File

@ -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";

View File

@ -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)}

View 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;