mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
[WEB-4471]fix: full screen view visibility (#7387)
* fix: full screen mode for analytics, work items peek and timeline chart * chore: added null check for portal elements
This commit is contained in:
@@ -11,6 +11,7 @@ export default function WorkspaceLayout({ children }: { children: React.ReactNod
|
||||
<CommandPalette />
|
||||
<WorkspaceAuthWrapper>
|
||||
<div className="relative flex flex-col h-full w-full overflow-hidden rounded-lg border border-custom-border-200">
|
||||
<div id="full-screen-portal" className="inset-0 absolute w-full" />
|
||||
<div className="relative flex size-full overflow-hidden">
|
||||
<ProjectAppSidebar />
|
||||
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// plane package imports
|
||||
import { createPortal } from "react-dom";
|
||||
import { ICycle, IModule, IProject } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
import { useAnalytics } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { WorkItemsModalMainContent } from "./content";
|
||||
@@ -23,6 +24,7 @@ export const WorkItemsModal: React.FC<Props> = observer((props) => {
|
||||
const [fullScreen, setFullScreen] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setFullScreen(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -30,50 +32,40 @@ export const WorkItemsModal: React.FC<Props> = observer((props) => {
|
||||
updateIsEpic(isEpic ?? false);
|
||||
}, [isEpic, updateIsEpic]);
|
||||
|
||||
return (
|
||||
<Transition.Root appear show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-30" onClose={handleClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="transition-transform duration-300"
|
||||
enterFrom="translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="transition-transform duration-200"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="translate-x-full"
|
||||
const portalContainer = document.getElementById("full-screen-portal") as HTMLElement;
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const content = (
|
||||
<div className={cn("inset-0 z-[25] h-full w-full overflow-y-auto", fullScreen ? "absolute" : "fixed")}>
|
||||
<div
|
||||
className={`right-0 top-0 z-20 h-full bg-custom-background-100 shadow-custom-shadow-md ${
|
||||
fullScreen ? "w-full p-2 absolute" : "w-full sm:w-full md:w-1/2 fixed"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex h-full flex-col overflow-hidden border-custom-border-200 bg-custom-background-100 text-left ${
|
||||
fullScreen ? "rounded-lg border" : "border-l"
|
||||
}`}
|
||||
>
|
||||
<div className="fixed inset-0 z-20 h-full w-full overflow-y-auto">
|
||||
<Dialog.Panel>
|
||||
<div
|
||||
className={`fixed right-0 top-0 z-20 h-full bg-custom-background-100 shadow-custom-shadow-md ${
|
||||
fullScreen ? "w-full p-2" : "w-full sm:w-full md:w-1/2"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex h-full flex-col overflow-hidden border-custom-border-200 bg-custom-background-100 text-left ${
|
||||
fullScreen ? "rounded-lg border" : "border-l"
|
||||
}`}
|
||||
>
|
||||
<WorkItemsModalHeader
|
||||
fullScreen={fullScreen}
|
||||
handleClose={handleClose}
|
||||
setFullScreen={setFullScreen}
|
||||
title={projectDetails?.name ?? ""}
|
||||
cycle={cycleDetails}
|
||||
module={moduleDetails}
|
||||
/>
|
||||
<WorkItemsModalMainContent
|
||||
fullScreen={fullScreen}
|
||||
projectDetails={projectDetails}
|
||||
cycleDetails={cycleDetails}
|
||||
moduleDetails={moduleDetails}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
<WorkItemsModalHeader
|
||||
fullScreen={fullScreen}
|
||||
handleClose={handleClose}
|
||||
setFullScreen={setFullScreen}
|
||||
title={projectDetails?.name ?? ""}
|
||||
cycle={cycleDetails}
|
||||
module={moduleDetails}
|
||||
/>
|
||||
<WorkItemsModalMainContent
|
||||
fullScreen={fullScreen}
|
||||
projectDetails={projectDetails}
|
||||
cycleDetails={cycleDetails}
|
||||
moduleDetails={moduleDetails}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return fullScreen && portalContainer ? createPortal(content, portalContainer) : content;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
// components
|
||||
@@ -176,10 +177,12 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||
scrollContainer.scrollLeft = scrollWidth;
|
||||
};
|
||||
|
||||
return (
|
||||
const portalContainer = document.getElementById("full-screen-portal") as HTMLElement;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
className={cn("relative flex flex-col h-full select-none rounded-sm bg-custom-background-100 shadow", {
|
||||
"fixed inset-0 z-30 bg-custom-background-100": fullScreenMode,
|
||||
"inset-0 z-[25] bg-custom-background-100": fullScreenMode,
|
||||
"border-[0.5px] border-custom-border-200": border,
|
||||
})}
|
||||
>
|
||||
@@ -217,4 +220,6 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return fullScreenMode && portalContainer ? createPortal(content, portalContainer) : content;
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import useKeypress from "@/hooks/use-keypress";
|
||||
import usePeekOverviewOutsideClickDetector from "@/hooks/use-peek-overview-outside-click";
|
||||
// store hooks
|
||||
import { IssueActivity } from "../issue-detail/issue-activity";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
interface IIssueView {
|
||||
workspaceSlug: string;
|
||||
@@ -108,15 +109,151 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
|
||||
const peekOverviewIssueClassName = cn(
|
||||
!embedIssue
|
||||
? "fixed z-20 flex flex-col overflow-hidden rounded border border-custom-border-200 bg-custom-background-100 transition-all duration-300"
|
||||
? "fixed z-[25] flex flex-col overflow-hidden rounded border border-custom-border-200 bg-custom-background-100 transition-all duration-300"
|
||||
: `w-full h-full`,
|
||||
!embedIssue && {
|
||||
"bottom-0 right-0 top-0 w-full md:w-[50%] border-0 border-l": peekMode === "side-peek",
|
||||
"size-5/6 top-[8.33%] left-[8.33%]": peekMode === "modal",
|
||||
"inset-0 m-4": peekMode === "full-screen",
|
||||
"inset-0 m-4 absolute": peekMode === "full-screen",
|
||||
}
|
||||
);
|
||||
|
||||
const shouldUsePortal = !embedIssue && peekMode === "full-screen";
|
||||
|
||||
const portalContainer = document.getElementById("full-screen-portal") as HTMLElement;
|
||||
|
||||
const content = (
|
||||
<div className="w-full !text-base">
|
||||
{issueId && (
|
||||
<div
|
||||
ref={issuePeekOverviewRef}
|
||||
className={peekOverviewIssueClassName}
|
||||
style={{
|
||||
boxShadow:
|
||||
"0px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px 6px 12px 0px rgba(16, 24, 40, 0.12), 0px 1px 16px 0px rgba(16, 24, 40, 0.12)",
|
||||
}}
|
||||
>
|
||||
{isError ? (
|
||||
<div className="relative h-screen w-full overflow-hidden">
|
||||
<IssuePeekOverviewError removeRoutePeekId={removeRoutePeekId} />
|
||||
</div>
|
||||
) : (
|
||||
isLoading && <IssuePeekOverviewLoader removeRoutePeekId={removeRoutePeekId} />
|
||||
)}
|
||||
{!isLoading && !isError && issue && (
|
||||
<>
|
||||
{/* header */}
|
||||
<IssuePeekOverviewHeader
|
||||
peekMode={peekMode}
|
||||
setPeekMode={(value) => setPeekMode(value)}
|
||||
removeRoutePeekId={removeRoutePeekId}
|
||||
toggleDeleteIssueModal={toggleDeleteIssueModal}
|
||||
toggleArchiveIssueModal={toggleArchiveIssueModal}
|
||||
handleRestoreIssue={handleRestore}
|
||||
isArchived={is_archived}
|
||||
issueId={issueId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
isSubmitting={isSubmitting}
|
||||
disabled={disabled}
|
||||
embedIssue={embedIssue}
|
||||
/>
|
||||
{/* content */}
|
||||
<div className="vertical-scrollbar scrollbar-md relative h-full w-full overflow-hidden overflow-y-auto">
|
||||
{["side-peek", "modal"].includes(peekMode) ? (
|
||||
<div className="relative flex flex-col gap-3 px-8 py-5 space-y-3">
|
||||
<PeekOverviewIssueDetails
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={disabled || isLocalDBIssueDescription}
|
||||
isArchived={is_archived}
|
||||
isSubmitting={isSubmitting}
|
||||
setIsSubmitting={(value) => setIsSubmitting(value)}
|
||||
/>
|
||||
|
||||
<div className="py-2">
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled || is_archived}
|
||||
issueServiceType={EIssueServiceType.ISSUES}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PeekOverviewProperties
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={disabled || is_archived}
|
||||
/>
|
||||
|
||||
<IssueActivity
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={is_archived}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="vertical-scrollbar flex h-full w-full overflow-auto">
|
||||
<div className="relative h-full w-full space-y-6 overflow-auto p-4 py-5">
|
||||
<div className="space-y-3">
|
||||
<PeekOverviewIssueDetails
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={disabled || isLocalDBIssueDescription}
|
||||
isArchived={is_archived}
|
||||
isSubmitting={isSubmitting}
|
||||
setIsSubmitting={(value) => setIsSubmitting(value)}
|
||||
/>
|
||||
|
||||
<div className="py-2">
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={EIssueServiceType.ISSUES}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<IssueActivity
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={is_archived}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`h-full !w-[400px] flex-shrink-0 border-l border-custom-border-200 p-4 py-5 overflow-hidden vertical-scrollbar scrollbar-sm ${
|
||||
is_archived ? "pointer-events-none" : ""
|
||||
}`}
|
||||
>
|
||||
<PeekOverviewProperties
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={disabled || is_archived}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{issue && !is_archived && (
|
||||
@@ -142,135 +279,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="w-full !text-base">
|
||||
{issueId && (
|
||||
<div
|
||||
ref={issuePeekOverviewRef}
|
||||
className={peekOverviewIssueClassName}
|
||||
style={{
|
||||
boxShadow:
|
||||
"0px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px 6px 12px 0px rgba(16, 24, 40, 0.12), 0px 1px 16px 0px rgba(16, 24, 40, 0.12)",
|
||||
}}
|
||||
>
|
||||
{isError ? (
|
||||
<div className="relative h-screen w-full overflow-hidden">
|
||||
<IssuePeekOverviewError removeRoutePeekId={removeRoutePeekId} />
|
||||
</div>
|
||||
) : (
|
||||
isLoading && <IssuePeekOverviewLoader removeRoutePeekId={removeRoutePeekId} />
|
||||
)}
|
||||
{!isLoading && !isError && issue && (
|
||||
<>
|
||||
{/* header */}
|
||||
<IssuePeekOverviewHeader
|
||||
peekMode={peekMode}
|
||||
setPeekMode={(value) => setPeekMode(value)}
|
||||
removeRoutePeekId={removeRoutePeekId}
|
||||
toggleDeleteIssueModal={toggleDeleteIssueModal}
|
||||
toggleArchiveIssueModal={toggleArchiveIssueModal}
|
||||
handleRestoreIssue={handleRestore}
|
||||
isArchived={is_archived}
|
||||
issueId={issueId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
isSubmitting={isSubmitting}
|
||||
disabled={disabled}
|
||||
embedIssue={embedIssue}
|
||||
/>
|
||||
{/* content */}
|
||||
<div className="vertical-scrollbar scrollbar-md relative h-full w-full overflow-hidden overflow-y-auto">
|
||||
{["side-peek", "modal"].includes(peekMode) ? (
|
||||
<div className="relative flex flex-col gap-3 px-8 py-5 space-y-3">
|
||||
<PeekOverviewIssueDetails
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={disabled || isLocalDBIssueDescription}
|
||||
isArchived={is_archived}
|
||||
isSubmitting={isSubmitting}
|
||||
setIsSubmitting={(value) => setIsSubmitting(value)}
|
||||
/>
|
||||
|
||||
<div className="py-2">
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled || is_archived}
|
||||
issueServiceType={EIssueServiceType.ISSUES}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PeekOverviewProperties
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={disabled || is_archived}
|
||||
/>
|
||||
|
||||
<IssueActivity
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={is_archived}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="vertical-scrollbar flex h-full w-full overflow-auto">
|
||||
<div className="relative h-full w-full space-y-6 overflow-auto p-4 py-5">
|
||||
<div className="space-y-3">
|
||||
<PeekOverviewIssueDetails
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={disabled || isLocalDBIssueDescription}
|
||||
isArchived={is_archived}
|
||||
isSubmitting={isSubmitting}
|
||||
setIsSubmitting={(value) => setIsSubmitting(value)}
|
||||
/>
|
||||
|
||||
<div className="py-2">
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={EIssueServiceType.ISSUES}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<IssueActivity
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={is_archived}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`h-full !w-[400px] flex-shrink-0 border-l border-custom-border-200 p-4 py-5 overflow-hidden vertical-scrollbar scrollbar-sm ${
|
||||
is_archived ? "pointer-events-none" : ""
|
||||
}`}
|
||||
>
|
||||
<PeekOverviewProperties
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={disabled || is_archived}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{shouldUsePortal && portalContainer ? createPortal(content, portalContainer) : content}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user