UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: миграция secondary detail action-menu на ActionDropdown

This commit is contained in:
DCCONSTRUCTIONS 2026-04-22 12:31:17 +03:00
parent b0173c82e6
commit c86fc16cdf
6 changed files with 176 additions and 215 deletions

View File

@ -10,12 +10,11 @@ import { MoreHorizontal } from "lucide-react";
// plane imports // plane imports
import { EIssueCommentAccessSpecifier } from "@plane/constants"; import { EIssueCommentAccessSpecifier } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; 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 { LinkIcon, GlobeIcon, LockIcon, EditIcon, TrashIcon } from "@plane/propel/icons";
import type { TIssueComment, TCommentsOperations } from "@plane/types"; import type { TIssueComment, TCommentsOperations } from "@plane/types";
import type { TContextMenuItem } from "@plane/ui"; import type { TContextMenuItem } from "@plane/ui";
import { CustomMenu } from "@plane/ui"; import { ActionDropdown } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks // hooks
import { useUser } from "@/hooks/store/user"; import { useUser } from "@/hooks/store/user";
@ -84,39 +83,11 @@ export const CommentQuickActions = observer(function CommentQuickActions(props:
); );
return ( return (
<CustomMenu customButton={<IconButton icon={MoreHorizontal} variant="ghost" size="sm" />} closeOnSelect> <ActionDropdown
{MENU_ITEMS.map((item) => { items={MENU_ITEMS}
if (item.shouldRender === false) return null; button={<MoreHorizontal className="size-3.5" />}
buttonClassName={getIconButtonStyling("ghost", "sm")}
return ( placement="bottom-end"
<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>
); );
}); });

View File

@ -7,12 +7,14 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { getIconButtonStyling } from "@plane/propel/icon-button";
import { TrashIcon } from "@plane/propel/icons"; import { TrashIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip"; import { Tooltip } from "@plane/propel/tooltip";
import type { TIssueServiceType } from "@plane/types"; import type { TIssueServiceType } from "@plane/types";
import { EIssueServiceType } from "@plane/types"; import { EIssueServiceType } from "@plane/types";
// ui // 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"; import { convertBytesToSize, getFileExtension, getFileName, getFileURL, renderFormattedDate } from "@plane/utils";
// components // components
// //
@ -46,19 +48,37 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
const fileExtension = getFileExtension(attachment?.attributes.name ?? ""); const fileExtension = getFileExtension(attachment?.attributes.name ?? "");
const fileIcon = getFileIcon(fileExtension, 18); const fileIcon = getFileIcon(fileExtension, 18);
const fileURL = getFileURL(attachment?.asset_url ?? ""); const fileURL = getFileURL(attachment?.asset_url ?? "");
const menuItems: TContextMenuItem[] = [
{
key: "delete",
action: () => {
toggleDeleteAttachmentModal(attachmentId);
},
title: t("common.actions.delete"),
icon: TrashIcon,
},
];
// hooks // hooks
const { isMobile } = usePlatformOS(); const { isMobile } = usePlatformOS();
if (!attachment) return <></>; if (!attachment) return <></>;
return ( return (
<> <div
<button role="button"
tabIndex={0}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
window.open(fileURL, "_blank"); 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 truncate text-13">
@ -86,21 +106,14 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
</> </>
)} )}
<CustomMenu ellipsis closeOnSelect placement="bottom-end" disabled={disabled}> <ActionDropdown
<CustomMenu.MenuItem items={menuItems}
onClick={() => { buttonClassName={getIconButtonStyling("ghost", "sm")}
toggleDeleteAttachmentModal(attachmentId); placement="bottom-end"
}} disabled={!!disabled}
> />
<div className="flex items-center gap-2"> </div>
<TrashIcon className="h-3.5 w-3.5" strokeWidth={2} />
<span>{t("common.actions.delete")}</span>
</div>
</CustomMenu.MenuItem>
</CustomMenu>
</div> </div>
</div> </div>
</button>
</>
); );
}); });

View File

@ -7,12 +7,14 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Link as Loader } from "lucide-react"; import { Link as Loader } from "lucide-react";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { getIconButtonStyling } from "@plane/propel/icon-button";
import { LinkIcon, EditIcon, TrashIcon, CloseIcon, ChevronRightIcon } from "@plane/propel/icons"; import { LinkIcon, EditIcon, TrashIcon, CloseIcon, ChevronRightIcon } from "@plane/propel/icons";
// plane imports // plane imports
import { Tooltip } from "@plane/propel/tooltip"; import { Tooltip } from "@plane/propel/tooltip";
import type { TIssue, TIssueServiceType, TSubIssueOperations } from "@plane/types"; import type { TIssue, TIssueServiceType, TSubIssueOperations } from "@plane/types";
import { EIssueServiceType, EIssuesStoreType } 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"; import { cn, generateWorkItemLink } from "@plane/utils";
// helpers // helpers
import { useSubIssueOperations } from "@/components/issues/issue-detail-widgets/sub-issues/helper"; 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, projectIdentifier: projectDetail?.identifier,
sequenceId: issue?.sequence_id, 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 ( return (
<div key={issueId}> <div key={issueId}>
@ -189,62 +230,11 @@ export const SubIssuesListItem = observer(function SubIssuesListItem(props: Prop
</div> </div>
<div className="flex-shrink-0 text-13"> <div className="flex-shrink-0 text-13">
<CustomMenu placement="bottom-end" ellipsis> <ActionDropdown
{canEdit && ( items={menuItems}
<CustomMenu.MenuItem buttonClassName={getIconButtonStyling("ghost", "sm")}
onClick={() => { placement="bottom-end"
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>
</div> </div>
</div> </div>
)} )}

View File

@ -5,15 +5,18 @@
*/ */
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { MoreHorizontal } from "lucide-react";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { getIconButtonStyling } from "@plane/propel/icon-button";
import { LinkIcon, CopyIcon, EditIcon, TrashIcon } from "@plane/propel/icons"; import { LinkIcon, CopyIcon, EditIcon, TrashIcon } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { Tooltip } from "@plane/propel/tooltip"; import { Tooltip } from "@plane/propel/tooltip";
import type { TIssueServiceType } from "@plane/types"; import type { TIssueServiceType } from "@plane/types";
import { EIssueServiceType } from "@plane/types"; import { EIssueServiceType } from "@plane/types";
// ui // ui
import { CustomMenu } from "@plane/ui"; import type { TContextMenuItem } from "@plane/ui";
import { ActionDropdown } from "@plane/ui";
import { calculateTimeAgo, copyTextToClipboard } from "@plane/utils"; import { calculateTimeAgo, copyTextToClipboard } from "@plane/utils";
// helpers // helpers
// hooks // hooks
@ -45,6 +48,26 @@ export const IssueLinkItem = observer(function IssueLinkItem(props: TIssueLinkIt
// const Icon = getIconForLink(linkDetail.url); // const Icon = getIconForLink(linkDetail.url);
const faviconUrl: string | undefined = linkDetail.metadata?.favicon; const faviconUrl: string | undefined = linkDetail.metadata?.favicon;
const linkTitle: string | undefined = linkDetail.metadata?.title; 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) => { const toggleIssueLinkModal = (modalToggle: boolean) => {
toggleIssueLinkModalStore(modalToggle); 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]" /> <CopyIcon className="h-3.5 w-3.5 stroke-[1.5]" />
</span> </span>
<CustomMenu <ActionDropdown
ellipsis items={menuItems}
buttonClassName="text-placeholder group-hover:text-secondary" button={<MoreHorizontal className="size-3.5" />}
buttonClassName={getIconButtonStyling("ghost", "sm") + " text-placeholder group-hover:text-secondary"}
placement="bottom-end" placement="bottom-end"
closeOnSelect
disabled={isNotAllowed} 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>
</div> </div>
</> </>

View File

@ -4,15 +4,16 @@
* See the LICENSE file for details. * See the LICENSE file for details.
*/ */
import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { getIconButtonStyling } from "@plane/propel/icon-button";
import { LinkIcon, EditIcon, TrashIcon, CloseIcon } from "@plane/propel/icons"; import { LinkIcon, EditIcon, TrashIcon, CloseIcon } from "@plane/propel/icons";
// plane imports // plane imports
import { Tooltip } from "@plane/propel/tooltip"; import { Tooltip } from "@plane/propel/tooltip";
import type { TIssue, TIssueServiceType } from "@plane/types"; import type { TIssue, TIssueServiceType } from "@plane/types";
import { EIssueServiceType } 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"; import { generateWorkItemLink } from "@plane/utils";
// hooks // hooks
import { useIssueDetail } from "@/hooks/store/use-issue-detail"; import { useIssueDetail } from "@/hooks/store/use-issue-detail";
@ -59,8 +60,8 @@ export const RelationIssueListItem = observer(function RelationIssueListItem(pro
const { const {
issue: { getIssueById }, issue: { getIssueById },
removeRelation, removeRelation,
toggleCreateIssueModal,
toggleDeleteIssueModal, toggleDeleteIssueModal,
toggleCreateIssueModal,
} = useIssueDetail(issueServiceType); } = useIssueDetail(issueServiceType);
const project = useProject(); const project = useProject();
const { isMobile } = usePlatformOS(); const { isMobile } = usePlatformOS();
@ -92,32 +93,42 @@ export const RelationIssueListItem = observer(function RelationIssueListItem(pro
handleRedirection(workspaceSlug, issue, isMobile); handleRedirection(workspaceSlug, issue, isMobile);
}; };
const handleEditIssue = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { const menuItems: TContextMenuItem[] = [
e.stopPropagation(); {
e.preventDefault(); key: "edit",
action: () => {
handleIssueCrudState("update", relationIssueId, { ...issue }); handleIssueCrudState("update", relationIssueId, { ...issue });
toggleCreateIssueModal(true); toggleCreateIssueModal(true);
}; },
title: t("common.actions.edit"),
const handleDeleteIssue = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { icon: EditIcon,
e.stopPropagation(); shouldRender: !disabled,
e.preventDefault(); },
{
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); handleIssueCrudState("delete", relationIssueId, issue);
toggleDeleteIssueModal(relationIssueId); toggleDeleteIssueModal(relationIssueId);
handleIssueCrudState("removeRelation", issueId, issue, relationKey, relationIssueId); handleIssueCrudState("removeRelation", issueId, issue, relationKey, relationIssueId);
}; },
title: t("common.actions.delete"),
const handleCopyIssueLink = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { icon: TrashIcon,
e.stopPropagation(); shouldRender: !disabled,
e.preventDefault(); },
issueOperations.copyLink(workItemLink); ];
};
const handleRemoveRelation = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault();
e.stopPropagation();
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
};
return ( return (
<div key={relationIssueId}> <div key={relationIssueId}>
@ -164,41 +175,11 @@ export const RelationIssueListItem = observer(function RelationIssueListItem(pro
/> />
</div> </div>
<div className="flex-shrink-0 pl-2 text-13"> <div className="flex-shrink-0 pl-2 text-13">
<CustomMenu placement="bottom-end" ellipsis> <ActionDropdown
{!disabled && ( items={menuItems}
<CustomMenu.MenuItem onClick={handleEditIssue}> buttonClassName={getIconButtonStyling("ghost", "sm")}
<div className="flex items-center gap-2"> placement="bottom-end"
<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>
</div> </div>
</div> </div>
)} )}

View File

@ -21,6 +21,7 @@ export interface IActionDropdownProps {
button?: TActionDropdownTrigger; button?: TActionDropdownTrigger;
buttonClassName?: string; buttonClassName?: string;
className?: string; className?: string;
disabled?: boolean;
items: TContextMenuItem[]; items: TContextMenuItem[];
menuClassName?: string; menuClassName?: string;
onOpenChange?: (isOpen: boolean) => void; onOpenChange?: (isOpen: boolean) => void;
@ -107,13 +108,14 @@ function ActionDropdownItem(props: TActionDropdownItemProps) {
} }
export function ActionDropdown(props: IActionDropdownProps) { 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 dropdownRef = React.useRef<HTMLDivElement | null>(null);
const [referenceElement, setReferenceElement] = React.useState<HTMLElement | null>(null); const [referenceElement, setReferenceElement] = React.useState<HTMLElement | null>(null);
const [popperElement, setPopperElement] = React.useState<HTMLDivElement | null>(null); const [popperElement, setPopperElement] = React.useState<HTMLDivElement | null>(null);
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = React.useState(false);
const renderedItems = items.filter((item) => item.shouldRender !== false); const renderedItems = items.filter((item) => item.shouldRender !== false);
const isDropdownDisabled = disabled || renderedItems.length === 0;
const { styles, attributes } = usePopper(referenceElement, popperElement, { const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-end", placement: placement ?? "bottom-end",
@ -141,10 +143,10 @@ export function ActionDropdown(props: IActionDropdownProps) {
}); });
const openDropdown = React.useCallback(() => { const openDropdown = React.useCallback(() => {
if (renderedItems.length === 0) return; if (isDropdownDisabled) return;
setIsOpen(true); setIsOpen(true);
onOpenChange?.(true); onOpenChange?.(true);
}, [onOpenChange, renderedItems.length]); }, [isDropdownDisabled, onOpenChange]);
const closeDropdown = React.useCallback(() => { const closeDropdown = React.useCallback(() => {
if (!isOpen) return; if (!isOpen) return;
@ -156,7 +158,7 @@ export function ActionDropdown(props: IActionDropdownProps) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
if (renderedItems.length === 0) return; if (isDropdownDisabled) return;
if (isOpen) { if (isOpen) {
closeDropdown(); closeDropdown();
@ -177,14 +179,14 @@ export function ActionDropdown(props: IActionDropdownProps) {
className={cn( 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", "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-not-allowed": isDropdownDisabled,
"cursor-pointer": renderedItems.length > 0, "cursor-pointer": !isDropdownDisabled,
}, },
buttonClassName buttonClassName
)} )}
onClick={handleTriggerClick} onClick={handleTriggerClick}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
disabled={renderedItems.length === 0} disabled={isDropdownDisabled}
aria-haspopup="menu" aria-haspopup="menu"
aria-expanded={isOpen} aria-expanded={isOpen}
aria-label="Work item actions" aria-label="Work item actions"
@ -201,14 +203,14 @@ export function ActionDropdown(props: IActionDropdownProps) {
className={cn( 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", "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-not-allowed": isDropdownDisabled,
"cursor-pointer": renderedItems.length > 0, "cursor-pointer": !isDropdownDisabled,
}, },
buttonClassName buttonClassName
)} )}
onClick={handleTriggerClick} onClick={handleTriggerClick}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
disabled={renderedItems.length === 0} disabled={isDropdownDisabled}
aria-haspopup="menu" aria-haspopup="menu"
aria-expanded={isOpen} aria-expanded={isOpen}
aria-label="Work item actions" aria-label="Work item actions"