forked from github/plane
154 lines
4.8 KiB
TypeScript
154 lines
4.8 KiB
TypeScript
|
import { FC } from "react";
|
||
|
import { useRouter } from "next/router";
|
||
|
import { observer } from "mobx-react";
|
||
|
import { MoveRight, MoveDiagonal, Link2, Trash2 } from "lucide-react";
|
||
|
// ui
|
||
|
import { CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon } from "@plane/ui";
|
||
|
// helpers
|
||
|
import { copyUrlToClipboard } from "helpers/string.helper";
|
||
|
// hooks
|
||
|
import useToast from "hooks/use-toast";
|
||
|
// store hooks
|
||
|
import { useUser } from "hooks/store";
|
||
|
// components
|
||
|
import { IssueSubscription, IssueUpdateStatus } from "components/issues";
|
||
|
|
||
|
export type TPeekModes = "side-peek" | "modal" | "full-screen";
|
||
|
|
||
|
const PEEK_OPTIONS: { key: TPeekModes; icon: any; title: string }[] = [
|
||
|
{
|
||
|
key: "side-peek",
|
||
|
icon: SidePanelIcon,
|
||
|
title: "Side Peek",
|
||
|
},
|
||
|
{
|
||
|
key: "modal",
|
||
|
icon: CenterPanelIcon,
|
||
|
title: "Modal",
|
||
|
},
|
||
|
{
|
||
|
key: "full-screen",
|
||
|
icon: FullScreenPanelIcon,
|
||
|
title: "Full Screen",
|
||
|
},
|
||
|
];
|
||
|
|
||
|
export type PeekOverviewHeaderProps = {
|
||
|
peekMode: TPeekModes;
|
||
|
setPeekMode: (value: TPeekModes) => void;
|
||
|
removeRoutePeekId: () => void;
|
||
|
workspaceSlug: string;
|
||
|
projectId: string;
|
||
|
issueId: string;
|
||
|
isArchived: boolean;
|
||
|
disabled: boolean;
|
||
|
toggleDeleteIssueModal: (value: boolean) => void;
|
||
|
isSubmitting: "submitting" | "submitted" | "saved";
|
||
|
};
|
||
|
|
||
|
export const IssuePeekOverviewHeader: FC<PeekOverviewHeaderProps> = observer((props) => {
|
||
|
const {
|
||
|
peekMode,
|
||
|
setPeekMode,
|
||
|
workspaceSlug,
|
||
|
projectId,
|
||
|
issueId,
|
||
|
isArchived,
|
||
|
disabled,
|
||
|
removeRoutePeekId,
|
||
|
toggleDeleteIssueModal,
|
||
|
isSubmitting,
|
||
|
} = props;
|
||
|
// router
|
||
|
const router = useRouter();
|
||
|
// store hooks
|
||
|
const { currentUser } = useUser();
|
||
|
// hooks
|
||
|
const { setToastAlert } = useToast();
|
||
|
// derived values
|
||
|
const currentMode = PEEK_OPTIONS.find((m) => m.key === peekMode);
|
||
|
|
||
|
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||
|
e.stopPropagation();
|
||
|
e.preventDefault();
|
||
|
copyUrlToClipboard(
|
||
|
`${workspaceSlug}/projects/${projectId}/${isArchived ? "archived-issues" : "issues"}/${issueId}`
|
||
|
).then(() => {
|
||
|
setToastAlert({
|
||
|
type: "success",
|
||
|
title: "Link Copied!",
|
||
|
message: "Issue link copied to clipboard.",
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
const redirectToIssueDetail = () => {
|
||
|
router.push({
|
||
|
pathname: `/${workspaceSlug}/projects/${projectId}/${isArchived ? "archived-issues" : "issues"}/${issueId}`,
|
||
|
});
|
||
|
removeRoutePeekId();
|
||
|
};
|
||
|
|
||
|
return (
|
||
|
<div
|
||
|
className={`relative flex items-center justify-between p-4 ${
|
||
|
currentMode?.key === "full-screen" ? "border-b border-custom-border-200" : ""
|
||
|
}`}
|
||
|
>
|
||
|
<div className="flex items-center gap-4">
|
||
|
<button onClick={removeRoutePeekId}>
|
||
|
<MoveRight className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200" />
|
||
|
</button>
|
||
|
|
||
|
<button onClick={redirectToIssueDetail}>
|
||
|
<MoveDiagonal className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200" />
|
||
|
</button>
|
||
|
{currentMode && (
|
||
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||
|
<CustomSelect
|
||
|
value={currentMode}
|
||
|
onChange={(val: any) => setPeekMode(val)}
|
||
|
customButton={
|
||
|
<button type="button" className="">
|
||
|
<currentMode.icon className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200" />
|
||
|
</button>
|
||
|
}
|
||
|
>
|
||
|
{PEEK_OPTIONS.map((mode) => (
|
||
|
<CustomSelect.Option key={mode.key} value={mode.key}>
|
||
|
<div
|
||
|
className={`flex items-center gap-1.5 ${
|
||
|
currentMode.key === mode.key
|
||
|
? "text-custom-text-200"
|
||
|
: "text-custom-text-400 hover:text-custom-text-200"
|
||
|
}`}
|
||
|
>
|
||
|
<mode.icon className="-my-1 h-4 w-4 flex-shrink-0" />
|
||
|
{mode.title}
|
||
|
</div>
|
||
|
</CustomSelect.Option>
|
||
|
))}
|
||
|
</CustomSelect>
|
||
|
</div>
|
||
|
)}
|
||
|
</div>
|
||
|
<div className="flex items-center gap-x-4">
|
||
|
<IssueUpdateStatus isSubmitting={isSubmitting} />
|
||
|
<div className="flex items-center gap-4">
|
||
|
{currentUser && !isArchived && (
|
||
|
<IssueSubscription workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} />
|
||
|
)}
|
||
|
<button onClick={handleCopyText}>
|
||
|
<Link2 className="h-4 w-4 -rotate-45 text-custom-text-300 hover:text-custom-text-200" />
|
||
|
</button>
|
||
|
{!disabled && (
|
||
|
<button onClick={() => toggleDeleteIssueModal(true)}>
|
||
|
<Trash2 className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200" />
|
||
|
</button>
|
||
|
)}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
);
|
||
|
});
|