UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: миграция secondary detail action-menu на ActionDropdown
This commit is contained in:
parent
b0173c82e6
commit
c86fc16cdf
|
|
@ -10,12 +10,11 @@ import { MoreHorizontal } from "lucide-react";
|
|||
// plane imports
|
||||
import { EIssueCommentAccessSpecifier } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IconButton } from "@plane/propel/icon-button";
|
||||
import { getIconButtonStyling } from "@plane/propel/icon-button";
|
||||
import { LinkIcon, GlobeIcon, LockIcon, EditIcon, TrashIcon } from "@plane/propel/icons";
|
||||
import type { TIssueComment, TCommentsOperations } from "@plane/types";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
import { ActionDropdown } from "@plane/ui";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
|
||||
|
|
@ -84,39 +83,11 @@ export const CommentQuickActions = observer(function CommentQuickActions(props:
|
|||
);
|
||||
|
||||
return (
|
||||
<CustomMenu customButton={<IconButton icon={MoreHorizontal} variant="ghost" size="sm" />} closeOnSelect>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
|
||||
return (
|
||||
<CustomMenu.MenuItem
|
||||
key={item.key}
|
||||
onClick={() => item.action()}
|
||||
className={cn(
|
||||
"flex items-center gap-2",
|
||||
{
|
||||
"text-placeholder": item.disabled,
|
||||
},
|
||||
item.className
|
||||
)}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.icon && <item.icon className={cn("size-3 shrink-0", item.iconClassName)} />}
|
||||
<div>
|
||||
<h5>{item.title}</h5>
|
||||
{item.description && (
|
||||
<p
|
||||
className={cn("whitespace-pre-line text-tertiary", {
|
||||
"text-placeholder": item.disabled,
|
||||
})}
|
||||
>
|
||||
{item.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
);
|
||||
})}
|
||||
</CustomMenu>
|
||||
<ActionDropdown
|
||||
items={MENU_ITEMS}
|
||||
button={<MoreHorizontal className="size-3.5" />}
|
||||
buttonClassName={getIconButtonStyling("ghost", "sm")}
|
||||
placement="bottom-end"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@
|
|||
import { observer } from "mobx-react";
|
||||
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { getIconButtonStyling } from "@plane/propel/icon-button";
|
||||
import { TrashIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { TIssueServiceType } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { ActionDropdown } from "@plane/ui";
|
||||
import { convertBytesToSize, getFileExtension, getFileName, getFileURL, renderFormattedDate } from "@plane/utils";
|
||||
// components
|
||||
//
|
||||
|
|
@ -46,21 +48,39 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
|||
const fileExtension = getFileExtension(attachment?.attributes.name ?? "");
|
||||
const fileIcon = getFileIcon(fileExtension, 18);
|
||||
const fileURL = getFileURL(attachment?.asset_url ?? "");
|
||||
const menuItems: TContextMenuItem[] = [
|
||||
{
|
||||
key: "delete",
|
||||
action: () => {
|
||||
toggleDeleteAttachmentModal(attachmentId);
|
||||
},
|
||||
title: t("common.actions.delete"),
|
||||
icon: TrashIcon,
|
||||
},
|
||||
];
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
if (!attachment) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(fileURL, "_blank");
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(fileURL, "_blank");
|
||||
}}
|
||||
>
|
||||
<div className="group flex h-11 items-center justify-between gap-3 pr-2 pl-9 hover:bg-surface-2">
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="group flex h-11 items-center justify-between gap-3 pr-2 pl-9 hover:bg-surface-2">
|
||||
<div className="flex items-center gap-3 truncate text-13">
|
||||
<div className="flex items-center gap-3">{fileIcon}</div>
|
||||
<Tooltip tooltipContent={`${fileName}.${fileExtension}`} isMobile={isMobile}>
|
||||
|
|
@ -86,21 +106,14 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
|||
</>
|
||||
)}
|
||||
|
||||
<CustomMenu ellipsis closeOnSelect placement="bottom-end" disabled={disabled}>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
toggleDeleteAttachmentModal(attachmentId);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<TrashIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span>{t("common.actions.delete")}</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
<ActionDropdown
|
||||
items={menuItems}
|
||||
buttonClassName={getIconButtonStyling("ghost", "sm")}
|
||||
placement="bottom-end"
|
||||
disabled={!!disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { Link as Loader } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { getIconButtonStyling } from "@plane/propel/icon-button";
|
||||
import { LinkIcon, EditIcon, TrashIcon, CloseIcon, ChevronRightIcon } from "@plane/propel/icons";
|
||||
// plane imports
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { TIssue, TIssueServiceType, TSubIssueOperations } from "@plane/types";
|
||||
import { EIssueServiceType, EIssuesStoreType } from "@plane/types";
|
||||
import { ControlLink, CustomMenu } from "@plane/ui";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { ActionDropdown, ControlLink } from "@plane/ui";
|
||||
import { cn, generateWorkItemLink } from "@plane/utils";
|
||||
// helpers
|
||||
import { useSubIssueOperations } from "@/components/issues/issue-detail-widgets/sub-issues/helper";
|
||||
|
|
@ -102,6 +104,45 @@ export const SubIssuesListItem = observer(function SubIssuesListItem(props: Prop
|
|||
projectIdentifier: projectDetail?.identifier,
|
||||
sequenceId: issue?.sequence_id,
|
||||
});
|
||||
const menuItems: TContextMenuItem[] = [
|
||||
{
|
||||
key: "edit",
|
||||
action: () => {
|
||||
handleIssueCrudState("update", parentIssueId, { ...issue });
|
||||
toggleCreateIssueModal(true);
|
||||
},
|
||||
title: t("issue.edit"),
|
||||
icon: EditIcon,
|
||||
shouldRender: canEdit,
|
||||
},
|
||||
{
|
||||
key: "copy-link",
|
||||
action: () => {
|
||||
subIssueOperations.copyLink(workItemLink);
|
||||
},
|
||||
title: t("issue.copy_link"),
|
||||
icon: LinkIcon,
|
||||
},
|
||||
{
|
||||
key: "remove-parent",
|
||||
action: () => {
|
||||
if (issue.project_id) subIssueOperations.removeSubIssue(workspaceSlug, issue.project_id, parentIssueId, issue.id);
|
||||
},
|
||||
title: issueServiceType === EIssueServiceType.ISSUES ? t("issue.remove.parent.label") : t("issue.remove.label"),
|
||||
icon: CloseIcon,
|
||||
shouldRender: canEdit,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
action: () => {
|
||||
handleIssueCrudState("delete", parentIssueId, issue);
|
||||
toggleDeleteIssueModal(issue.id);
|
||||
},
|
||||
title: t("issue.delete.label"),
|
||||
icon: TrashIcon,
|
||||
shouldRender: canEdit,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div key={issueId}>
|
||||
|
|
@ -189,62 +230,11 @@ export const SubIssuesListItem = observer(function SubIssuesListItem(props: Prop
|
|||
</div>
|
||||
|
||||
<div className="flex-shrink-0 text-13">
|
||||
<CustomMenu placement="bottom-end" ellipsis>
|
||||
{canEdit && (
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
handleIssueCrudState("update", parentIssueId, { ...issue });
|
||||
toggleCreateIssueModal(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<EditIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span>{t("issue.edit")}</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
subIssueOperations.copyLink(workItemLink);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span>{t("issue.copy_link")}</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
|
||||
{canEdit && (
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (issue.project_id)
|
||||
subIssueOperations.removeSubIssue(workspaceSlug, issue.project_id, parentIssueId, issue.id);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<CloseIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
{issueServiceType === EIssueServiceType.ISSUES
|
||||
? t("issue.remove.parent.label")
|
||||
: t("issue.remove.label")}
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
|
||||
{canEdit && (
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
handleIssueCrudState("delete", parentIssueId, issue);
|
||||
toggleDeleteIssueModal(issue.id);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<TrashIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span>{t("issue.delete.label")}</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</CustomMenu>
|
||||
<ActionDropdown
|
||||
items={menuItems}
|
||||
buttonClassName={getIconButtonStyling("ghost", "sm")}
|
||||
placement="bottom-end"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,18 @@
|
|||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { getIconButtonStyling } from "@plane/propel/icon-button";
|
||||
import { LinkIcon, CopyIcon, EditIcon, TrashIcon } from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { TIssueServiceType } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { ActionDropdown } from "@plane/ui";
|
||||
import { calculateTimeAgo, copyTextToClipboard } from "@plane/utils";
|
||||
// helpers
|
||||
// hooks
|
||||
|
|
@ -45,6 +48,26 @@ export const IssueLinkItem = observer(function IssueLinkItem(props: TIssueLinkIt
|
|||
// const Icon = getIconForLink(linkDetail.url);
|
||||
const faviconUrl: string | undefined = linkDetail.metadata?.favicon;
|
||||
const linkTitle: string | undefined = linkDetail.metadata?.title;
|
||||
const menuItems: TContextMenuItem[] = [
|
||||
{
|
||||
key: "edit",
|
||||
action: () => {
|
||||
toggleIssueLinkModal(true);
|
||||
},
|
||||
title: t("common.actions.edit"),
|
||||
icon: EditIcon,
|
||||
shouldRender: !isNotAllowed,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
action: () => {
|
||||
linkOperations.remove(linkDetail.id);
|
||||
},
|
||||
title: t("common.actions.delete"),
|
||||
icon: TrashIcon,
|
||||
shouldRender: !isNotAllowed,
|
||||
},
|
||||
];
|
||||
|
||||
const toggleIssueLinkModal = (modalToggle: boolean) => {
|
||||
toggleIssueLinkModalStore(modalToggle);
|
||||
|
|
@ -95,32 +118,13 @@ export const IssueLinkItem = observer(function IssueLinkItem(props: TIssueLinkIt
|
|||
>
|
||||
<CopyIcon className="h-3.5 w-3.5 stroke-[1.5]" />
|
||||
</span>
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
buttonClassName="text-placeholder group-hover:text-secondary"
|
||||
<ActionDropdown
|
||||
items={menuItems}
|
||||
button={<MoreHorizontal className="size-3.5" />}
|
||||
buttonClassName={getIconButtonStyling("ghost", "sm") + " text-placeholder group-hover:text-secondary"}
|
||||
placement="bottom-end"
|
||||
closeOnSelect
|
||||
disabled={isNotAllowed}
|
||||
>
|
||||
<CustomMenu.MenuItem
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => {
|
||||
toggleIssueLinkModal(true);
|
||||
}}
|
||||
>
|
||||
<EditIcon className="h-3 w-3 stroke-[1.5] text-secondary" />
|
||||
{t("common.actions.edit")}
|
||||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => {
|
||||
linkOperations.remove(linkDetail.id);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="h-3 w-3" />
|
||||
{t("common.actions.delete")}
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@
|
|||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { getIconButtonStyling } from "@plane/propel/icon-button";
|
||||
import { LinkIcon, EditIcon, TrashIcon, CloseIcon } from "@plane/propel/icons";
|
||||
// plane imports
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { TIssue, TIssueServiceType } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/types";
|
||||
import { ControlLink, CustomMenu } from "@plane/ui";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { ActionDropdown, ControlLink } from "@plane/ui";
|
||||
import { generateWorkItemLink } from "@plane/utils";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
||||
|
|
@ -59,8 +60,8 @@ export const RelationIssueListItem = observer(function RelationIssueListItem(pro
|
|||
const {
|
||||
issue: { getIssueById },
|
||||
removeRelation,
|
||||
toggleCreateIssueModal,
|
||||
toggleDeleteIssueModal,
|
||||
toggleCreateIssueModal,
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const project = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
|
@ -92,32 +93,42 @@ export const RelationIssueListItem = observer(function RelationIssueListItem(pro
|
|||
handleRedirection(workspaceSlug, issue, isMobile);
|
||||
};
|
||||
|
||||
const handleEditIssue = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleIssueCrudState("update", relationIssueId, { ...issue });
|
||||
toggleCreateIssueModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteIssue = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleIssueCrudState("delete", relationIssueId, issue);
|
||||
toggleDeleteIssueModal(relationIssueId);
|
||||
handleIssueCrudState("removeRelation", issueId, issue, relationKey, relationIssueId);
|
||||
};
|
||||
|
||||
const handleCopyIssueLink = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
issueOperations.copyLink(workItemLink);
|
||||
};
|
||||
|
||||
const handleRemoveRelation = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
|
||||
};
|
||||
const menuItems: TContextMenuItem[] = [
|
||||
{
|
||||
key: "edit",
|
||||
action: () => {
|
||||
handleIssueCrudState("update", relationIssueId, { ...issue });
|
||||
toggleCreateIssueModal(true);
|
||||
},
|
||||
title: t("common.actions.edit"),
|
||||
icon: EditIcon,
|
||||
shouldRender: !disabled,
|
||||
},
|
||||
{
|
||||
key: "copy-link",
|
||||
action: () => issueOperations.copyLink(workItemLink),
|
||||
title: t("common.actions.copy_link"),
|
||||
icon: LinkIcon,
|
||||
},
|
||||
{
|
||||
key: "remove-relation",
|
||||
action: () => removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId),
|
||||
title: t("common.actions.remove_relation"),
|
||||
icon: CloseIcon,
|
||||
shouldRender: !disabled,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
action: () => {
|
||||
handleIssueCrudState("delete", relationIssueId, issue);
|
||||
toggleDeleteIssueModal(relationIssueId);
|
||||
handleIssueCrudState("removeRelation", issueId, issue, relationKey, relationIssueId);
|
||||
},
|
||||
title: t("common.actions.delete"),
|
||||
icon: TrashIcon,
|
||||
shouldRender: !disabled,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div key={relationIssueId}>
|
||||
|
|
@ -164,41 +175,11 @@ export const RelationIssueListItem = observer(function RelationIssueListItem(pro
|
|||
/>
|
||||
</div>
|
||||
<div className="flex-shrink-0 pl-2 text-13">
|
||||
<CustomMenu placement="bottom-end" ellipsis>
|
||||
{!disabled && (
|
||||
<CustomMenu.MenuItem onClick={handleEditIssue}>
|
||||
<div className="flex items-center gap-2">
|
||||
<EditIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span>{t("common.actions.edit")}</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
|
||||
<CustomMenu.MenuItem onClick={handleCopyIssueLink}>
|
||||
<div className="flex items-center gap-2">
|
||||
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span>{t("common.actions.copy_link")}</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
|
||||
{!disabled && (
|
||||
<CustomMenu.MenuItem onClick={handleRemoveRelation}>
|
||||
<div className="flex items-center gap-2">
|
||||
<CloseIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span>{t("common.actions.remove_relation")}</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
|
||||
{!disabled && (
|
||||
<CustomMenu.MenuItem onClick={handleDeleteIssue}>
|
||||
<div className="flex items-center gap-2">
|
||||
<TrashIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span>{t("common.actions.delete")}</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</CustomMenu>
|
||||
<ActionDropdown
|
||||
items={menuItems}
|
||||
buttonClassName={getIconButtonStyling("ghost", "sm")}
|
||||
placement="bottom-end"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export interface IActionDropdownProps {
|
|||
button?: TActionDropdownTrigger;
|
||||
buttonClassName?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
items: TContextMenuItem[];
|
||||
menuClassName?: string;
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
|
|
@ -107,13 +108,14 @@ function ActionDropdownItem(props: TActionDropdownItemProps) {
|
|||
}
|
||||
|
||||
export function ActionDropdown(props: IActionDropdownProps) {
|
||||
const { button, buttonClassName, className, items, menuClassName, onOpenChange, placement, portalElement } = props;
|
||||
const { button, buttonClassName, className, disabled = false, items, menuClassName, onOpenChange, placement, portalElement } = props;
|
||||
const dropdownRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const [referenceElement, setReferenceElement] = React.useState<HTMLElement | null>(null);
|
||||
const [popperElement, setPopperElement] = React.useState<HTMLDivElement | null>(null);
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
const renderedItems = items.filter((item) => item.shouldRender !== false);
|
||||
const isDropdownDisabled = disabled || renderedItems.length === 0;
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-end",
|
||||
|
|
@ -141,10 +143,10 @@ export function ActionDropdown(props: IActionDropdownProps) {
|
|||
});
|
||||
|
||||
const openDropdown = React.useCallback(() => {
|
||||
if (renderedItems.length === 0) return;
|
||||
if (isDropdownDisabled) return;
|
||||
setIsOpen(true);
|
||||
onOpenChange?.(true);
|
||||
}, [onOpenChange, renderedItems.length]);
|
||||
}, [isDropdownDisabled, onOpenChange]);
|
||||
|
||||
const closeDropdown = React.useCallback(() => {
|
||||
if (!isOpen) return;
|
||||
|
|
@ -156,7 +158,7 @@ export function ActionDropdown(props: IActionDropdownProps) {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (renderedItems.length === 0) return;
|
||||
if (isDropdownDisabled) return;
|
||||
|
||||
if (isOpen) {
|
||||
closeDropdown();
|
||||
|
|
@ -177,14 +179,14 @@ export function ActionDropdown(props: IActionDropdownProps) {
|
|||
className={cn(
|
||||
"clickable relative grid place-items-center rounded-sm border-0 bg-transparent p-1 text-secondary shadow-none outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 hover:text-primary",
|
||||
{
|
||||
"cursor-not-allowed": renderedItems.length === 0,
|
||||
"cursor-pointer": renderedItems.length > 0,
|
||||
"cursor-not-allowed": isDropdownDisabled,
|
||||
"cursor-pointer": !isDropdownDisabled,
|
||||
},
|
||||
buttonClassName
|
||||
)}
|
||||
onClick={handleTriggerClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={renderedItems.length === 0}
|
||||
disabled={isDropdownDisabled}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={isOpen}
|
||||
aria-label="Work item actions"
|
||||
|
|
@ -201,14 +203,14 @@ export function ActionDropdown(props: IActionDropdownProps) {
|
|||
className={cn(
|
||||
"clickable block h-full rounded-full border-0 bg-transparent p-0 shadow-none outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0",
|
||||
{
|
||||
"cursor-not-allowed": renderedItems.length === 0,
|
||||
"cursor-pointer": renderedItems.length > 0,
|
||||
"cursor-not-allowed": isDropdownDisabled,
|
||||
"cursor-pointer": !isDropdownDisabled,
|
||||
},
|
||||
buttonClassName
|
||||
)}
|
||||
onClick={handleTriggerClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={renderedItems.length === 0}
|
||||
disabled={isDropdownDisabled}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={isOpen}
|
||||
aria-label="Work item actions"
|
||||
|
|
|
|||
Loading…
Reference in New Issue