UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: glass shell и компактный composer открытой карточки внутреннего контура
This commit is contained in:
parent
fc72c35e5a
commit
4ba06307ed
|
|
@ -26,6 +26,7 @@ type TCommentCreate = {
|
||||||
showToolbarInitially?: boolean;
|
showToolbarInitially?: boolean;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
onSubmitCallback?: (elementId: string) => void;
|
onSubmitCallback?: (elementId: string) => void;
|
||||||
|
appearance?: "default" | "issue-peek-compact";
|
||||||
};
|
};
|
||||||
|
|
||||||
// services
|
// services
|
||||||
|
|
@ -39,6 +40,7 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
|
||||||
showToolbarInitially = false,
|
showToolbarInitially = false,
|
||||||
projectId,
|
projectId,
|
||||||
onSubmitCallback,
|
onSubmitCallback,
|
||||||
|
appearance = "default",
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
const [uploadedAssetIds, setUploadedAssetIds] = useState<string[]>([]);
|
const [uploadedAssetIds, setUploadedAssetIds] = useState<string[]>([]);
|
||||||
|
|
@ -89,10 +91,13 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
|
||||||
|
|
||||||
const commentHTML = watch("comment_html");
|
const commentHTML = watch("comment_html");
|
||||||
const isEmpty = isCommentEmpty(commentHTML ?? undefined);
|
const isEmpty = isCommentEmpty(commentHTML ?? undefined);
|
||||||
|
const isCompactAppearance = appearance === "issue-peek-compact";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("sticky bottom-0 z-[4] bg-surface-1 sm:static")}
|
className={cn(
|
||||||
|
isCompactAppearance ? "relative z-[4] bg-transparent" : "sticky bottom-0 z-[4] bg-surface-1 sm:static"
|
||||||
|
)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (
|
if (
|
||||||
e.key === "Enter" &&
|
e.key === "Enter" &&
|
||||||
|
|
@ -128,7 +133,6 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
|
||||||
}}
|
}}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
initialValue={value ?? "<p></p>"}
|
initialValue={value ?? "<p></p>"}
|
||||||
containerClassName="min-h-min"
|
|
||||||
onChange={(comment_json, comment_html) => onChange(comment_html)}
|
onChange={(comment_json, comment_html) => onChange(comment_html)}
|
||||||
accessSpecifier={accessValue ?? EIssueCommentAccessSpecifier.INTERNAL}
|
accessSpecifier={accessValue ?? EIssueCommentAccessSpecifier.INTERNAL}
|
||||||
handleAccessChange={onAccessChange}
|
handleAccessChange={onAccessChange}
|
||||||
|
|
@ -144,7 +148,16 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
|
||||||
return asset_id;
|
return asset_id;
|
||||||
}}
|
}}
|
||||||
showToolbarInitially={showToolbarInitially}
|
showToolbarInitially={showToolbarInitially}
|
||||||
parentClassName="p-2"
|
parentClassName={cn(
|
||||||
|
isCompactAppearance
|
||||||
|
? "rounded-[20px] border border-subtle/70 bg-layer-2/75 p-4 backdrop-blur-xl"
|
||||||
|
: "p-2"
|
||||||
|
)}
|
||||||
|
variant={isCompactAppearance ? "full" : "full"}
|
||||||
|
toolbarMode={isCompactAppearance ? "compact-comment" : "default"}
|
||||||
|
showPlaceholderOnEmpty={isCompactAppearance}
|
||||||
|
editorClassName={cn(isCompactAppearance && "min-h-[112px]")}
|
||||||
|
containerClassName={cn(isCompactAppearance ? "min-h-[112px] border-none p-0" : "min-h-min")}
|
||||||
displayConfig={{
|
displayConfig={{
|
||||||
fontSize: "small-font",
|
fontSize: "small-font",
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ type LiteTextEditorWrapperProps = MakeOptional<
|
||||||
parentClassName?: string;
|
parentClassName?: string;
|
||||||
editorClassName?: string;
|
editorClassName?: string;
|
||||||
submitButtonText?: string;
|
submitButtonText?: string;
|
||||||
|
toolbarMode?: "default" | "compact-comment";
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
editable: false;
|
editable: false;
|
||||||
|
|
@ -81,6 +82,7 @@ export const LiteTextEditor = React.forwardRef(function LiteTextEditor(
|
||||||
editorClassName = "",
|
editorClassName = "",
|
||||||
showPlaceholderOnEmpty = true,
|
showPlaceholderOnEmpty = true,
|
||||||
submitButtonText = "common.comment",
|
submitButtonText = "common.comment",
|
||||||
|
toolbarMode = "default",
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
|
|
@ -217,6 +219,7 @@ export const LiteTextEditor = React.forwardRef(function LiteTextEditor(
|
||||||
editorRef={editorRef}
|
editorRef={editorRef}
|
||||||
showSubmitButton={showSubmitButton}
|
showSubmitButton={showSubmitButton}
|
||||||
submitButtonText={submitButtonText}
|
submitButtonText={submitButtonText}
|
||||||
|
mode={toolbarMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback } from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
|
import { ArrowUp, Paperclip, SmilePlus } from "lucide-react";
|
||||||
import type { LucideIcon } from "lucide-react";
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
import { EIssueCommentAccessSpecifier } from "@plane/constants";
|
import { EIssueCommentAccessSpecifier } from "@plane/constants";
|
||||||
|
|
@ -20,7 +21,7 @@ import { Tooltip } from "@plane/propel/tooltip";
|
||||||
// constants
|
// constants
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
import type { ToolbarMenuItem } from "@/constants/editor";
|
import type { ToolbarMenuItem } from "@/constants/editor";
|
||||||
import { TOOLBAR_ITEMS } from "@/constants/editor";
|
import { IMAGE_ITEM, TOOLBAR_ITEMS } from "@/constants/editor";
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -34,6 +35,7 @@ type Props = {
|
||||||
showSubmitButton: boolean;
|
showSubmitButton: boolean;
|
||||||
editorRef: EditorRefApi | null;
|
editorRef: EditorRefApi | null;
|
||||||
submitButtonText?: string;
|
submitButtonText?: string;
|
||||||
|
mode?: "default" | "compact-comment";
|
||||||
};
|
};
|
||||||
|
|
||||||
type TCommentAccessType = {
|
type TCommentAccessType = {
|
||||||
|
|
@ -56,6 +58,13 @@ const COMMENT_ACCESS_SPECIFIERS: TCommentAccessType[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const toolbarItems = TOOLBAR_ITEMS.lite;
|
const toolbarItems = TOOLBAR_ITEMS.lite;
|
||||||
|
const EMOJI_ITEM: ToolbarMenuItem<"emoji"> = {
|
||||||
|
itemKey: "emoji",
|
||||||
|
renderKey: "emoji",
|
||||||
|
name: "Emoji",
|
||||||
|
icon: SmilePlus,
|
||||||
|
editors: ["lite", "document"],
|
||||||
|
};
|
||||||
|
|
||||||
export function IssueCommentToolbar(props: Props) {
|
export function IssueCommentToolbar(props: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -70,6 +79,7 @@ export function IssueCommentToolbar(props: Props) {
|
||||||
showSubmitButton,
|
showSubmitButton,
|
||||||
editorRef,
|
editorRef,
|
||||||
submitButtonText = "common.comment",
|
submitButtonText = "common.comment",
|
||||||
|
mode = "default",
|
||||||
} = props;
|
} = props;
|
||||||
// State to manage active states of toolbar items
|
// State to manage active states of toolbar items
|
||||||
const [activeStates, setActiveStates] = useState<Record<string, boolean>>({});
|
const [activeStates, setActiveStates] = useState<Record<string, boolean>>({});
|
||||||
|
|
@ -102,6 +112,43 @@ export function IssueCommentToolbar(props: Props) {
|
||||||
const isEditorReadyToDiscard = editorRef?.isEditorReadyToDiscard();
|
const isEditorReadyToDiscard = editorRef?.isEditorReadyToDiscard();
|
||||||
const isSubmitButtonDisabled = isCommentEmpty || !isEditorReadyToDiscard;
|
const isSubmitButtonDisabled = isCommentEmpty || !isEditorReadyToDiscard;
|
||||||
|
|
||||||
|
if (mode === "compact-comment") {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between gap-3 border-t border-subtle/70 pt-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Tooltip tooltipContent={t("common.attach")}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => executeCommand(IMAGE_ITEM)}
|
||||||
|
className="grid size-10 place-items-center rounded-[16px] bg-layer-1/85 text-secondary transition-colors hover:bg-layer-1 focus-visible:outline-none"
|
||||||
|
>
|
||||||
|
<Paperclip className="h-4 w-4" strokeWidth={2} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip tooltipContent="Emoji">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => executeCommand(EMOJI_ITEM)}
|
||||||
|
className="grid size-10 place-items-center rounded-[16px] bg-layer-1/85 text-secondary transition-colors hover:bg-layer-1 focus-visible:outline-none"
|
||||||
|
>
|
||||||
|
<SmilePlus className="h-4 w-4" strokeWidth={2} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{showSubmitButton && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={isSubmitButtonDisabled || isSubmitting}
|
||||||
|
className="grid size-10 place-items-center rounded-[16px] bg-accent-primary text-on-color transition-colors hover:bg-accent-primary-hover focus-visible:outline-none disabled:bg-layer-disabled disabled:text-on-color-disabled"
|
||||||
|
>
|
||||||
|
<ArrowUp className="h-4 w-4" strokeWidth={2} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-9 w-full items-stretch gap-1.5 overflow-x-scroll bg-surface-2">
|
<div className="flex h-9 w-full items-stretch gap-1.5 overflow-x-scroll bg-surface-2">
|
||||||
{showAccessSpecifier && (
|
{showAccessSpecifier && (
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { useTranslation } from "@plane/i18n";
|
||||||
import { LinkIcon, ViewsIcon, RelationPropertyIcon } from "@plane/propel/icons";
|
import { LinkIcon, ViewsIcon, RelationPropertyIcon } from "@plane/propel/icons";
|
||||||
// plane imports
|
// plane imports
|
||||||
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
|
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
|
||||||
|
import { cn } from "@plane/utils";
|
||||||
// plane web imports
|
// plane web imports
|
||||||
import { WorkItemAdditionalWidgetActionButtons } from "@/plane-web/components/issues/issue-detail-widgets/action-buttons";
|
import { WorkItemAdditionalWidgetActionButtons } from "@/plane-web/components/issues/issue-detail-widgets/action-buttons";
|
||||||
// local imports
|
// local imports
|
||||||
|
|
@ -26,15 +27,16 @@ type Props = {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
issueServiceType: TIssueServiceType;
|
issueServiceType: TIssueServiceType;
|
||||||
hideWidgets?: TWorkItemWidgets[];
|
hideWidgets?: TWorkItemWidgets[];
|
||||||
|
compactView?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function IssueDetailWidgetActionButtons(props: Props) {
|
export function IssueDetailWidgetActionButtons(props: Props) {
|
||||||
const { workspaceSlug, projectId, issueId, disabled, issueServiceType, hideWidgets } = props;
|
const { workspaceSlug, projectId, issueId, disabled, issueServiceType, hideWidgets, compactView = false } = props;
|
||||||
// translation
|
// translation
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className={cn("flex flex-wrap items-center gap-2", compactView ? "justify-center" : "justify-start")}>
|
||||||
{!hideWidgets?.includes("sub-work-items") && (
|
{!hideWidgets?.includes("sub-work-items") && (
|
||||||
<SubIssuesActionButton
|
<SubIssuesActionButton
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
|
|
@ -43,6 +45,7 @@ export function IssueDetailWidgetActionButtons(props: Props) {
|
||||||
title={t("issue.add.sub_issue")}
|
title={t("issue.add.sub_issue")}
|
||||||
icon={<ViewsIcon className="h-3.5 w-3.5 flex-shrink-0" strokeWidth={2} />}
|
icon={<ViewsIcon className="h-3.5 w-3.5 flex-shrink-0" strokeWidth={2} />}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
compactView={compactView}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -57,6 +60,7 @@ export function IssueDetailWidgetActionButtons(props: Props) {
|
||||||
title={t("issue.add.relation")}
|
title={t("issue.add.relation")}
|
||||||
icon={<RelationPropertyIcon className="h-3.5 w-3.5 flex-shrink-0" />}
|
icon={<RelationPropertyIcon className="h-3.5 w-3.5 flex-shrink-0" />}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
compactView={compactView}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -70,6 +74,7 @@ export function IssueDetailWidgetActionButtons(props: Props) {
|
||||||
title={t("issue.add.link")}
|
title={t("issue.add.link")}
|
||||||
icon={<LinkIcon className="h-3.5 w-3.5 flex-shrink-0" strokeWidth={2} />}
|
icon={<LinkIcon className="h-3.5 w-3.5 flex-shrink-0" strokeWidth={2} />}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
compactView={compactView}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -86,6 +91,7 @@ export function IssueDetailWidgetActionButtons(props: Props) {
|
||||||
title={t("common.attach")}
|
title={t("common.attach")}
|
||||||
icon={<Paperclip className="h-3.5 w-3.5 flex-shrink-0" strokeWidth={2} />}
|
icon={<Paperclip className="h-3.5 w-3.5 flex-shrink-0" strokeWidth={2} />}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
compactView={compactView}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type Props = {
|
||||||
renderWidgetModals?: boolean;
|
renderWidgetModals?: boolean;
|
||||||
issueServiceType: TIssueServiceType;
|
issueServiceType: TIssueServiceType;
|
||||||
hideWidgets?: TWorkItemWidgets[];
|
hideWidgets?: TWorkItemWidgets[];
|
||||||
|
compactView?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function IssueDetailWidgets(props: Props) {
|
export function IssueDetailWidgets(props: Props) {
|
||||||
|
|
@ -31,6 +32,7 @@ export function IssueDetailWidgets(props: Props) {
|
||||||
renderWidgetModals = true,
|
renderWidgetModals = true,
|
||||||
issueServiceType,
|
issueServiceType,
|
||||||
hideWidgets,
|
hideWidgets,
|
||||||
|
compactView = false,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -43,6 +45,7 @@ export function IssueDetailWidgets(props: Props) {
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
issueServiceType={issueServiceType}
|
issueServiceType={issueServiceType}
|
||||||
hideWidgets={hideWidgets}
|
hideWidgets={hideWidgets}
|
||||||
|
compactView={compactView}
|
||||||
/>
|
/>
|
||||||
<IssueDetailWidgetCollapsibles
|
<IssueDetailWidgetCollapsibles
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,29 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
// helpers
|
// helpers
|
||||||
import { Button } from "@plane/propel/button";
|
import { Button } from "@plane/propel/button";
|
||||||
|
import { cn } from "@plane/utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
compactView?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function IssueDetailWidgetButton(props: Props) {
|
export function IssueDetailWidgetButton(props: Props) {
|
||||||
const { icon, title, disabled = false } = props;
|
const { icon, title, disabled = false, compactView = false } = props;
|
||||||
return (
|
return (
|
||||||
<Button variant={"secondary"} disabled={disabled} size="lg">
|
<Button
|
||||||
|
variant={"secondary"}
|
||||||
|
disabled={disabled}
|
||||||
|
size="xl"
|
||||||
|
className={cn(
|
||||||
|
"border-transparent shadow-none focus-visible:outline-none",
|
||||||
|
compactView
|
||||||
|
? "h-10 rounded-[18px] bg-layer-2/80 px-4 backdrop-blur-xl hover:bg-layer-2-active"
|
||||||
|
: "rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{icon && icon}
|
{icon && icon}
|
||||||
<span className="text-body-xs-medium">{title}</span>
|
<span className="text-body-xs-medium">{title}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { useLocalStorage } from "@plane/hooks";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
//types
|
//types
|
||||||
import type { TFileSignedURLResponse, TIssueComment } from "@plane/types";
|
import type { TFileSignedURLResponse, TIssueComment } from "@plane/types";
|
||||||
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { CommentCreate } from "@/components/comments/comment-create";
|
import { CommentCreate } from "@/components/comments/comment-create";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -34,6 +35,7 @@ type TIssueActivity = {
|
||||||
issueId: string;
|
issueId: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isIntakeIssue?: boolean;
|
isIntakeIssue?: boolean;
|
||||||
|
compactComposer?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TActivityOperations = {
|
export type TActivityOperations = {
|
||||||
|
|
@ -44,7 +46,7 @@ export type TActivityOperations = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueActivity = observer(function IssueActivity(props: TIssueActivity) {
|
export const IssueActivity = observer(function IssueActivity(props: TIssueActivity) {
|
||||||
const { workspaceSlug, projectId, issueId, disabled = false, isIntakeIssue = false } = props;
|
const { workspaceSlug, projectId, issueId, disabled = false, isIntakeIssue = false, compactComposer = false } = props;
|
||||||
// i18n
|
// i18n
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -92,15 +94,19 @@ export const IssueActivity = observer(function IssueActivity(props: TIssueActivi
|
||||||
const project = getProjectById(projectId);
|
const project = getProjectById(projectId);
|
||||||
const renderCommentCreationBox = useMemo(
|
const renderCommentCreationBox = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<CommentCreate
|
<div className={cn(compactComposer && "space-y-3 rounded-[24px] border border-subtle/70 bg-surface-2/70 p-4 backdrop-blur-xl")}>
|
||||||
workspaceSlug={workspaceSlug}
|
{compactComposer && <div className="text-body-sm-medium text-primary">{t("issue.comments.placeholder")}</div>}
|
||||||
entityId={issueId}
|
<CommentCreate
|
||||||
activityOperations={activityOperations}
|
workspaceSlug={workspaceSlug}
|
||||||
showToolbarInitially
|
entityId={issueId}
|
||||||
projectId={projectId}
|
activityOperations={activityOperations}
|
||||||
/>
|
showToolbarInitially
|
||||||
|
projectId={projectId}
|
||||||
|
appearance={compactComposer ? "issue-peek-compact" : "default"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
[workspaceSlug, issueId, activityOperations, projectId]
|
[workspaceSlug, issueId, activityOperations, projectId, compactComposer, t]
|
||||||
);
|
);
|
||||||
if (!project) return <></>;
|
if (!project) return <></>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { Button } from "@plane/propel/button";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||||
import { EIssueServiceType } from "@plane/types";
|
import { EIssueServiceType } from "@plane/types";
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
|
import { cn } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
||||||
import { useUserPermissions } from "@/hooks/store/user";
|
import { useUserPermissions } from "@/hooks/store/user";
|
||||||
|
|
@ -25,10 +26,11 @@ export type TIssueSubscription = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
issueId: string;
|
issueId: string;
|
||||||
serviceType?: EIssueServiceType;
|
serviceType?: EIssueServiceType;
|
||||||
|
buttonClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueSubscription = observer(function IssueSubscription(props: TIssueSubscription) {
|
export const IssueSubscription = observer(function IssueSubscription(props: TIssueSubscription) {
|
||||||
const { workspaceSlug, projectId, issueId, serviceType = EIssueServiceType.ISSUES } = props;
|
const { workspaceSlug, projectId, issueId, serviceType = EIssueServiceType.ISSUES, buttonClassName } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// hooks
|
// hooks
|
||||||
const {
|
const {
|
||||||
|
|
@ -84,7 +86,7 @@ export const IssueSubscription = observer(function IssueSubscription(props: TIss
|
||||||
<Button
|
<Button
|
||||||
prependIcon={isSubscribed ? <BellOff /> : <Bell className="h-3 w-3" />}
|
prependIcon={isSubscribed ? <BellOff /> : <Bell className="h-3 w-3" />}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="hover:!bg-accent-primary/20"
|
className={cn("hover:!bg-accent-primary/20", buttonClassName)}
|
||||||
onClick={handleSubscription}
|
onClick={handleSubscription}
|
||||||
disabled={!isEditable || loading}
|
disabled={!isEditable || loading}
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ type TWorkItemDetailQuickActionProps = IQuickActionProps & {
|
||||||
toggleDuplicateIssueModal?: (value: boolean) => void;
|
toggleDuplicateIssueModal?: (value: boolean) => void;
|
||||||
toggleArchiveIssueModal?: (value: boolean) => void;
|
toggleArchiveIssueModal?: (value: boolean) => void;
|
||||||
isPeekMode?: boolean;
|
isPeekMode?: boolean;
|
||||||
|
buttonClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkItemDetailQuickActions = observer(function WorkItemDetailQuickActions(
|
export const WorkItemDetailQuickActions = observer(function WorkItemDetailQuickActions(
|
||||||
|
|
@ -57,6 +58,7 @@ export const WorkItemDetailQuickActions = observer(function WorkItemDetailQuickA
|
||||||
toggleDuplicateIssueModal,
|
toggleDuplicateIssueModal,
|
||||||
toggleArchiveIssueModal,
|
toggleArchiveIssueModal,
|
||||||
isPeekMode = false,
|
isPeekMode = false,
|
||||||
|
buttonClassName,
|
||||||
} = props;
|
} = props;
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
|
|
@ -241,7 +243,7 @@ export const WorkItemDetailQuickActions = observer(function WorkItemDetailQuickA
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
ellipsis
|
ellipsis
|
||||||
placement={placements}
|
placement={placements}
|
||||||
customButton={<IconButton size="lg" variant="secondary" icon={Ellipsis} />}
|
customButton={<IconButton size="lg" variant="secondary" icon={Ellipsis} className={buttonClassName} />}
|
||||||
portalElement={portalElement}
|
portalElement={portalElement}
|
||||||
menuItemsClassName="z-[14]"
|
menuItemsClassName="z-[14]"
|
||||||
maxHeight="lg"
|
maxHeight="lg"
|
||||||
|
|
|
||||||
|
|
@ -154,8 +154,8 @@ export const IssuePeekOverviewHeader = observer(function IssuePeekOverviewHeader
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative flex items-center justify-between p-4 ${
|
className={`relative flex items-center justify-between px-6 py-5 ${
|
||||||
currentMode?.key === "full-screen" ? "border-b border-subtle" : ""
|
currentMode?.key === "full-screen" ? "border-b border-subtle/70" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|
@ -203,10 +203,21 @@ export const IssuePeekOverviewHeader = observer(function IssuePeekOverviewHeader
|
||||||
<NameDescriptionUpdateStatus isSubmitting={isSubmitting} />
|
<NameDescriptionUpdateStatus isSubmitting={isSubmitting} />
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{currentUser && !isArchived && (
|
{currentUser && !isArchived && (
|
||||||
<IssueSubscription workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} />
|
<IssueSubscription
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
issueId={issueId}
|
||||||
|
buttonClassName="!h-10 rounded-[18px] border-transparent bg-layer-2/80 px-4 shadow-none backdrop-blur-xl hover:!bg-layer-2-active focus-visible:outline-none"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Tooltip tooltipContent={t("common.actions.copy_link")} isMobile={isMobile}>
|
<Tooltip tooltipContent={t("common.actions.copy_link")} isMobile={isMobile}>
|
||||||
<IconButton variant="secondary" size="lg" onClick={handleCopyText} icon={CopyLinkIcon} />
|
<IconButton
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
onClick={handleCopyText}
|
||||||
|
icon={CopyLinkIcon}
|
||||||
|
className="size-10 rounded-[18px] border-transparent bg-layer-2/80 shadow-none backdrop-blur-xl hover:bg-layer-2-active focus-visible:outline-none"
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{issueDetails && (
|
{issueDetails && (
|
||||||
<WorkItemDetailQuickActions
|
<WorkItemDetailQuickActions
|
||||||
|
|
@ -221,6 +232,7 @@ export const IssuePeekOverviewHeader = observer(function IssuePeekOverviewHeader
|
||||||
toggleDuplicateIssueModal={toggleDuplicateIssueModal}
|
toggleDuplicateIssueModal={toggleDuplicateIssueModal}
|
||||||
toggleEditIssueModal={toggleEditIssueModal}
|
toggleEditIssueModal={toggleEditIssueModal}
|
||||||
isPeekMode
|
isPeekMode
|
||||||
|
buttonClassName="size-10 rounded-[18px] border-transparent bg-layer-2/80 shadow-none backdrop-blur-xl hover:bg-layer-2-active focus-visible:outline-none"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -120,12 +120,13 @@ export const IssueView = observer(function IssueView(props: IIssueView) {
|
||||||
|
|
||||||
const peekOverviewIssueClassName = cn(
|
const peekOverviewIssueClassName = cn(
|
||||||
!embedIssue
|
!embedIssue
|
||||||
? "absolute z-[25] flex flex-col overflow-hidden rounded-sm border border-subtle bg-surface-1 transition-all duration-300"
|
? "absolute z-[25] flex flex-col overflow-hidden border border-subtle/70 bg-surface-1/80 backdrop-blur-2xl transition-all duration-300"
|
||||||
: `h-full w-full`,
|
: `h-full w-full`,
|
||||||
!embedIssue && {
|
!embedIssue && {
|
||||||
"top-0 right-0 bottom-0 w-full border-0 border-l md:w-[50%]": peekMode === "side-peek",
|
"top-3 right-3 bottom-3 w-[calc(100%-1.5rem)] resize-x rounded-[28px] border md:w-[50%] md:min-w-[640px] md:max-w-[calc(100vw-1.5rem)]":
|
||||||
"top-[8.33%] left-[8.33%] size-5/6": peekMode === "modal",
|
peekMode === "side-peek",
|
||||||
"absolute inset-0 m-4": peekMode === "full-screen",
|
"top-[8.33%] left-[8.33%] size-5/6 rounded-[28px]": peekMode === "modal",
|
||||||
|
"absolute inset-0 m-4 rounded-[28px]": peekMode === "full-screen",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -174,7 +175,7 @@ export const IssueView = observer(function IssueView(props: IIssueView) {
|
||||||
{/* content */}
|
{/* content */}
|
||||||
<div className="vertical-scrollbar relative scrollbar-md h-full w-full overflow-hidden overflow-y-auto">
|
<div className="vertical-scrollbar relative scrollbar-md h-full w-full overflow-hidden overflow-y-auto">
|
||||||
{["side-peek", "modal"].includes(peekMode) ? (
|
{["side-peek", "modal"].includes(peekMode) ? (
|
||||||
<div className="relative flex flex-col gap-3 space-y-3 px-8 py-5">
|
<div className="relative flex flex-col gap-4 space-y-3 px-8 py-6">
|
||||||
<PeekOverviewIssueDetails
|
<PeekOverviewIssueDetails
|
||||||
editorRef={editorRef}
|
editorRef={editorRef}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
|
|
@ -193,6 +194,7 @@ export const IssueView = observer(function IssueView(props: IIssueView) {
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
disabled={disabled || is_archived}
|
disabled={disabled || is_archived}
|
||||||
|
compactView
|
||||||
issueServiceType={EIssueServiceType.ISSUES}
|
issueServiceType={EIssueServiceType.ISSUES}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -210,6 +212,7 @@ export const IssueView = observer(function IssueView(props: IIssueView) {
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
disabled={is_archived}
|
disabled={is_archived}
|
||||||
|
compactComposer
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue