chore: AppIDE new Entity Explorer changes (#39557)

## Description

Just UI changes from #39093 


Fixes #39556

## Automation

/ok-to-test tags="@tag.IDE"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/13673781581>
> Commit: b78e21d50dce1e6af78e40237a9a0bced5b35bc7
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13673781581&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.IDE`
> Spec:
> <hr>Wed, 05 Mar 2025 10:57:58 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Enhanced editable text components now support name normalization with
dynamic ellipsis and improved tooltip handling.
- Entity and widget list views now display names directly with updated
expand/collapse icons and refined selection behavior.
- Context menus reflect conditional interactivity by disabling options
based on permissions and item types.
- New identifiers for list items and context menus improve testing
capabilities.

- **Refactor**
- Consolidated property handling and class name assignments across
components for consistent behavior.
- Streamlined data structures and conditional rendering in entity
explorers and context menus to enhance clarity and maintainability.
- Updated components to use direct property access for improved
performance and readability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Hetu Nandu 2025-03-06 11:22:27 +05:30 committed by GitHub
parent 44de3c82b0
commit defc0a93f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 172 additions and 95 deletions

View File

@ -119,7 +119,7 @@ function ListItem(props: ListItemProps) {
return (
<StyledListItem
className={clsx(ListItemClassName, props.className)}
className={clsx(ListItemClassName, props.className, "t--ide-list-item")}
data-disabled={props.isDisabled || false}
data-isblockdescription={isBlockDescription}
data-rightcontrolvisibility={rightControlVisibility}

View File

@ -1,11 +1,17 @@
import React, { useMemo } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { Spinner, Tooltip } from "../..";
import { Spinner } from "../../Spinner";
import { Tooltip } from "../../Tooltip";
import { useEditableText } from "../../__hooks__";
import * as Styled from "./EditableEntityName.styles";
import type { EditableEntityNameProps } from "./EditableEntityName.types";
import clsx from "clsx";
export const isEllipsisActive = (element: HTMLElement | null) => {
return element && element.clientWidth < element.scrollWidth;
};
export const EditableEntityName = (props: EditableEntityNameProps) => {
const {
@ -16,13 +22,17 @@ export const EditableEntityName = (props: EditableEntityNameProps) => {
isFixedWidth,
isLoading,
name,
normalizeName = false,
onExitEditing,
onNameSave,
showEllipsis = false,
size = "small",
validateName,
} = props;
const inEditMode = canEdit ? isEditing : false;
const [showTooltip, setShowTooltip] = useState(false);
const longNameRef = useRef<HTMLDivElement | null>(null);
const [
inputRef,
@ -36,6 +46,7 @@ export const EditableEntityName = (props: EditableEntityNameProps) => {
onExitEditing,
validateName,
onNameSave,
normalizeName,
);
// When in loading state, start icon becomes the loading icon
@ -58,30 +69,54 @@ export const EditableEntityName = (props: EditableEntityNameProps) => {
paddingTop: "4px",
paddingBottom: "4px",
top: "-5px",
placeholder: "Name",
},
}),
[handleKeyUp, handleTitleChange, inputTestId],
);
useEffect(
function handleShowTooltipOnEllipsis() {
if (showEllipsis) {
setShowTooltip(!!isEllipsisActive(longNameRef.current));
}
},
[editableName, showEllipsis],
);
return (
<Styled.Root data-size={size}>
{startIcon}
<Tooltip
content={validationError}
placement="bottom"
visible={Boolean(validationError)}
content={name}
isDisabled={!showTooltip}
key="entity-name"
mouseEnterDelay={1}
placement="topLeft"
showArrow={false}
>
<Styled.Text
aria-invalid={Boolean(validationError)}
data-isediting={inEditMode}
data-isfixedwidth={isFixedWidth}
inputProps={inputProps}
inputRef={inputRef}
isEditable={inEditMode}
kind={size === "small" ? "body-s" : "body-m"}
<Tooltip
content={validationError}
isDisabled={false}
mouseEnterDelay={0}
placement="bottom"
showArrow
visible={Boolean(validationError)}
>
{editableName}
</Styled.Text>
<Styled.Text
aria-invalid={Boolean(validationError)}
className={clsx("t--entity-name", { editing: inEditMode })}
data-isediting={inEditMode}
data-isfixedwidth={isFixedWidth}
inputProps={inputProps}
inputRef={inputRef}
isEditable={inEditMode}
kind={size === "small" ? "body-s" : "body-m"}
ref={showEllipsis ? longNameRef : null}
>
{editableName}
</Styled.Text>
</Tooltip>
</Tooltip>
</Styled.Root>
);

View File

@ -23,4 +23,8 @@ export interface EditableEntityNameProps {
onNameSave: (name: string) => void;
/** Function to validate the name. */
validateName: (name: string) => string | null;
/** Whether a name should be normalized on renaming */
normalizeName?: boolean;
/** Used for showing ellipsis for longer names */
showEllipsis?: boolean;
}

View File

@ -14,7 +14,7 @@ import {
import * as Styled from "./EntityContextMenu.styles";
interface Props {
dataTestid?: string;
dataTestId?: string;
children?: React.ReactNode[] | React.ReactNode;
tooltipContent?: React.ReactNode;
}
@ -22,7 +22,7 @@ interface Props {
export const EntityContextMenu = (props: Props) => {
const {
children,
dataTestid = DEFAULT_DATA_TEST_ID,
dataTestId = DEFAULT_DATA_TEST_ID,
tooltipContent = DEFAULT_TOOLTIP_CONTENT,
} = props;
@ -40,7 +40,7 @@ export const EntityContextMenu = (props: Props) => {
>
<Button
className={EntityClassNames.CONTEXT_MENU}
data-testid={dataTestid}
data-testid={dataTestId}
isIconButton
kind="tertiary"
startIcon="more-2-fill"

View File

@ -69,6 +69,7 @@ const EntityGroup = <T,>({
group.renderList(item)
) : (
<ListItem
dataTestId={`entity-group-item-${(item as ListItemProps)?.title}`}
key={(item as ListItemProps)?.title || `item-${index}`}
{...(item as ListItemProps)}
/>

View File

@ -17,6 +17,7 @@ export const EntityItem = (props: EntityItemProps) => {
canEdit,
isEditing,
isLoading,
normalizeName = false,
onEditComplete,
onNameSave,
validateName,
@ -34,8 +35,10 @@ export const EntityItem = (props: EntityItemProps) => {
isFixedWidth
isLoading={isLoading}
name={props.title}
normalizeName={normalizeName}
onExitEditing={onEditComplete}
onNameSave={onNameSave}
showEllipsis
size="medium"
validateName={validateName}
/>
@ -44,6 +47,7 @@ export const EntityItem = (props: EntityItemProps) => {
canEdit,
isEditing,
isLoading,
normalizeName,
onEditComplete,
onNameSave,
props.title,
@ -65,7 +69,7 @@ export const EntityItem = (props: EntityItemProps) => {
{...rest}
className={clx("t--entity-item", props.className)}
customTitleComponent={customTitle}
data-testid={`t--entity-item-${props.title}`}
dataTestId={`t--entity-item-${props.title}`}
id={"entity-" + props.id}
onDoubleClick={doubleClickOverride}
rightControl={rightControl}

View File

@ -21,5 +21,7 @@ export interface EntityItemProps
onNameSave: (newName: string) => void;
// Provide a function validate the new name
validateName: (newName: string) => string | null;
// Whether entity name should be normalized on renaming
normalizeName?: boolean;
};
}

View File

@ -28,36 +28,31 @@ const nameEditorConfig = {
validateName: () => null,
};
const names = {
"1": "Parent 1",
"1.1": "Child 1.1",
"1.1.1": "Child 1.1.1",
"1.1.2": "Child 1.1.2",
"1.2": "Child 1.2",
"2": "Parent 2",
};
const Tree: EntityListTreeProps["items"] = [
{
id: "1",
isExpanded: true,
isSelected: false,
name: "Parent 1",
children: [
{
id: "1.1",
isExpanded: false,
isSelected: true,
name: "Child 1.1",
children: [
{
id: "1.1.1",
isExpanded: false,
isSelected: false,
name: "Child 1.1.1",
},
{
id: "1.1.2",
isDisabled: true,
isExpanded: false,
isSelected: false,
name: "Child 1.1.2",
},
],
},
@ -65,6 +60,7 @@ const Tree: EntityListTreeProps["items"] = [
id: "1.2",
isExpanded: false,
isSelected: false,
name: "Child 1.2",
},
],
},
@ -72,6 +68,7 @@ const Tree: EntityListTreeProps["items"] = [
id: "2",
isExpanded: false,
isSelected: false,
name: "Parent 2",
},
];
@ -111,7 +108,7 @@ const EntityItemComponent = (props: { item: EntityListTreeItem }) => {
onClick={noop}
onDoubleClick={() => onItemEdit(item.id)}
startIcon={<Icon name="apps-line" />}
title={names[item.id as keyof typeof names] || item.id}
title={item.name}
/>
);
};

View File

@ -8,18 +8,8 @@ import type {
const mockOnItemExpand = jest.fn();
const name = {
"1": "Parent 1",
"1.1": "Child 1.1",
"1.1.1": "Child 1.1.1",
"1.1.2": "Child 1.1.2",
"1.2": "Child 1.2",
"2": "No Children Parent",
"1-1": "Child",
};
const ItemComponent = ({ item }: { item: EntityListTreeItem }) => {
return <div>{name[item.id as keyof typeof name] || item.id}</div>;
return <div>{item.name}</div>;
};
const defaultProps: EntityListTreeProps = {
@ -30,12 +20,14 @@ const defaultProps: EntityListTreeProps = {
isExpanded: false,
isSelected: false,
isDisabled: false,
name: "Parent 1",
children: [
{
id: "1-1",
isExpanded: false,
isSelected: false,
isDisabled: false,
name: "Child 1.1",
children: [],
},
],
@ -52,7 +44,7 @@ describe("EntityListTree", () => {
it("calls onItemExpand when expand icon is clicked", () => {
render(<EntityListTree {...defaultProps} />);
const expandIcon = screen.getByTestId("t--entity-item-expand-icon");
const expandIcon = screen.getByTestId("t--entity-collapse-toggle");
fireEvent.click(expandIcon);
expect(mockOnItemExpand).toHaveBeenCalledWith("1");
@ -67,13 +59,14 @@ describe("EntityListTree", () => {
isExpanded: false,
isSelected: false,
isDisabled: false,
name: "No Children Parent",
children: [],
},
],
};
render(<EntityListTree {...props} />);
const expandIcon = screen.queryByTestId("t--entity-item-expand-icon");
const expandIcon = screen.queryByTestId("t--entity-collapse-toggle");
expect(
screen.getByRole("treeitem", { name: "No Children Parent" }),
@ -90,12 +83,14 @@ describe("EntityListTree", () => {
isExpanded: true,
isSelected: false,
isDisabled: false,
name: "Parent 1",
children: [
{
id: "1-1",
isExpanded: false,
isSelected: false,
isDisabled: false,
name: "Child 1.1",
children: [],
},
],
@ -105,12 +100,14 @@ describe("EntityListTree", () => {
render(<EntityListTree {...props} />);
expect(screen.getByRole("treeitem", { name: "Child" })).toBeInTheDocument();
expect(
screen.getByRole("treeitem", { name: "Child 1.1" }),
).toBeInTheDocument();
});
it("does not render nested EntityListTree when item is not expanded", () => {
render(<EntityListTree {...defaultProps} />);
expect(screen.queryByRole("treeitem", { name: "Child" })).toBeNull();
expect(screen.queryByRole("treeitem", { name: "Child 1.1" })).toBeNull();
});
});

View File

@ -49,8 +49,11 @@ export function EntityListTree(props: EntityListTreeProps) {
>
{item.children && item.children.length ? (
<CollapseWrapper
data-icon={
item.isExpanded ? "arrow-down-s-line" : "arrow-right-s-line"
}
data-itemid={item.id}
data-testid="t--entity-item-expand-icon"
data-testid="t--entity-collapse-toggle"
onClick={handleOnExpandClick}
>
<Icon

View File

@ -4,6 +4,7 @@ export interface EntityListTreeItem {
isSelected: boolean;
isDisabled?: boolean;
id: string;
name: string;
}
export interface EntityListTreeProps {

View File

@ -19,6 +19,7 @@ export function useEditableText(
exitEditing: () => void,
validateName: (name: string) => string | null,
onNameSave: (name: string) => void,
normalizeName?: boolean,
): [
RefObject<HTMLInputElement>,
string,
@ -87,7 +88,9 @@ export function useEditableText(
const handleTitleChange = useEventCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const value = normaliseName(e.target.value);
const value = normalizeName
? normaliseName(e.target.value)
: e.target.value;
setEditableName(value);
validate(value);

View File

@ -8,7 +8,8 @@ import {
import { EntityClassNames } from "pages/Editor/Explorer/Entity";
interface Props {
children?: React.ReactNode[] | React.ReactNode;
children: React.ReactNode[] | React.ReactNode;
dataTestId?: string;
}
const EntityContextMenu = (props: Props) => {
@ -26,7 +27,7 @@ const EntityContextMenu = (props: Props) => {
>
<Button
className={EntityClassNames.CONTEXT_MENU}
data-testid="t--more-action-trigger"
data-testid={props.dataTestId}
isIconButton
kind="tertiary"
startIcon="more-2-fill"

View File

@ -31,7 +31,7 @@ export const Copy = ({ action, disabled }: Props) => {
return (
<MenuSub>
<MenuSubTrigger startIcon="duplicate">
<MenuSubTrigger disabled={disabled} startIcon="duplicate">
{createMessage(CONTEXT_COPY)}
</MenuSubTrigger>
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">

View File

@ -42,7 +42,7 @@ export const Move = ({ action, disabled }: Props) => {
return (
<MenuSub>
<MenuSubTrigger startIcon="swap-horizontal">
<MenuSubTrigger disabled={disabled} startIcon="swap-horizontal">
{createMessage(CONTEXT_MOVE)}
</MenuSubTrigger>
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">

View File

@ -20,6 +20,7 @@ import { jsCollectionIdURL } from "ee/RouteBuilder";
import { JsFileIconV2 } from "pages/Editor/Explorer/ExplorerIcons";
import { AppJSContextMenuItems } from "./AppJSContextMenuItems";
import type { EntityItem as EntityItemProps } from "ee/IDE/Interfaces/EntityItem";
import clsx from "clsx";
export const JSEntityItem = ({ item }: { item: EntityItemProps }) => {
const jsAction = useSelector((state: AppState) =>
@ -37,14 +38,17 @@ export const JSEntityItem = ({ item }: { item: EntityItemProps }) => {
entityName: item.title,
});
const dispatch = useDispatch();
const contextMenu = useMemo(
() => (
const contextMenu = useMemo(() => {
if (Boolean(jsAction.isMainJSCollection)) {
return null;
}
return (
<EntityContextMenu>
<AppJSContextMenuItems jsAction={jsAction} />
</EntityContextMenu>
),
[jsAction],
);
);
}, [jsAction]);
const jsActionPermissions = jsAction.userPermissions || [];
@ -55,6 +59,11 @@ export const JSEntityItem = ({ item }: { item: EntityItemProps }) => {
jsActionPermissions,
);
const canEdit = useMemo(
() => canManageJSAction && !Boolean(jsAction?.isMainJSCollection),
[canManageJSAction, jsAction?.isMainJSCollection],
);
const navigateToUrl = jsCollectionIdURL({
baseParentEntityId: parentEntityId,
baseCollectionId: jsAction.baseId,
@ -73,11 +82,11 @@ export const JSEntityItem = ({ item }: { item: EntityItemProps }) => {
invokedBy: NavigationMethod.EntityExplorer,
});
}
}, [parentEntityId, jsAction.baseId, jsAction.name, location.pathname]);
}, [jsAction.baseId, jsAction.name, location.pathname, navigateToUrl]);
const nameEditorConfig = useMemo(() => {
return {
canEdit: canManageJSAction && !Boolean(jsAction.isMainJSCollection),
canEdit,
isEditing: editingEntity === jsAction.id,
isLoading: updatingEntity === jsAction.id,
onEditComplete: exitEditMode,
@ -86,23 +95,23 @@ export const JSEntityItem = ({ item }: { item: EntityItemProps }) => {
validateName: (newName: string) => validateName(newName),
};
}, [
canManageJSAction,
canEdit,
editingEntity,
exitEditMode,
ideType,
item.title,
jsAction.id,
jsAction.isMainJSCollection,
dispatch,
updatingEntity,
exitEditMode,
dispatch,
ideType,
validateName,
]);
return (
<EntityItem
className="t--jsaction"
className={clsx("t--jsaction", {
editable: canEdit,
})}
id={jsAction.id}
isSelected={activeActionBaseId === jsAction.id}
isSelected={activeActionBaseId === item.key}
key={jsAction.id}
nameEditorConfig={nameEditorConfig}
onClick={navigateToJSCollection}

View File

@ -50,14 +50,14 @@ export const QueryEntityItem = ({ item }: { item: EntityItemProps }) => {
const dispatch = useDispatch();
const contextMenu = useMemo(
() => (
<EntityContextMenu>
<EntityContextMenu dataTestId="t--entity-context-menu-trigger">
<AppQueryContextMenuItems action={action} />
</EntityContextMenu>
),
[action],
);
const actionPermissions = action.userPermissions || [];
const actionPermissions = action?.userPermissions || [];
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
@ -74,6 +74,8 @@ export const QueryEntityItem = ({ item }: { item: EntityItemProps }) => {
pluginGroups[action.pluginId],
);
const icon = config?.getIcon(action, pluginGroups[action.pluginId]);
const switchToAction = useCallback(() => {
url && history.push(url, { invokedBy: NavigationMethod.EntityExplorer });
AnalyticsUtil.logEvent("ENTITY_EXPLORER_CLICK", {
@ -104,25 +106,26 @@ export const QueryEntityItem = ({ item }: { item: EntityItemProps }) => {
}, [
canManageAction,
editingEntity,
exitEditMode,
ideType,
item.title,
action.id,
updatingEntity,
exitEditMode,
dispatch,
ideType,
validateName,
]);
return (
<EntityItem
className="action t--action-entity"
id={action.id}
isSelected={activeActionBaseId === action.id}
isSelected={activeActionBaseId === item.key}
key={action.id}
nameEditorConfig={nameEditorConfig}
onClick={switchToAction}
onDoubleClick={() => enterEditMode(action.id)}
rightControl={contextMenu}
rightControlVisibility="hover"
startIcon={item.icon}
startIcon={icon}
title={item.title}
/>
);

View File

@ -15,6 +15,7 @@ export const UIEntityListTree = () => {
const items = enhanceItemsTree(widgets?.children || [], (widget) => ({
id: widget.widgetId,
name: widget.widgetName,
isSelected: selectedWidgets.includes(widget.widgetId),
isExpanded: expandedWidgets.includes(widget.widgetId),
}));

View File

@ -22,6 +22,8 @@ import {
} from "ee/constants/messages";
import { useDeleteWidget } from "./hooks/useDeleteWidget";
import { InspectStateMenuItem } from "components/editorComponents/Debugger/StateInspector/CTAs";
import { EntityClassNames } from "pages/Editor/Explorer/Entity";
import clsx from "clsx";
export const WidgetContextMenu = (props: {
widgetId: string;
@ -86,6 +88,7 @@ export const WidgetContextMenu = (props: {
editWidgetName,
showBinding,
widget?.isDeletable,
widgetId,
]);
return (
@ -98,7 +101,16 @@ export const WidgetContextMenu = (props: {
startIcon="more-2-fill"
/>
</MenuTrigger>
<MenuContent align="start" key={widgetId} side="right" width="300px">
<MenuContent
align="start"
className={clsx(
"t--entity-context-menu",
EntityClassNames.CONTEXT_MENU_CONTENT,
)}
key={widgetId}
side="right"
width="300px"
>
{menuContent}
</MenuContent>
</Menu>

View File

@ -12,9 +12,10 @@ import { getPagePermissions } from "selectors/editorSelectors";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { useSwitchToWidget } from "./hooks/useSwitchToWidget";
import { WidgetTypeIcon } from "./WidgetTypeIcon";
import type { WidgetProps } from "widgets/BaseWidget";
export const WidgetTreeItem = ({ item }: { item: EntityListTreeItem }) => {
const widget = useSelector(getWidgetByID(item.id));
const widget: WidgetProps | undefined = useSelector(getWidgetByID(item.id));
const switchToWidget = useSwitchToWidget();
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
@ -36,15 +37,15 @@ export const WidgetTreeItem = ({ item }: { item: EntityListTreeItem }) => {
useNameEditorState();
const validateName = useValidateEntityName({
entityName: widget.widgetName,
entityId: widget.widgetId,
entityName: item.name,
entityId: item.id,
});
const isLoading = updatingEntity === widget.widgetId;
const isEditing = editingEntity === widget.widgetId;
const isLoading = updatingEntity === item.id;
const isEditing = editingEntity === item.id;
const onNameSave = useCallback(
(newName: string) => handleNameSave(widget.widgetId, newName),
[handleNameSave, widget.widgetId],
(newName: string) => handleNameSave(item.id, newName),
[handleNameSave, item.id],
);
const nameEditorConfig = useMemo(
@ -67,8 +68,8 @@ export const WidgetTreeItem = ({ item }: { item: EntityListTreeItem }) => {
);
const startIcon = useMemo(
() => <WidgetTypeIcon type={widget.type} />,
[widget.type],
() => <WidgetTypeIcon type={widget?.type} />,
[widget?.type],
);
const onClick = useCallback(
@ -77,20 +78,19 @@ export const WidgetTreeItem = ({ item }: { item: EntityListTreeItem }) => {
);
const onDoubleClick = useCallback(
() => enterEditMode(widget.widgetId),
[enterEditMode, widget.widgetId],
() => enterEditMode(item.id),
[enterEditMode, item.id],
);
const rightControl = useMemo(
() => (
<WidgetContextMenu
canManagePages={canManagePages}
widgetId={widget.widgetId}
/>
<WidgetContextMenu canManagePages={canManagePages} widgetId={item.id} />
),
[canManagePages, widget.widgetId],
[canManagePages, item.id],
);
if (!widget) return null;
return (
<EntityItem
id={item.id}
@ -101,7 +101,7 @@ export const WidgetTreeItem = ({ item }: { item: EntityListTreeItem }) => {
rightControl={rightControl}
rightControlVisibility="hover"
startIcon={startIcon}
title={widget.widgetName}
title={item.name}
/>
);
};

View File

@ -45,7 +45,7 @@ const Wrapper = styled.div`
}
`;
const EditableWrapper = styled.div`
const EditableWrapper = styled.span`
overflow: hidden;
margin: 0 ${(props) => props.theme.spaces[1]}px;
padding: ${(props) => props.theme.spaces[3] + 1}px 0;

View File

@ -28,8 +28,12 @@ export function AppJSEditorContextMenu({
jsCollection?.userPermissions || [],
);
if (Boolean(jsCollection?.isMainJSCollection)) {
return null;
}
return (
<EntityContextMenu>
<EntityContextMenu dataTestId="t--more-action-trigger">
<RenameMenuItem
disabled={!isChangePermitted}
entityId={jsCollection.id}

View File

@ -31,7 +31,7 @@ export const Copy = ({ disabled, jsAction }: Props) => {
return (
<MenuSub>
<MenuSubTrigger startIcon="duplicate">
<MenuSubTrigger disabled={disabled} startIcon="duplicate">
{createMessage(CONTEXT_COPY)}
</MenuSubTrigger>
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">

View File

@ -41,7 +41,7 @@ export const Move = ({ disabled, jsAction }: Props) => {
return (
<MenuSub>
<MenuSubTrigger startIcon="swap-horizontal">
<MenuSubTrigger disabled={disabled} startIcon="swap-horizontal">
{createMessage(CONTEXT_MOVE)}
</MenuSubTrigger>
<MenuSubContent style={{ maxHeight: "350px" }} width="220px">