UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: миграция modal property и mobile selection dropdown на общий канон
This commit is contained in:
parent
882216922e
commit
6d35fc7bee
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import type { Placement } from "@popperjs/core";
|
||||||
|
import { FiltersDropdown, FilterOption } from "@/components/issues/issue-layouts/filters/header";
|
||||||
|
|
||||||
|
export type TSelectionDropdownOption = {
|
||||||
|
key: string;
|
||||||
|
title: ReactNode;
|
||||||
|
icon?: ReactNode;
|
||||||
|
isChecked: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
shouldRender?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
activePulse?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
disabled?: boolean;
|
||||||
|
menuButton: ReactNode;
|
||||||
|
menuButtonWrapperClassName?: string;
|
||||||
|
options: TSelectionDropdownOption[];
|
||||||
|
placement?: Placement;
|
||||||
|
title?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SelectionDropdown(props: Props) {
|
||||||
|
const { disabled = false, menuButton, menuButtonWrapperClassName, options, placement = "bottom-start", title } = props;
|
||||||
|
|
||||||
|
const renderedOptions = options.filter((option) => option.shouldRender !== false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FiltersDropdown
|
||||||
|
menuButton={menuButton}
|
||||||
|
menuButtonWrapperClassName={menuButtonWrapperClassName}
|
||||||
|
placement={placement}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{({ closeDropdown }) => (
|
||||||
|
<div className="vertical-scrollbar relative scrollbar-sm h-full w-full overflow-y-auto px-2.5 py-2">
|
||||||
|
{title && (
|
||||||
|
<div className="sticky top-0 z-[1] bg-[rgba(8,8,11,0.92)] pb-1 pt-0.5 text-caption-sm-medium text-placeholder backdrop-blur-xl">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={title ? "pt-2" : ""}>
|
||||||
|
{renderedOptions.map((option) => (
|
||||||
|
<FilterOption
|
||||||
|
key={option.key}
|
||||||
|
activePulse={option.activePulse}
|
||||||
|
disabled={option.disabled}
|
||||||
|
icon={option.icon}
|
||||||
|
isChecked={option.isChecked}
|
||||||
|
onClick={() => {
|
||||||
|
option.onClick();
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
title={option.title}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FiltersDropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,7 @@ export function SortingDropdown(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FiltersDropdown menuButton={menuButton} placement={placement} disabled={disabled}>
|
<FiltersDropdown menuButton={menuButton} placement={placement} disabled={disabled}>
|
||||||
|
{({ closeDropdown }) => (
|
||||||
<div className="vertical-scrollbar relative scrollbar-sm h-full w-full overflow-y-auto px-2.5">
|
<div className="vertical-scrollbar relative scrollbar-sm h-full w-full overflow-y-auto px-2.5">
|
||||||
<div className="sticky top-0 z-[1] bg-[rgba(8,8,11,0.92)] pb-1 pt-0.5 text-caption-sm-medium text-placeholder backdrop-blur-xl">
|
<div className="sticky top-0 z-[1] bg-[rgba(8,8,11,0.92)] pb-1 pt-0.5 text-caption-sm-medium text-placeholder backdrop-blur-xl">
|
||||||
{title}
|
{title}
|
||||||
|
|
@ -58,13 +59,17 @@ export function SortingDropdown(props: Props) {
|
||||||
disabled={option.disabled}
|
disabled={option.disabled}
|
||||||
icon={option.icon}
|
icon={option.icon}
|
||||||
isChecked={option.isChecked}
|
isChecked={option.isChecked}
|
||||||
onClick={option.onClick}
|
onClick={() => {
|
||||||
|
option.onClick();
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
title={option.title}
|
title={option.title}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import { observer } from "mobx-react";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { ChevronDownIcon } from "@plane/propel/icons";
|
import { ChevronDownIcon } from "@plane/propel/icons";
|
||||||
import type { TRecentActivityFilterKeys } from "@plane/types";
|
import type { TRecentActivityFilterKeys } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
|
import { SelectionDropdown } from "@/components/common/selection-dropdown";
|
||||||
|
|
||||||
export type TFiltersDropdown = {
|
export type TFiltersDropdown = {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
@ -22,36 +22,24 @@ export const FiltersDropdown = observer(function FiltersDropdown(props: TFilters
|
||||||
const { className, activeFilter, setActiveFilter, filters } = props;
|
const { className, activeFilter, setActiveFilter, filters } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
function DropdownOptions() {
|
|
||||||
return filters?.map((filter) => (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
key={filter.name}
|
|
||||||
className="flex items-center gap-2 truncate text-secondary"
|
|
||||||
onClick={() => {
|
|
||||||
setActiveFilter(filter.name);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="truncate text-11 font-medium capitalize">{t(filter.i18n_key)}</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = activeFilter ? filters?.find((filter) => filter.name === activeFilter)?.i18n_key : "";
|
const title = activeFilter ? filters?.find((filter) => filter.name === activeFilter)?.i18n_key : "";
|
||||||
return (
|
return (
|
||||||
<CustomMenu
|
<SelectionDropdown
|
||||||
maxHeight={"md"}
|
menuButton={
|
||||||
className={cn("flex w-fit justify-center text-11 text-secondary", className)}
|
<div className="flex gap-1 rounded-sm border border-subtle px-2 py-1 capitalize hover:bg-layer-transparent-hover">
|
||||||
placement="bottom-start"
|
|
||||||
customButton={
|
|
||||||
<button className="flex gap-1 rounded-sm border border-subtle px-2 py-1 capitalize hover:bg-layer-transparent-hover">
|
|
||||||
<span className="my-auto text-13 font-medium">{t(title || "")}</span>
|
<span className="my-auto text-13 font-medium">{t(title || "")}</span>
|
||||||
<ChevronDownIcon className={cn("my-auto size-3 text-tertiary duration-300 hover:text-secondary")} />
|
<ChevronDownIcon className={cn("my-auto size-3 text-tertiary duration-300 hover:text-secondary")} />
|
||||||
</button>
|
</div>
|
||||||
}
|
}
|
||||||
customButtonClassName="flex justify-center"
|
menuButtonWrapperClassName={cn("flex w-fit justify-center text-11 text-secondary", className)}
|
||||||
closeOnSelect
|
placement="bottom-start"
|
||||||
>
|
options={filters.map((filter) => ({
|
||||||
<DropdownOptions />
|
key: filter.name,
|
||||||
</CustomMenu>
|
title: <div className="truncate text-11 font-medium capitalize">{t(filter.i18n_key)}</div>,
|
||||||
|
icon: filter.icon,
|
||||||
|
isChecked: activeFilter === filter.name,
|
||||||
|
onClick: () => setActiveFilter(filter.name),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@
|
||||||
* See the LICENSE file for details.
|
* See the LICENSE file for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
import { PencilLine, Unlink2 } from "lucide-react";
|
||||||
import { ETabIndices } from "@plane/constants";
|
import { ETabIndices } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { ParentPropertyIcon } from "@plane/propel/icons";
|
import { ParentPropertyIcon } from "@plane/propel/icons";
|
||||||
import type { ISearchIssueResponse, TIssue } from "@plane/types";
|
import type { ISearchIssueResponse, TIssue } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { ActionDropdown, type TContextMenuItem } from "@plane/ui";
|
||||||
import { cn, getDate, getTabIndex, renderFormattedPayloadDate } from "@plane/utils";
|
import { cn, getDate, getTabIndex, renderFormattedPayloadDate } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { CycleDropdown } from "@/components/dropdowns/cycle";
|
import { CycleDropdown } from "@/components/dropdowns/cycle";
|
||||||
|
|
@ -40,6 +42,7 @@ export const InboxIssueProperties = observer(function InboxIssueProperties(props
|
||||||
// hooks
|
// hooks
|
||||||
const { areEstimateEnabledByProjectId } = useProjectEstimates();
|
const { areEstimateEnabledByProjectId } = useProjectEstimates();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
const { t } = useTranslation();
|
||||||
// states
|
// states
|
||||||
const [parentIssueModalOpen, setParentIssueModalOpen] = useState(false);
|
const [parentIssueModalOpen, setParentIssueModalOpen] = useState(false);
|
||||||
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | undefined>(undefined);
|
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | undefined>(undefined);
|
||||||
|
|
@ -54,6 +57,26 @@ export const InboxIssueProperties = observer(function InboxIssueProperties(props
|
||||||
|
|
||||||
const maxDate = getDate(targetDate);
|
const maxDate = getDate(targetDate);
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
const parentMenuItems = useMemo<TContextMenuItem[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: "change-parent",
|
||||||
|
title: t("change_parent_issue"),
|
||||||
|
icon: PencilLine,
|
||||||
|
action: () => setParentIssueModalOpen(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "remove-parent",
|
||||||
|
title: t("remove_parent_issue"),
|
||||||
|
icon: Unlink2,
|
||||||
|
action: () => {
|
||||||
|
handleData("parent_id", "");
|
||||||
|
setSelectedParentIssue(undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[handleData, t]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("relative flex flex-wrap items-center gap-2", rootClassName)}>
|
<div className={cn("relative flex flex-wrap items-center gap-2", rootClassName)}>
|
||||||
|
|
@ -185,8 +208,8 @@ export const InboxIssueProperties = observer(function InboxIssueProperties(props
|
||||||
{isVisible && (
|
{isVisible && (
|
||||||
<div className="h-7">
|
<div className="h-7">
|
||||||
{selectedParentIssue ? (
|
{selectedParentIssue ? (
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
customButton={
|
button={
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -198,30 +221,15 @@ export const InboxIssueProperties = observer(function InboxIssueProperties(props
|
||||||
<span className="whitespace-nowrap">
|
<span className="whitespace-nowrap">
|
||||||
{selectedParentIssue
|
{selectedParentIssue
|
||||||
? `${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`
|
? `${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`
|
||||||
: `Add parent`}
|
: t("add_parent")}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
buttonAsChild
|
||||||
|
buttonClassName="h-full"
|
||||||
|
items={parentMenuItems}
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
className="h-full w-full"
|
/>
|
||||||
customButtonClassName="h-full"
|
|
||||||
tabIndex={getIndex("parent_id")}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueModalOpen(true)}>
|
|
||||||
Change parent work item
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
className="!p-1"
|
|
||||||
onClick={() => {
|
|
||||||
handleData("parent_id", "");
|
|
||||||
setSelectedParentIssue(undefined);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Remove parent work item
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
</>
|
|
||||||
</CustomMenu>
|
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -232,7 +240,7 @@ export const InboxIssueProperties = observer(function InboxIssueProperties(props
|
||||||
onClick={() => setParentIssueModalOpen(true)}
|
onClick={() => setParentIssueModalOpen(true)}
|
||||||
>
|
>
|
||||||
<ParentPropertyIcon className="h-3 w-3 flex-shrink-0" />
|
<ParentPropertyIcon className="h-3 w-3 flex-shrink-0" />
|
||||||
<span className="whitespace-nowrap">Add parent</span>
|
<span className="whitespace-nowrap">{t("add_parent")}</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { Popover, Portal, Transition } from "@headlessui/react";
|
||||||
import { Button } from "@plane/propel/button";
|
import { Button } from "@plane/propel/button";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode | ((props: { closeDropdown: () => void }) => React.ReactNode);
|
||||||
icon?: React.ReactElement;
|
icon?: React.ReactElement;
|
||||||
miniIcon?: React.ReactNode;
|
miniIcon?: React.ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
@ -21,6 +21,7 @@ type Props = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
menuButton?: React.ReactNode;
|
menuButton?: React.ReactNode;
|
||||||
|
menuButtonWrapperClassName?: string;
|
||||||
isFiltersApplied?: boolean;
|
isFiltersApplied?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -34,6 +35,7 @@ export function FiltersDropdown(props: Props) {
|
||||||
disabled = false,
|
disabled = false,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
menuButton,
|
menuButton,
|
||||||
|
menuButtonWrapperClassName,
|
||||||
isFiltersApplied = false,
|
isFiltersApplied = false,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
@ -46,11 +48,16 @@ export function FiltersDropdown(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover as="div">
|
<Popover as="div">
|
||||||
{({ open }) => (
|
{({ open, close }) => (
|
||||||
<>
|
<>
|
||||||
<Popover.Button as={React.Fragment}>
|
<Popover.Button as={React.Fragment}>
|
||||||
{menuButton ? (
|
{menuButton ? (
|
||||||
<button type="button" ref={setReferenceElement}>
|
<button
|
||||||
|
type="button"
|
||||||
|
ref={setReferenceElement}
|
||||||
|
className={menuButtonWrapperClassName}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
{menuButton}
|
{menuButton}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -109,7 +116,7 @@ export function FiltersDropdown(props: Props) {
|
||||||
{...attributes.popper}
|
{...attributes.popper}
|
||||||
>
|
>
|
||||||
<div className="flex max-h-[30rem] w-[18.75rem] flex-col overflow-hidden lg:max-h-[37.5rem]">
|
<div className="flex max-h-[30rem] w-[18.75rem] flex-col overflow-hidden lg:max-h-[37.5rem]">
|
||||||
{children}
|
{typeof children === "function" ? children({ closeDropdown: close }) : children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@
|
||||||
|
|
||||||
import { ISSUE_LAYOUTS } from "@plane/constants";
|
import { ISSUE_LAYOUTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Button } from "@plane/propel/button";
|
|
||||||
import { ChevronDownIcon } from "@plane/propel/icons";
|
import { ChevronDownIcon } from "@plane/propel/icons";
|
||||||
import type { EIssueLayoutTypes } from "@plane/types";
|
import type { EIssueLayoutTypes } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { cn } from "@plane/utils";
|
||||||
|
import { SelectionDropdown } from "@/components/common/selection-dropdown";
|
||||||
import { IssueLayoutIcon } from "../../layout-icon";
|
import { IssueLayoutIcon } from "../../layout-icon";
|
||||||
|
|
||||||
export function MobileLayoutSelection({
|
export function MobileLayoutSelection({
|
||||||
|
|
@ -24,35 +24,26 @@ export function MobileLayoutSelection({
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<CustomMenu
|
<SelectionDropdown
|
||||||
maxHeight={"md"}
|
menuButton={
|
||||||
className="flex flex-grow justify-center text-13 text-secondary"
|
<div className="nodedc-toolbar-pill relative flex items-center gap-2 px-3">
|
||||||
placement="bottom-start"
|
|
||||||
customButton={
|
|
||||||
<Button variant="secondary" className="nodedc-toolbar-pill relative gap-2 px-3">
|
|
||||||
<span className="nodedc-toolbar-icon-active-dot bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]">
|
<span className="nodedc-toolbar-icon-active-dot bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]">
|
||||||
{activeLayout && (
|
{activeLayout && (
|
||||||
<IssueLayoutIcon layout={activeLayout} size={14} strokeWidth={2} className={`h-3.5 w-3.5`} />
|
<IssueLayoutIcon layout={activeLayout} size={14} strokeWidth={2} className="h-3.5 w-3.5" />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDownIcon className="my-auto size-3 text-secondary" strokeWidth={2} />
|
<ChevronDownIcon className="my-auto size-3 text-secondary" strokeWidth={2} />
|
||||||
</Button>
|
</div>
|
||||||
}
|
}
|
||||||
customButtonClassName="flex flex-grow justify-center text-secondary text-13"
|
menuButtonWrapperClassName="flex flex-grow justify-center text-13 text-secondary"
|
||||||
closeOnSelect
|
placement="bottom-start"
|
||||||
>
|
options={ISSUE_LAYOUTS.filter((layout) => layouts.includes(layout.key)).map((layout) => ({
|
||||||
{ISSUE_LAYOUTS.filter((l) => layouts.includes(l.key)).map((layout, index) => (
|
key: layout.key,
|
||||||
<CustomMenu.MenuItem
|
title: <div className={cn("text-tertiary", { "text-secondary": activeLayout === layout.key })}>{t(layout.i18n_label)}</div>,
|
||||||
key={index}
|
icon: <IssueLayoutIcon layout={layout.key} className="h-3 w-3" />,
|
||||||
onClick={() => {
|
isChecked: activeLayout === layout.key,
|
||||||
onChange(layout.key);
|
onClick: () => onChange(layout.key),
|
||||||
}}
|
}))}
|
||||||
className="flex items-center gap-2"
|
/>
|
||||||
>
|
|
||||||
<IssueLayoutIcon layout={layout.key} className="h-3 w-3" />
|
|
||||||
<div className="text-tertiary">{t(layout.i18n_label)}</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
))}
|
|
||||||
</CustomMenu>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,18 @@
|
||||||
* See the LICENSE file for details.
|
* See the LICENSE file for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { Control } from "react-hook-form";
|
import type { Control } from "react-hook-form";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
|
import { PencilLine, Unlink2 } from "lucide-react";
|
||||||
import { ETabIndices, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { ETabIndices, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { ParentPropertyIcon } from "@plane/propel/icons";
|
import { ParentPropertyIcon } from "@plane/propel/icons";
|
||||||
// types
|
// types
|
||||||
import type { ISearchIssueResponse, TIssue } from "@plane/types";
|
import type { ISearchIssueResponse, TIssue } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { ActionDropdown, type TContextMenuItem } from "@plane/ui";
|
||||||
import { getDate, renderFormattedPayloadDate, getTabIndex } from "@plane/utils";
|
import { getDate, renderFormattedPayloadDate, getTabIndex } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { CycleDropdown } from "@/components/dropdowns/cycle";
|
import { CycleDropdown } from "@/components/dropdowns/cycle";
|
||||||
|
|
@ -46,7 +47,7 @@ type TIssueDefaultPropertiesProps = {
|
||||||
parentId: string | null;
|
parentId: string | null;
|
||||||
isDraft: boolean;
|
isDraft: boolean;
|
||||||
handleFormChange: () => void;
|
handleFormChange: () => void;
|
||||||
setSelectedParentIssue: (issue: ISearchIssueResponse) => void;
|
setSelectedParentIssue: (issue: ISearchIssueResponse | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueDefaultProperties = observer(function IssueDefaultProperties(props: TIssueDefaultPropertiesProps) {
|
export const IssueDefaultProperties = observer(function IssueDefaultProperties(props: TIssueDefaultPropertiesProps) {
|
||||||
|
|
@ -85,6 +86,23 @@ export const IssueDefaultProperties = observer(function IssueDefaultProperties(p
|
||||||
const maxDate = getDate(targetDate);
|
const maxDate = getDate(targetDate);
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
const propertyButtonClassName = "nodedc-work-item-property-button";
|
const propertyButtonClassName = "nodedc-work-item-property-button";
|
||||||
|
const parentMenuItems = useMemo<TContextMenuItem[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: "change-parent",
|
||||||
|
title: t("change_parent_issue"),
|
||||||
|
icon: PencilLine,
|
||||||
|
action: () => setParentIssueListModalOpen(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "remove-parent",
|
||||||
|
title: t("remove_parent_issue"),
|
||||||
|
icon: Unlink2,
|
||||||
|
action: () => setSelectedParentIssue(null),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[setSelectedParentIssue, t]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="nodedc-work-item-properties-row">
|
<div className="nodedc-work-item-properties-row">
|
||||||
|
|
@ -276,8 +294,12 @@ export const IssueDefaultProperties = observer(function IssueDefaultProperties(p
|
||||||
)}
|
)}
|
||||||
<div className="h-7">
|
<div className="h-7">
|
||||||
{parentId ? (
|
{parentId ? (
|
||||||
<CustomMenu
|
<Controller
|
||||||
customButton={
|
control={control}
|
||||||
|
name="parent_id"
|
||||||
|
render={({ field: { onChange } }) => (
|
||||||
|
<ActionDropdown
|
||||||
|
button={
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`${propertyButtonClassName} flex h-full cursor-pointer items-center justify-between gap-1 whitespace-nowrap`}
|
className={`${propertyButtonClassName} flex h-full cursor-pointer items-center justify-between gap-1 whitespace-nowrap`}
|
||||||
|
|
@ -293,32 +315,23 @@ export const IssueDefaultProperties = observer(function IssueDefaultProperties(p
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
placement="bottom-start"
|
buttonAsChild
|
||||||
className="h-full w-full"
|
buttonClassName="h-full"
|
||||||
customButtonClassName="h-full"
|
items={[
|
||||||
tabIndex={getIndex("parent_id")}
|
parentMenuItems[0],
|
||||||
>
|
{
|
||||||
<>
|
...parentMenuItems[1],
|
||||||
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}>
|
action: () => {
|
||||||
{t("change_parent_issue")}
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="parent_id"
|
|
||||||
render={({ field: { onChange } }) => (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
className="!p-1"
|
|
||||||
onClick={() => {
|
|
||||||
onChange(null);
|
onChange(null);
|
||||||
handleFormChange();
|
handleFormChange();
|
||||||
}}
|
setSelectedParentIssue(null);
|
||||||
>
|
},
|
||||||
{t("remove_parent_issue")}
|
},
|
||||||
</CustomMenu.MenuItem>
|
]}
|
||||||
|
placement="bottom-start"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
</CustomMenu>
|
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue