- {sourceContourName}
+ const header = (
+
+
-
-
-
-
updateIssue?.(issue.project_id ?? null, issue.id, { assignee_ids: assigneeIds })}
+
+ {quickActions({
+ issue,
+ parentRef: cardRef,
+ customActionButton,
+ })}
+
updateIssue?.(issue.project_id ?? null, issue.id, { priority })}
disabled={isReadOnly || !updateIssue}
button={
-
-
+
+ }
+ />
+
updateIssue?.(issue.project_id ?? null, issue.id, { state_id: stateId })}
+ disabled={isReadOnly || !updateIssue}
+ button={
+
+
}
/>
-
-
-
- updateIssue?.(issue.project_id ?? null, issue.id, {
- target_date: targetDate ? renderFormattedPayloadDate(targetDate) : null,
- })
- }
- disabled={isReadOnly || !updateIssue}
- button={
-
-
- {dueDateLabel}
-
- }
- />
-
);
+
+ const footer = (
+ <>
+ updateIssue?.(issue.project_id ?? null, issue.id, { assignee_ids: assigneeIds })}
+ disabled={isReadOnly || !updateIssue}
+ button={
+
+
+
+ }
+ />
+
+
+
+ updateIssue?.(issue.project_id ?? null, issue.id, {
+ target_date: targetDate ? renderFormattedPayloadDate(targetDate) : null,
+ })
+ }
+ disabled={isReadOnly || !updateIssue}
+ button={
+
+
+ {dueDateLabel}
+
+ }
+ />
+
+ >
+ );
+
+ return (
+
+ );
});
diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/list/list-view-types.d.ts b/plane-src/apps/web/core/components/issues/issue-layouts/list/list-view-types.d.ts
index 5de43cb..7f5164e 100644
--- a/plane-src/apps/web/core/components/issues/issue-layouts/list/list-view-types.d.ts
+++ b/plane-src/apps/web/core/components/issues/issue-layouts/list/list-view-types.d.ts
@@ -10,8 +10,8 @@ export interface IQuickActionProps {
handleArchive?: () => Promise;
handleRestore?: () => Promise;
handleMoveToIssues?: () => Promise;
- customActionButton?: React.ReactElement;
- portalElement?: HTMLDivElement | null;
+ customActionButton?: React.ReactNode;
+ portalElement?: Element | null;
readOnly?: boolean;
placements?: TPlacement;
}
@@ -25,7 +25,7 @@ export type TRenderQuickActions = ({
}: {
issue: TIssue;
parentRef: React.RefObject;
- customActionButton?: React.ReactElement;
+ customActionButton?: React.ReactNode;
placement?: TPlacement;
- portalElement?: HTMLDivElement | null;
+ portalElement?: Element | null;
}) => React.ReactNode;
diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx
index 91fc2e2..1d74e71 100644
--- a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx
+++ b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx
@@ -12,8 +12,7 @@ import { useParams } from "next/navigation";
import { ARCHIVABLE_STATE_GROUPS } from "@plane/constants";
import type { TIssue } from "@plane/types";
import { EIssuesStoreType } from "@plane/types";
-import { ContextMenu, CustomMenu } from "@plane/ui";
-import { cn } from "@plane/utils";
+import { ActionDropdown, ContextMenu } from "@plane/ui";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useProjectState } from "@/hooks/store/use-project-state";
@@ -136,115 +135,7 @@ export const AllIssueQuickActions = observer(function AllIssueQuickActions(props
)}
-
- {MENU_ITEMS.map((item) => {
- if (item.shouldRender === false) return null;
-
- // Render submenu if nestedMenuItems exist
- if (item.nestedMenuItems && item.nestedMenuItems.length > 0) {
- return (
-
- {item.icon && }
- {item.title}
- {item.description && (
-
- {item.description}
-
- )}
-
- }
- disabled={item.disabled}
- className={cn(
- "flex items-center gap-2",
- {
- "text-placeholder": item.disabled,
- },
- item.className
- )}
- >
- {item.nestedMenuItems.map((nestedItem) => (
-
{
- nestedItem.action();
- }}
- className={cn(
- "flex items-center gap-2",
- {
- "text-placeholder": nestedItem.disabled,
- },
- nestedItem.className
- )}
- disabled={nestedItem.disabled}
- >
- {nestedItem.icon && }
-
-
{nestedItem.title}
- {nestedItem.description && (
-
- {nestedItem.description}
-
- )}
-
-
- ))}
-
- );
- }
-
- // Render regular menu item
- return (
-
{
- item.action();
- }}
- className={cn(
- "flex items-center gap-2",
- {
- "text-placeholder": item.disabled,
- },
- item.className
- )}
- disabled={item.disabled}
- >
- {item.icon && }
-
-
{item.title}
- {item.description && (
-
- {item.description}
-
- )}
-
-
- );
- })}
-
+
>
);
});
diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx
index beb885b..6efc698 100644
--- a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx
+++ b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx
@@ -10,8 +10,7 @@ import { useParams } from "next/navigation";
// ui
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { EIssuesStoreType } from "@plane/types";
-import { ContextMenu, CustomMenu } from "@plane/ui";
-import { cn } from "@plane/utils";
+import { ActionDropdown, ContextMenu } from "@plane/ui";
// hooks
import { useIssues } from "@/hooks/store/use-issues";
import { useUserPermissions } from "@/hooks/store/user";
@@ -85,50 +84,7 @@ export const ArchivedIssueQuickActions = observer(function ArchivedIssueQuickAct
/>
-
- {MENU_ITEMS.map((item) => {
- if (item.shouldRender === false) return null;
- return (
- {
- item.action();
- }}
- className={cn(
- "flex items-center gap-2",
- {
- "text-placeholder": item.disabled,
- },
- item.className
- )}
- disabled={item.disabled}
- >
- {item.icon && }
-
-
{item.title}
- {item.description && (
-
- {item.description}
-
- )}
-
-
- );
- })}
-
+
>
);
});
diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx
index bcd9ed8..ec9d2de 100644
--- a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx
+++ b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx
@@ -12,8 +12,7 @@ import { useParams } from "next/navigation";
import { ARCHIVABLE_STATE_GROUPS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import type { TIssue } from "@plane/types";
import { EIssuesStoreType } from "@plane/types";
-import { ContextMenu, CustomMenu } from "@plane/ui";
-import { cn } from "@plane/utils";
+import { ActionDropdown, ContextMenu } from "@plane/ui";
// hooks
import { useIssues } from "@/hooks/store/use-issues";
import { useProject } from "@/hooks/store/use-project";
@@ -149,115 +148,7 @@ export const CycleIssueQuickActions = observer(function CycleIssueQuickActions(p
)}
-
- {MENU_ITEMS.map((item) => {
- if (item.shouldRender === false) return null;
-
- // Render submenu if nestedMenuItems exist
- if (item.nestedMenuItems && item.nestedMenuItems.length > 0) {
- return (
-
- {item.icon && }
- {item.title}
- {item.description && (
-
- {item.description}
-
- )}
-
- }
- disabled={item.disabled}
- className={cn(
- "flex items-center gap-2",
- {
- "text-placeholder": item.disabled,
- },
- item.className
- )}
- >
- {item.nestedMenuItems.map((nestedItem) => (
-
{
- nestedItem.action();
- }}
- className={cn(
- "flex items-center gap-2",
- {
- "text-placeholder": nestedItem.disabled,
- },
- nestedItem.className
- )}
- disabled={nestedItem.disabled}
- >
- {nestedItem.icon && }
-
-
{nestedItem.title}
- {nestedItem.description && (
-
- {nestedItem.description}
-
- )}
-
-
- ))}
-
- );
- }
-
- // Render regular menu item
- return (
-
{
- item.action();
- }}
- className={cn(
- "flex items-center gap-2",
- {
- "text-placeholder": item.disabled,
- },
- item.className
- )}
- disabled={item.disabled}
- >
- {item.icon && }
-
-
{item.title}
- {item.description && (
-
- {item.description}
-
- )}
-
-
- );
- })}
-
+
>
);
});
diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx
index a7f33ba..3c19633 100644
--- a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx
+++ b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx
@@ -4,6 +4,7 @@
* See the LICENSE file for details.
*/
+import type { ReactNode } from "react";
import { useMemo } from "react";
import { XCircle, ArchiveRestoreIcon } from "lucide-react";
// plane imports
@@ -11,7 +12,8 @@ import { useTranslation } from "@plane/i18n";
import { LinkIcon, CopyIcon, NewTabIcon, EditIcon, ArchiveIcon, TrashIcon } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { EIssuesStoreType, TIssue } from "@plane/types";
-import type { TContextMenuItem } from "@plane/ui";
+import { CustomMenu, type TContextMenuItem } from "@plane/ui";
+import { cn } from "@plane/utils";
import { copyUrlToClipboard, generateWorkItemLink } from "@plane/utils";
// types
import { createCopyMenuWithDuplication } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns";
@@ -80,6 +82,68 @@ export interface MenuItemFactoryProps {
storeType?: EIssuesStoreType;
}
+export const QUICK_ACTION_MENU_LAYER_CLASS_NAME = "z-[220]";
+
+const renderQuickActionMenuItemContent = (item: TContextMenuItem) =>
+ item.customContent ?? (
+
+ {item.icon &&
}
+
+ {item.title &&
{item.title}
}
+ {item.description && (
+
+ {item.description}
+
+ )}
+
+
+ );
+
+export const renderQuickActionMenuItems = (items: TContextMenuItem[]): ReactNode[] =>
+ items.map((item) => {
+ if (item.shouldRender === false) return null;
+
+ if (item.nestedMenuItems?.some((nestedItem) => nestedItem.shouldRender !== false)) {
+ return (
+
+ {renderQuickActionMenuItems(item.nestedMenuItems)}
+
+ );
+ }
+
+ return (
+
item.action()}
+ className={cn(
+ "flex items-center gap-2",
+ {
+ "text-placeholder": item.disabled,
+ },
+ item.className
+ )}
+ disabled={item.disabled}
+ >
+ {renderQuickActionMenuItemContent(item)}
+
+ );
+ });
+
// Common action handlers hook
export const useIssueActionHandlers = (props: MenuItemFactoryProps) => {
const { issue, workspaceSlug, projectIdentifier, handleRestore } = props;
diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx
index d64580f..3ee1343 100644
--- a/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx
+++ b/plane-src/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx
@@ -13,8 +13,7 @@ import { Ellipsis } from "lucide-react";
import { ARCHIVABLE_STATE_GROUPS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import type { TIssue } from "@plane/types";
import { EIssuesStoreType } from "@plane/types";
-import { ContextMenu, CustomMenu } from "@plane/ui";
-import { cn } from "@plane/utils";
+import { ActionDropdown, ContextMenu } from "@plane/ui";
// hooks
import { useIssues } from "@/hooks/store/use-issues";
import { useProject } from "@/hooks/store/use-project";
@@ -29,7 +28,6 @@ import { CreateUpdateIssueModal } from "../../issue-modal/modal";
import type { IQuickActionProps } from "../list/list-view-types";
import type { MenuItemFactoryProps } from "./helper";
import { useWorkItemDetailMenuItems } from "./helper";
-import { IconButton } from "@plane/propel/icon-button";
type TWorkItemDetailQuickActionProps = IQuickActionProps & {
toggleEditIssueModal?: (value: boolean) => void;
@@ -240,114 +238,21 @@ export const WorkItemDetailQuickActions = observer(function WorkItemDetailQuickA
)}
-
+
+
+ }
+ items={MENU_ITEMS}
placement={placements}
- customButton={
}
portalElement={portalElement}
- menuItemsClassName="z-[14]"
- maxHeight="lg"
- closeOnSelect
- >
- {MENU_ITEMS.map((item) => {
- if (item.shouldRender === false) return null;
-
- // Render submenu if nestedMenuItems exist
- if (item.nestedMenuItems && item.nestedMenuItems.length > 0) {
- return (
-