1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/menu/menu-actions.tsx

181 lines
5.4 KiB
TypeScript

/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./menu-actions.scss";
import React, { isValidElement } from "react";
import { observable, makeObservable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { autoBind, cssNames } from "../../utils";
import type { IconProps } from "../icon";
import { Icon } from "../icon";
import type { MenuProps } from "./menu";
import { Menu, MenuItem } from "./menu";
import isString from "lodash/isString";
import type { TooltipDecoratorProps } from "../tooltip";
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
import getRandomIdInjectable from "../../../common/utils/get-random-id.injectable";
export interface MenuActionsProps extends Partial<MenuProps> {
className?: string;
toolbar?: boolean; // display menu as toolbar with icons
autoCloseOnSelect?: boolean;
triggerIcon?: string | (IconProps & TooltipDecoratorProps) | React.ReactNode;
/**
* @deprecated Provide your own remove `<MenuItem>` as part of the `children` passed to this component
*/
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
/**
* @deprecated Provide your own update `<MenuItem>` as part of the `children` passed to this component
*/
updateAction?: () => void | Promise<void>;
/**
* @deprecated Provide your own remove `<MenuItem>` as part of the `children` passed to this component
*/
removeAction?: () => void | Promise<void>;
onOpen?: () => void;
id?: string;
}
interface Dependencies {
openConfirmDialog: OpenConfirmDialog;
}
@observer
class NonInjectedMenuActions extends React.Component<MenuActionsProps & Dependencies> {
static defaultProps = {
removeConfirmationMessage: "Remove item?",
};
@observable isOpen = !!this.props.toolbar;
toggle = () => {
if (this.props.toolbar) return;
this.isOpen = !this.isOpen;
};
constructor(props: MenuActionsProps & Dependencies) {
super(props);
makeObservable(this);
autoBind(this);
}
componentDidMount(): void {
disposeOnUnmount(this, [
reaction(() => this.isOpen, (isOpen) => {
if (isOpen) {
this.props.onOpen?.();
}
}, {
fireImmediately: true,
}),
]);
}
remove() {
const { removeAction, openConfirmDialog } = this.props;
let { removeConfirmationMessage } = this.props;
if (typeof removeConfirmationMessage === "function") {
removeConfirmationMessage = removeConfirmationMessage();
}
openConfirmDialog({
ok: removeAction,
labelOk: "Remove",
message: <div>{removeConfirmationMessage}</div>,
});
}
renderTriggerIcon() {
if (this.props.toolbar) return null;
const { triggerIcon = "more_vert" } = this.props;
let className: string;
if (isValidElement<HTMLElement>(triggerIcon)) {
className = cssNames(triggerIcon.props.className, { active: this.isOpen });
return React.cloneElement(triggerIcon, { id: this.props.id, className });
}
const iconProps: IconProps & TooltipDecoratorProps = {
id: this.props.id,
interactive: true,
material: isString(triggerIcon) ? triggerIcon : undefined,
active: this.isOpen,
...(typeof triggerIcon === "object" ? triggerIcon : {}),
};
if (iconProps.tooltip && this.isOpen) {
delete iconProps.tooltip; // don't show tooltip for icon when menu is open
}
return (
<Icon {...iconProps}/>
);
}
render() {
const {
className, toolbar, autoCloseOnSelect, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage,
...menuProps
} = this.props;
const autoClose = !toolbar;
return (
<>
{this.renderTriggerIcon()}
<Menu
htmlFor={this.props.id}
isOpen={this.isOpen}
open={this.toggle}
close={this.toggle}
className={cssNames("MenuActions flex", className, {
toolbar,
gaps: toolbar, // add spacing for .flex
})}
animated={!toolbar}
usePortal={autoClose}
closeOnScroll={autoClose}
closeOnClickItem={autoCloseOnSelect ?? autoClose}
closeOnClickOutside={autoClose}
{...menuProps}
>
{children}
{updateAction && (
<MenuItem onClick={updateAction}>
<Icon
material="edit"
interactive={toolbar}
tooltip="Edit"
/>
<span className="title">Edit</span>
</MenuItem>
)}
{removeAction && (
<MenuItem onClick={this.remove} data-testid="menu-action-remove">
<Icon
material="delete"
interactive={toolbar}
tooltip="Delete"
/>
<span className="title">Delete</span>
</MenuItem>
)}
</Menu>
</>
);
}
}
export const MenuActions = withInjectables<Dependencies, MenuActionsProps>(NonInjectedMenuActions, {
getProps: (di, props) => ({
id: di.inject(getRandomIdInjectable)(),
openConfirmDialog: di.inject(openConfirmDialogInjectable),
...props,
}),
});