Shared:Components:ContextMenuButton: rewrite to typescript
This commit is contained in:
parent
32902c49f7
commit
8fa274a9f6
@ -1,72 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
// @ts-expect-error TS(2307): Cannot find module 'PUBLIC_DIR/images/vertical-dot... Remove this comment to see the full error message
|
||||
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
|
||||
import ContextMenuButton from "./";
|
||||
|
||||
export default {
|
||||
title: "Components/ContextMenuButton",
|
||||
component: ContextMenuButton,
|
||||
argTypes: {
|
||||
clickColor: { control: "color" },
|
||||
color: { control: "color" },
|
||||
getData: { required: true },
|
||||
hoverColor: { control: "color" },
|
||||
onClickLabel: { action: "onClickLabel", table: { disable: true } },
|
||||
onMouseLeave: { action: "onMouseLeave" },
|
||||
onMouseEnter: { action: "onMouseEnter" },
|
||||
onMouseOver: { action: "onMouseOver" },
|
||||
onMouseOut: { action: "onMouseOut" },
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `ContextMenuButton is used for displaying context menu actions on a list's item`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args: any) => {
|
||||
const [isOpen, setIsOpen] = useState(args.opened);
|
||||
const getData = () => {
|
||||
return [
|
||||
{
|
||||
key: "key1",
|
||||
label: "label1",
|
||||
onClick: () => args.onClickLabel("label1"),
|
||||
},
|
||||
{
|
||||
key: "key2",
|
||||
label: "label2",
|
||||
onClick: () => args.onClickLabel("label2"),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const onClickHandler = () => {
|
||||
setIsOpen(!isOpen);
|
||||
args.onClickLabel();
|
||||
};
|
||||
return (
|
||||
<div style={{ height: "100px" }}>
|
||||
<ContextMenuButton
|
||||
{...args}
|
||||
opened={isOpen}
|
||||
getData={getData}
|
||||
isDisabled={false}
|
||||
onClick={onClickHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
// @ts-expect-error TS(2339): Property 'args' does not exist on type '(args: any... Remove this comment to see the full error message
|
||||
Default.args = {
|
||||
title: "Actions",
|
||||
displayType: "dropdown",
|
||||
iconName: VerticalDotsReactSvgUrl,
|
||||
size: 16,
|
||||
directionX: "left",
|
||||
isDisabled: false,
|
||||
};
|
@ -1,225 +0,0 @@
|
||||
import React from "react";
|
||||
// @ts-expect-error TS(7016): Could not find a declaration file for module 'enzy... Remove this comment to see the full error message
|
||||
import { mount, shallow } from "enzyme";
|
||||
import ContextMenuButton from ".";
|
||||
// @ts-expect-error TS(2307): Cannot find module 'PUBLIC_DIR/images/vertical-dot... Remove this comment to see the full error message
|
||||
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
|
||||
|
||||
const baseData = () => [
|
||||
{
|
||||
key: "key",
|
||||
label: "label",
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
onClick: () => jest.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
const baseProps = {
|
||||
title: "Actions",
|
||||
iconName: VerticalDotsReactSvgUrl,
|
||||
size: 16,
|
||||
color: "#A3A9AE",
|
||||
getData: baseData,
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'describe'. Do you need to instal... Remove this comment to see the full error message
|
||||
describe("<ContextMenuButton />", () => {
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("renders without error", () => {
|
||||
const wrapper = mount(<ContextMenuButton {...baseProps} />);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("render with full custom props", () => {
|
||||
const wrapper = mount(
|
||||
<ContextMenuButton
|
||||
// @ts-expect-error TS(2322): Type '{ color: string; hoverColor: string; clickCo... Remove this comment to see the full error message
|
||||
color="red"
|
||||
hoverColor="red"
|
||||
clickColor="red"
|
||||
size={20}
|
||||
iconName="CatalogFolderIcon"
|
||||
iconHoverName="CatalogFolderIcon"
|
||||
iconClickName="CatalogFolderIcon"
|
||||
isFill={true}
|
||||
isDisabled={true}
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
onClick={() => jest.fn()}
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
onMouseEnter={() => jest.fn()}
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
onMouseLeave={() => jest.fn()}
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
onMouseOver={() => jest.fn()}
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
onMouseOut={() => jest.fn()}
|
||||
getData={() => [
|
||||
{
|
||||
key: "key",
|
||||
icon: "CatalogFolderIcon",
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
onClick: () => jest.fn(),
|
||||
},
|
||||
{
|
||||
label: "CatalogFolderIcon",
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
onClick: () => jest.fn(),
|
||||
},
|
||||
{},
|
||||
]}
|
||||
directionX="right"
|
||||
opened={true}
|
||||
/>
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("disabled", () => {
|
||||
const wrapper = mount(
|
||||
// @ts-expect-error TS(2322): Type '{ isDisabled: boolean; title: string; iconNa... Remove this comment to see the full error message
|
||||
<ContextMenuButton {...baseProps} isDisabled={true} />
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.prop("isDisabled")).toEqual(true);
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("not re-render", () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(
|
||||
wrapper.props,
|
||||
wrapper.state
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("re-render", () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(
|
||||
{ opened: true },
|
||||
wrapper.state
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("causes function onDropDownItemClick()", () => {
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
const onClick = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
// @ts-expect-error TS(2322): Type '{ opened: boolean; onClick: any; title: stri... Remove this comment to see the full error message
|
||||
<ContextMenuButton {...baseProps} opened={true} onClick={onClick} />
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.onDropDownItemClick({
|
||||
key: "key",
|
||||
label: "label",
|
||||
onClick: onClick,
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.state("isOpen")).toBe(false);
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("causes function onIconButtonClick()", () => {
|
||||
const wrapper = shallow(
|
||||
// @ts-expect-error TS(2322): Type '{ isDisabled: boolean; opened: boolean; titl... Remove this comment to see the full error message
|
||||
<ContextMenuButton {...baseProps} isDisabled={false} opened={true} />
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.onIconButtonClick();
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.state("isOpen")).toBe(false);
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("causes function onIconButtonClick() with isDisabled prop", () => {
|
||||
const wrapper = shallow(
|
||||
// @ts-expect-error TS(2322): Type '{ isDisabled: boolean; opened: boolean; titl... Remove this comment to see the full error message
|
||||
<ContextMenuButton {...baseProps} isDisabled={true} opened={true} />
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.onIconButtonClick();
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.state("isOpen")).toBe(true);
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("componentDidUpdate() state lifecycle test", () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
wrapper.setState({ isOpen: false });
|
||||
|
||||
instance.componentDidUpdate(wrapper.props(), wrapper.state());
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.state()).toBe(wrapper.state());
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("componentDidUpdate() props lifecycle test", () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentDidUpdate({ opened: true }, wrapper.state());
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.props()).toBe(wrapper.props());
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("accepts id", () => {
|
||||
// @ts-expect-error TS(2322): Type '{ id: string; title: string; iconName: any; ... Remove this comment to see the full error message
|
||||
const wrapper = mount(<ContextMenuButton {...baseProps} id="testId" />);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.prop("id")).toEqual("testId");
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("accepts className", () => {
|
||||
const wrapper = mount(
|
||||
// @ts-expect-error TS(2322): Type '{ className: string; title: string; iconName... Remove this comment to see the full error message
|
||||
<ContextMenuButton {...baseProps} className="test" />
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.prop("className")).toEqual("test");
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("accepts style", () => {
|
||||
const wrapper = mount(
|
||||
// @ts-expect-error TS(2322): Type '{ style: { color: string; }; title: string; ... Remove this comment to see the full error message
|
||||
<ContextMenuButton {...baseProps} style={{ color: "red" }} />
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
|
||||
});
|
||||
});
|
@ -1,427 +0,0 @@
|
||||
import React from "react";
|
||||
import throttle from "lodash/throttle";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import DropDownItem from "../drop-down-item";
|
||||
import DropDown from "../drop-down";
|
||||
import IconButton from "../icon-button";
|
||||
import Backdrop from "../backdrop";
|
||||
import Aside from "../aside";
|
||||
import Heading from "../heading";
|
||||
import Link from "../link";
|
||||
import { desktop, isTablet, isMobile } from "../utils/device";
|
||||
import { isTablet as Tablet } from "react-device-detect";
|
||||
|
||||
import {
|
||||
StyledBodyContent,
|
||||
StyledHeaderContent,
|
||||
StyledContent,
|
||||
StyledOuter,
|
||||
} from "./styled-context-menu-button";
|
||||
|
||||
// @ts-expect-error TS(2307): Cannot find module 'PUBLIC_DIR/images/vertical-dot... Remove this comment to see the full error message
|
||||
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
|
||||
|
||||
class ContextMenuButton extends React.Component {
|
||||
ref: any;
|
||||
throttledResize: any;
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.ref = React.createRef();
|
||||
const displayType =
|
||||
props.displayType === "auto" ? this.getTypeByWidth() : props.displayType;
|
||||
|
||||
this.state = {
|
||||
isOpen: props.opened,
|
||||
data: props.data,
|
||||
displayType,
|
||||
};
|
||||
this.throttledResize = throttle(this.resize, 300);
|
||||
}
|
||||
|
||||
getTypeByWidth = () => {
|
||||
// @ts-expect-error TS(2339): Property 'displayType' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
if (this.props.displayType !== "auto") return this.props.displayType;
|
||||
// @ts-expect-error TS(2365): Operator '<' cannot be applied to types 'number' a... Remove this comment to see the full error message
|
||||
return window.innerWidth < desktop.match(/\d+/)[0] ? "aside" : "dropdown";
|
||||
};
|
||||
|
||||
resize = () => {
|
||||
// @ts-expect-error TS(2339): Property 'displayType' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
if (this.props.displayType !== "auto") return;
|
||||
const type = this.getTypeByWidth();
|
||||
// @ts-expect-error TS(2339): Property 'displayType' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
if (type === this.state.displayType) return;
|
||||
this.setState({ displayType: type });
|
||||
};
|
||||
|
||||
popstate = () => {
|
||||
window.removeEventListener("popstate", this.popstate, false);
|
||||
this.onClose();
|
||||
window.history.go(1);
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("resize", this.throttledResize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("resize", this.throttledResize);
|
||||
window.removeEventListener("popstate", this.popstate, false);
|
||||
this.throttledResize.cancel();
|
||||
}
|
||||
|
||||
stopAction = (e: any) => e.preventDefault();
|
||||
toggle = (isOpen: any) => this.setState({ isOpen: isOpen });
|
||||
onClose = () => {
|
||||
// @ts-expect-error TS(2339): Property 'isOpen' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
this.setState({ isOpen: !this.state.isOpen });
|
||||
// @ts-expect-error TS(2339): Property 'onClose' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
this.props.onClose && this.props.onClose();
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: any) {
|
||||
// @ts-expect-error TS(2339): Property 'opened' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
if (this.props.opened !== prevProps.opened) {
|
||||
// @ts-expect-error TS(2339): Property 'opened' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
this.toggle(this.props.opened);
|
||||
}
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'opened' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
if (this.props.opened && this.state.displayType === "aside") {
|
||||
window.addEventListener("popstate", this.popstate, false);
|
||||
}
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'displayType' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
if (this.props.displayType !== prevProps.displayType) {
|
||||
this.setState({ displayType: this.getTypeByWidth() });
|
||||
}
|
||||
}
|
||||
|
||||
onIconButtonClick = (e: any) => {
|
||||
// @ts-expect-error TS(2339): Property 'isDisabled' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
if (this.props.isDisabled || this.state.displayType === "toggle") {
|
||||
this.stopAction;
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
// @ts-expect-error TS(2339): Property 'getData' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
data: this.props.getData(),
|
||||
// @ts-expect-error TS(2339): Property 'isOpen' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
isOpen: !this.state.isOpen,
|
||||
},
|
||||
() =>
|
||||
// @ts-expect-error TS(2339): Property 'isDisabled' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
!this.props.isDisabled &&
|
||||
// @ts-expect-error TS(2339): Property 'isOpen' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
this.state.isOpen &&
|
||||
// @ts-expect-error TS(2339): Property 'onClick' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
this.props.onClick &&
|
||||
// @ts-expect-error TS(2339): Property 'onClick' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
this.props.onClick(e)
|
||||
); // eslint-disable-line react/prop-types
|
||||
};
|
||||
|
||||
clickOutsideAction = (e: any) => {
|
||||
const path = e.path || (e.composedPath && e.composedPath());
|
||||
const dropDownItem = path ? path.find((x: any) => x === this.ref.current) : null;
|
||||
if (dropDownItem) return;
|
||||
|
||||
this.onClose();
|
||||
};
|
||||
|
||||
onDropDownItemClick = (item: any, e: any) => {
|
||||
// @ts-expect-error TS(2339): Property 'displayType' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
const open = this.state.displayType === "dropdown";
|
||||
item.onClick && item.onClick(e, open, item);
|
||||
// @ts-expect-error TS(2339): Property 'isOpen' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
this.toggle(!this.state.isOpen);
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps: any, nextState: any) {
|
||||
if (
|
||||
// @ts-expect-error TS(2339): Property 'opened' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
this.props.opened === nextProps.opened &&
|
||||
// @ts-expect-error TS(2339): Property 'isOpen' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
this.state.isOpen === nextState.isOpen &&
|
||||
// @ts-expect-error TS(2339): Property 'displayType' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
this.props.displayType === nextProps.displayType &&
|
||||
// @ts-expect-error TS(2339): Property 'isDisabled' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
this.props.isDisabled === nextProps.isDisabled
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
callNewMenu = (e: any) => {
|
||||
// @ts-expect-error TS(2339): Property 'isDisabled' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
if (this.props.isDisabled || this.state.displayType !== "toggle") {
|
||||
this.stopAction;
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
// @ts-expect-error TS(2339): Property 'getData' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
data: this.props.getData(),
|
||||
},
|
||||
// @ts-expect-error TS(2339): Property 'onClick' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
() => this.props.onClick(e)
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
//console.log("ContextMenuButton render", this.props);
|
||||
const {
|
||||
// @ts-expect-error TS(2339): Property 'className' does not exist on type 'Reado... Remove this comment to see the full error message
|
||||
className,
|
||||
// @ts-expect-error TS(2339): Property 'clickColor' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
clickColor,
|
||||
// @ts-expect-error TS(2339): Property 'color' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
color,
|
||||
// @ts-expect-error TS(2339): Property 'columnCount' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
columnCount,
|
||||
// @ts-expect-error TS(2339): Property 'directionX' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
directionX,
|
||||
// @ts-expect-error TS(2339): Property 'directionY' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
directionY,
|
||||
// @ts-expect-error TS(2339): Property 'hoverColor' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
hoverColor,
|
||||
// @ts-expect-error TS(2339): Property 'iconClickName' does not exist on type 'R... Remove this comment to see the full error message
|
||||
iconClickName,
|
||||
// @ts-expect-error TS(2339): Property 'iconHoverName' does not exist on type 'R... Remove this comment to see the full error message
|
||||
iconHoverName,
|
||||
// @ts-expect-error TS(2339): Property 'iconName' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
iconName,
|
||||
// @ts-expect-error TS(2339): Property 'iconOpenName' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
iconOpenName,
|
||||
// @ts-expect-error TS(2339): Property 'id' does not exist on type 'Readonly<{}>... Remove this comment to see the full error message
|
||||
id,
|
||||
// @ts-expect-error TS(2339): Property 'isDisabled' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
isDisabled,
|
||||
// @ts-expect-error TS(2339): Property 'onMouseEnter' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
onMouseEnter,
|
||||
// @ts-expect-error TS(2339): Property 'onMouseLeave' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
onMouseLeave,
|
||||
// @ts-expect-error TS(2339): Property 'onMouseOut' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
onMouseOut,
|
||||
// @ts-expect-error TS(2339): Property 'onMouseOver' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
onMouseOver,
|
||||
// @ts-expect-error TS(2339): Property 'size' does not exist on type 'Readonly<{... Remove this comment to see the full error message
|
||||
size,
|
||||
// @ts-expect-error TS(2339): Property 'style' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
style,
|
||||
// @ts-expect-error TS(2339): Property 'isFill' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
isFill, // eslint-disable-line react/prop-types
|
||||
// @ts-expect-error TS(2339): Property 'asideHeader' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
asideHeader, // eslint-disable-line react/prop-types
|
||||
// @ts-expect-error TS(2339): Property 'title' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
title,
|
||||
// @ts-expect-error TS(2339): Property 'zIndex' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
zIndex,
|
||||
// @ts-expect-error TS(2339): Property 'usePortal' does not exist on type 'Reado... Remove this comment to see the full error message
|
||||
usePortal,
|
||||
// @ts-expect-error TS(2339): Property 'dropDownClassName' does not exist on typ... Remove this comment to see the full error message
|
||||
dropDownClassName,
|
||||
// @ts-expect-error TS(2339): Property 'iconClassName' does not exist on type 'R... Remove this comment to see the full error message
|
||||
iconClassName,
|
||||
// @ts-expect-error TS(2339): Property 'displayIconBorder' does not exist on typ... Remove this comment to see the full error message
|
||||
displayIconBorder,
|
||||
} = this.props;
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'isOpen' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
const { isOpen, displayType, offsetX, offsetY } = this.state;
|
||||
const iconButtonName = isOpen && iconOpenName ? iconOpenName : iconName;
|
||||
return (
|
||||
<StyledOuter
|
||||
ref={this.ref}
|
||||
className={className}
|
||||
id={id}
|
||||
style={style}
|
||||
onClick={this.callNewMenu}
|
||||
// @ts-expect-error TS(2769): No overload matches this call.
|
||||
displayIconBorder={displayIconBorder}
|
||||
>
|
||||
<IconButton
|
||||
// @ts-expect-error TS(2322): Type '{ className: any; color: any; hoverColor: an... Remove this comment to see the full error message
|
||||
className={iconClassName}
|
||||
color={color}
|
||||
hoverColor={hoverColor}
|
||||
clickColor={clickColor}
|
||||
size={size}
|
||||
iconName={iconButtonName}
|
||||
iconHoverName={iconHoverName}
|
||||
iconClickName={iconClickName}
|
||||
isFill={isFill}
|
||||
isDisabled={isDisabled}
|
||||
onClick={this.onIconButtonClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseOver={onMouseOver}
|
||||
onMouseOut={onMouseOut}
|
||||
title={title}
|
||||
/>
|
||||
{displayType === "dropdown" ? (
|
||||
// @ts-expect-error TS(2769): No overload matches this call.
|
||||
<DropDown
|
||||
className={dropDownClassName}
|
||||
directionX={directionX}
|
||||
directionY={directionY}
|
||||
open={isOpen}
|
||||
forwardedRef={this.ref}
|
||||
clickOutsideAction={this.clickOutsideAction}
|
||||
columnCount={columnCount}
|
||||
withBackdrop={isTablet() || isMobile() || Tablet}
|
||||
zIndex={zIndex}
|
||||
isDefaultMode={usePortal}
|
||||
>
|
||||
// @ts-expect-error TS(2339): Property 'data' does not exist on type 'Readonly<{... Remove this comment to see the full error message
|
||||
{this.state.data?.map(
|
||||
(item: any, index: any) =>
|
||||
item &&
|
||||
(item.label || item.icon || item.key) && (
|
||||
<DropDownItem
|
||||
{...item}
|
||||
id={item.id}
|
||||
key={item.key || index}
|
||||
onClick={this.onDropDownItemClick.bind(this, item)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</DropDown>
|
||||
) : (
|
||||
displayType === "aside" && (
|
||||
<>
|
||||
<Backdrop
|
||||
// @ts-expect-error TS(2322): Type '{ onClick: () => void; visible: any; zIndex:... Remove this comment to see the full error message
|
||||
onClick={this.onClose}
|
||||
visible={isOpen}
|
||||
zIndex={310}
|
||||
isAside={true}
|
||||
/>
|
||||
<Aside
|
||||
visible={isOpen}
|
||||
scale={false}
|
||||
zIndex={310}
|
||||
onClose={this.onClose}
|
||||
>
|
||||
<StyledContent>
|
||||
<StyledHeaderContent>
|
||||
// @ts-expect-error TS(2322): Type '{ children: any; className: string; size: st... Remove this comment to see the full error message
|
||||
<Heading className="header" size="medium" truncate={true}>
|
||||
{asideHeader}
|
||||
</Heading>
|
||||
</StyledHeaderContent>
|
||||
<StyledBodyContent>
|
||||
// @ts-expect-error TS(2339): Property 'data' does not exist on type 'Readonly<{... Remove this comment to see the full error message
|
||||
{this.state.data.map(
|
||||
(item: any, index: any) =>
|
||||
item &&
|
||||
(item.label || item.icon || item.key) && (
|
||||
<Link
|
||||
className={`context-menu-button_link${
|
||||
item.isHeader ? "-header" : ""
|
||||
}`}
|
||||
key={item.key || index}
|
||||
fontSize={item.isHeader ? "15px" : "13px"}
|
||||
noHover={item.isHeader}
|
||||
fontWeight={600}
|
||||
onClick={this.onDropDownItemClick.bind(this, item)}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</StyledBodyContent>
|
||||
</StyledContent>
|
||||
</Aside>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</StyledOuter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'propTypes' does not exist on type 'typeo... Remove this comment to see the full error message
|
||||
ContextMenuButton.propTypes = {
|
||||
/** Sets the button to present an opened state */
|
||||
opened: PropTypes.bool,
|
||||
/** Array of options for display */
|
||||
data: PropTypes.array,
|
||||
/** Function for converting to inner data */
|
||||
getData: PropTypes.func.isRequired,
|
||||
/** Specifies the icon title */
|
||||
title: PropTypes.string,
|
||||
/** Specifies the icon name */
|
||||
iconName: PropTypes.string,
|
||||
/** Specifies the icon size */
|
||||
size: PropTypes.number,
|
||||
/** Specifies the icon color */
|
||||
color: PropTypes.string,
|
||||
/** Sets the button to present a disabled state */
|
||||
isDisabled: PropTypes.bool,
|
||||
/** Specifies the icon hover color */
|
||||
hoverColor: PropTypes.string,
|
||||
/** Specifies the icon click color */
|
||||
clickColor: PropTypes.string,
|
||||
/** Specifies the icon hover name */
|
||||
iconHoverName: PropTypes.string,
|
||||
/** Specifies the icon click name */
|
||||
iconClickName: PropTypes.string,
|
||||
/** Specifies the icon open name */
|
||||
iconOpenName: PropTypes.string,
|
||||
/** Triggers a callback function when the mouse enters the button borders */
|
||||
onMouseEnter: PropTypes.func,
|
||||
/** Triggers a callback function when the mouse leaves the button borders */
|
||||
onMouseLeave: PropTypes.func,
|
||||
/** Triggers a callback function when the mouse moves over the button borders */
|
||||
onMouseOver: PropTypes.func,
|
||||
/** Triggers a callback function when the mouse moves out of the button borders */
|
||||
onMouseOut: PropTypes.func,
|
||||
/** Direction X */
|
||||
directionX: PropTypes.string,
|
||||
/** Direction Y */
|
||||
directionY: PropTypes.string,
|
||||
/** Accepts class */
|
||||
className: PropTypes.string,
|
||||
/** Accepts id */
|
||||
id: PropTypes.string,
|
||||
/** Accepts css style */
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
/** Sets the number of columns */
|
||||
columnCount: PropTypes.number,
|
||||
/** Sets the display type */
|
||||
displayType: PropTypes.oneOf(["dropdown", "toggle", "aside", "auto"]),
|
||||
/** Closing event */
|
||||
onClose: PropTypes.func,
|
||||
/** Sets the drop down open with the portal */
|
||||
usePortal: PropTypes.bool,
|
||||
/** Sets the class of the drop down element */
|
||||
dropDownClassName: PropTypes.string,
|
||||
/** Sets the class of the icon button */
|
||||
iconClassName: PropTypes.string,
|
||||
/** Enables displaying the icon borders */
|
||||
displayIconBorder: PropTypes.bool,
|
||||
};
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'defaultProps' does not exist on type 'ty... Remove this comment to see the full error message
|
||||
ContextMenuButton.defaultProps = {
|
||||
opened: false,
|
||||
data: [],
|
||||
title: "",
|
||||
iconName: VerticalDotsReactSvgUrl,
|
||||
size: 16,
|
||||
isDisabled: false,
|
||||
directionX: "left",
|
||||
isFill: false,
|
||||
displayType: "dropdown",
|
||||
usePortal: true,
|
||||
displayIconBorder: false,
|
||||
};
|
||||
|
||||
export default ContextMenuButton;
|
@ -0,0 +1,6 @@
|
||||
export const enum ContextMenuButtonDisplayType {
|
||||
dropdown = "dropdown",
|
||||
toggle = "toggle",
|
||||
aside = "aside",
|
||||
auto = "auto",
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import React, { useState } from "react";
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
|
||||
|
||||
import { ContextMenuButton } from "./ContextMenuButton";
|
||||
import { ContextMenuButtonProps } from "./ContextMenuButton.types";
|
||||
import { ContextMenuButtonDisplayType } from "./ContextMenuButton.enums";
|
||||
|
||||
const meta = {
|
||||
title: "Components/ContextMenuButton",
|
||||
component: ContextMenuButton,
|
||||
argTypes: {
|
||||
clickColor: { control: "color" },
|
||||
color: { control: "color" },
|
||||
getData: { required: true },
|
||||
hoverColor: { control: "color" },
|
||||
// onClickLabel: { action: "onClickLabel", table: { disable: true } },
|
||||
onMouseLeave: { action: "onMouseLeave" },
|
||||
onMouseEnter: { action: "onMouseEnter" },
|
||||
onMouseOver: { action: "onMouseOver" },
|
||||
onMouseOut: { action: "onMouseOut" },
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `ContextMenuButton is used for displaying context menu actions on a list's item`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof ContextMenuButton>;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export default meta;
|
||||
|
||||
const Template = (args: ContextMenuButtonProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const onClickHandler = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
return (
|
||||
<div style={{ height: "100px" }}>
|
||||
<ContextMenuButton
|
||||
{...args}
|
||||
opened={isOpen}
|
||||
isDisabled={false}
|
||||
onClick={onClickHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getData = () => {
|
||||
return [
|
||||
{
|
||||
key: "key1",
|
||||
label: "label1",
|
||||
},
|
||||
{
|
||||
key: "key2",
|
||||
label: "label2",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => <Template {...args} />,
|
||||
args: {
|
||||
title: "Actions",
|
||||
displayType: ContextMenuButtonDisplayType.dropdown,
|
||||
iconName: VerticalDotsReactSvgUrl,
|
||||
size: 16,
|
||||
directionX: "left",
|
||||
isDisabled: false,
|
||||
data: [],
|
||||
getData,
|
||||
},
|
||||
};
|
@ -1,17 +1,17 @@
|
||||
import styled, { css } from "styled-components";
|
||||
import Base from "../themes/base";
|
||||
|
||||
const StyledOuter = styled.div`
|
||||
import { Base } from "../../themes";
|
||||
|
||||
const StyledOuter = styled.div<{ displayIconBorder?: boolean }>`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
${(props) =>
|
||||
// @ts-expect-error TS(2339): Property 'displayIconBorder' does not exist on typ... Remove this comment to see the full error message
|
||||
props.displayIconBorder &&
|
||||
css`
|
||||
border: ${(props) => props.theme.comboBox.button.border};
|
||||
border: ${props.theme.comboBox.button.border};
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
@ -21,8 +21,7 @@ const StyledOuter = styled.div`
|
||||
padding: 6px 7px;
|
||||
}
|
||||
:hover {
|
||||
border-color: ${(props) =>
|
||||
props.theme.comboBox.button.hoverBorderColor};
|
||||
border-color: ${props.theme.comboBox.button.hoverBorderColor};
|
||||
}
|
||||
`}
|
||||
`;
|
@ -0,0 +1,216 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
|
||||
|
||||
import { ContextMenuButton } from "./ContextMenuButton";
|
||||
import { ContextMenuButtonDisplayType } from "./ContextMenuButton.enums";
|
||||
|
||||
const baseData = () => [
|
||||
{
|
||||
key: "key",
|
||||
label: "label",
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
const baseProps = {
|
||||
title: "Actions",
|
||||
iconName: VerticalDotsReactSvgUrl,
|
||||
size: 16,
|
||||
color: "#A3A9AE",
|
||||
getData: baseData,
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
describe("<ContextMenuButton />", () => {
|
||||
it("renders without error", () => {
|
||||
render(
|
||||
<ContextMenuButton
|
||||
displayType={ContextMenuButtonDisplayType.dropdown}
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("context-menu-button")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// it("render with full custom props", () => {
|
||||
// const wrapper = mount(
|
||||
// <ContextMenuButton
|
||||
// // @ts-expect-error TS(2322): Type '{ color: string; hoverColor: string; clickCo... Remove this comment to see the full error message
|
||||
// color="red"
|
||||
// hoverColor="red"
|
||||
// clickColor="red"
|
||||
// size={20}
|
||||
// iconName="CatalogFolderIcon"
|
||||
// iconHoverName="CatalogFolderIcon"
|
||||
// iconClickName="CatalogFolderIcon"
|
||||
// isFill={true}
|
||||
// isDisabled={true}
|
||||
// // @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
// onClick={() => jest.fn()}
|
||||
// // @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
// onMouseEnter={() => jest.fn()}
|
||||
// // @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
// onMouseLeave={() => jest.fn()}
|
||||
// // @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
// onMouseOver={() => jest.fn()}
|
||||
// // @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
// onMouseOut={() => jest.fn()}
|
||||
// getData={() => [
|
||||
// {
|
||||
// key: "key",
|
||||
// icon: "CatalogFolderIcon",
|
||||
// // @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
// onClick: () => jest.fn(),
|
||||
// },
|
||||
// {
|
||||
// label: "CatalogFolderIcon",
|
||||
// // @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
// onClick: () => jest.fn(),
|
||||
// },
|
||||
// {},
|
||||
// ]}
|
||||
// directionX="right"
|
||||
// opened={true}
|
||||
// />,
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper).toExist();
|
||||
// });
|
||||
|
||||
// it("disabled", () => {
|
||||
// const wrapper = mount(
|
||||
// // @ts-expect-error TS(2322): Type '{ isDisabled: boolean; title: string; iconNa... Remove this comment to see the full error message
|
||||
// <ContextMenuButton {...baseProps} isDisabled={true} />,
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.prop("isDisabled")).toEqual(true);
|
||||
// });
|
||||
|
||||
// it("not re-render", () => {
|
||||
// const wrapper = shallow(<ContextMenuButton {...baseProps} />).instance();
|
||||
|
||||
// const shouldUpdate = wrapper.shouldComponentUpdate(
|
||||
// wrapper.props,
|
||||
// wrapper.state,
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(shouldUpdate).toBe(false);
|
||||
// });
|
||||
|
||||
// it("re-render", () => {
|
||||
// const wrapper = shallow(<ContextMenuButton {...baseProps} />).instance();
|
||||
|
||||
// const shouldUpdate = wrapper.shouldComponentUpdate(
|
||||
// { opened: true },
|
||||
// wrapper.state,
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(shouldUpdate).toBe(true);
|
||||
// });
|
||||
|
||||
// it("causes function onDropDownItemClick()", () => {
|
||||
// // @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
// const onClick = jest.fn();
|
||||
|
||||
// const wrapper = shallow(
|
||||
// // @ts-expect-error TS(2322): Type '{ opened: boolean; onClick: any; title: stri... Remove this comment to see the full error message
|
||||
// <ContextMenuButton {...baseProps} opened={true} onClick={onClick} />,
|
||||
// );
|
||||
// const instance = wrapper.instance();
|
||||
|
||||
// instance.onDropDownItemClick({
|
||||
// key: "key",
|
||||
// label: "label",
|
||||
// onClick: onClick,
|
||||
// });
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.state("isOpen")).toBe(false);
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(onClick).toHaveBeenCalled();
|
||||
// });
|
||||
|
||||
// it("causes function onIconButtonClick()", () => {
|
||||
// const wrapper = shallow(
|
||||
// // @ts-expect-error TS(2322): Type '{ isDisabled: boolean; opened: boolean; titl... Remove this comment to see the full error message
|
||||
// <ContextMenuButton {...baseProps} isDisabled={false} opened={true} />,
|
||||
// );
|
||||
// const instance = wrapper.instance();
|
||||
|
||||
// instance.onIconButtonClick();
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.state("isOpen")).toBe(false);
|
||||
// });
|
||||
|
||||
// it("causes function onIconButtonClick() with isDisabled prop", () => {
|
||||
// const wrapper = shallow(
|
||||
// // @ts-expect-error TS(2322): Type '{ isDisabled: boolean; opened: boolean; titl... Remove this comment to see the full error message
|
||||
// <ContextMenuButton {...baseProps} isDisabled={true} opened={true} />,
|
||||
// );
|
||||
// const instance = wrapper.instance();
|
||||
|
||||
// instance.onIconButtonClick();
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.state("isOpen")).toBe(true);
|
||||
// });
|
||||
|
||||
// it("componentDidUpdate() state lifecycle test", () => {
|
||||
// const wrapper = shallow(<ContextMenuButton {...baseProps} />);
|
||||
// const instance = wrapper.instance();
|
||||
|
||||
// wrapper.setState({ isOpen: false });
|
||||
|
||||
// instance.componentDidUpdate(wrapper.props(), wrapper.state());
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.state()).toBe(wrapper.state());
|
||||
// });
|
||||
|
||||
// it("componentDidUpdate() props lifecycle test", () => {
|
||||
// const wrapper = shallow(<ContextMenuButton {...baseProps} />);
|
||||
// const instance = wrapper.instance();
|
||||
|
||||
// instance.componentDidUpdate({ opened: true }, wrapper.state());
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.props()).toBe(wrapper.props());
|
||||
// });
|
||||
|
||||
// it("accepts id", () => {
|
||||
// // @ts-expect-error TS(2322): Type '{ id: string; title: string; iconName: any; ... Remove this comment to see the full error message
|
||||
// const wrapper = mount(<ContextMenuButton {...baseProps} id="testId" />);
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.prop("id")).toEqual("testId");
|
||||
// });
|
||||
|
||||
// it("accepts className", () => {
|
||||
// const wrapper = mount(
|
||||
// // @ts-expect-error TS(2322): Type '{ className: string; title: string; iconName... Remove this comment to see the full error message
|
||||
// <ContextMenuButton {...baseProps} className="test" />,
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.prop("className")).toEqual("test");
|
||||
// });
|
||||
|
||||
// it("accepts style", () => {
|
||||
// const wrapper = mount(
|
||||
// // @ts-expect-error TS(2322): Type '{ style: { color: string; }; title: string; ... Remove this comment to see the full error message
|
||||
// <ContextMenuButton {...baseProps} style={{ color: "red" }} />,
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
|
||||
// });
|
||||
});
|
@ -0,0 +1,335 @@
|
||||
import React from "react";
|
||||
import { DebouncedFunc } from "lodash";
|
||||
import throttle from "lodash/throttle";
|
||||
import { isTablet as Tablet } from "react-device-detect";
|
||||
|
||||
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
|
||||
|
||||
import { desktop, isTablet, isMobile } from "../../utils";
|
||||
|
||||
import { DropDownItem } from "../drop-down-item";
|
||||
import { DropDown } from "../drop-down";
|
||||
import { IconButton } from "../icon-button";
|
||||
import { Backdrop } from "../backdrop";
|
||||
import { Aside } from "../aside";
|
||||
import { Heading, HeadingSize } from "../heading";
|
||||
import { Link } from "../link";
|
||||
import { ContextMenuModel } from "../context-menu";
|
||||
|
||||
import {
|
||||
StyledBodyContent,
|
||||
StyledHeaderContent,
|
||||
StyledContent,
|
||||
StyledOuter,
|
||||
} from "./ContextMenuButton.styled";
|
||||
import { ContextMenuButtonProps } from "./ContextMenuButton.types";
|
||||
import { ContextMenuButtonDisplayType } from "./ContextMenuButton.enums";
|
||||
|
||||
const ContextMenuButtonPure = ({
|
||||
opened,
|
||||
data,
|
||||
displayType,
|
||||
onClose,
|
||||
isDisabled,
|
||||
getData,
|
||||
onClick,
|
||||
className,
|
||||
iconOpenName,
|
||||
id,
|
||||
style,
|
||||
displayIconBorder,
|
||||
iconClassName,
|
||||
color,
|
||||
hoverColor,
|
||||
clickColor,
|
||||
size,
|
||||
iconHoverName,
|
||||
iconClickName,
|
||||
isFill,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onMouseOut,
|
||||
onMouseOver,
|
||||
title,
|
||||
dropDownClassName,
|
||||
directionX,
|
||||
directionY,
|
||||
columnCount,
|
||||
zIndex,
|
||||
usePortal,
|
||||
asideHeader,
|
||||
iconName = VerticalDotsReactSvgUrl,
|
||||
}: ContextMenuButtonProps) => {
|
||||
const ref = React.useRef<HTMLDivElement | null>(null);
|
||||
const throttledResize = React.useRef<null | DebouncedFunc<() => void>>(null);
|
||||
|
||||
const [state, setState] = React.useState({
|
||||
isOpen: opened,
|
||||
data,
|
||||
displayType: ContextMenuButtonDisplayType.dropdown,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
});
|
||||
|
||||
const getTypeByWidth = React.useCallback(() => {
|
||||
if (displayType !== "auto") return displayType;
|
||||
const desktopSize = desktop.match(/\d+/)?.[0] as number | undefined;
|
||||
if (typeof desktopSize !== "undefined" && window.innerWidth < desktopSize) {
|
||||
return ContextMenuButtonDisplayType.aside;
|
||||
}
|
||||
|
||||
return ContextMenuButtonDisplayType.dropdown;
|
||||
}, [displayType]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const type = displayType === "auto" ? getTypeByWidth() : displayType;
|
||||
|
||||
setState((s) => ({ ...s, displayType: type }));
|
||||
}, [displayType, getTypeByWidth]);
|
||||
|
||||
const resize = React.useCallback(() => {
|
||||
if (displayType !== "auto") return;
|
||||
const type = getTypeByWidth();
|
||||
|
||||
if (type === state.displayType) return;
|
||||
setState((s) => ({ ...s, displayType: type }));
|
||||
}, [displayType, getTypeByWidth, state.displayType]);
|
||||
|
||||
React.useEffect(() => {
|
||||
throttledResize.current = throttle(resize, 300);
|
||||
|
||||
window.addEventListener("resize", throttledResize.current);
|
||||
|
||||
return () => {
|
||||
if (throttledResize.current) {
|
||||
window.removeEventListener("resize", throttledResize.current);
|
||||
throttledResize.current.cancel();
|
||||
}
|
||||
};
|
||||
}, [resize]);
|
||||
|
||||
const stopAction = React.useCallback(
|
||||
(e: React.MouseEvent) => e.preventDefault(),
|
||||
[],
|
||||
);
|
||||
|
||||
const toggle = (isOpen: boolean) => setState((s) => ({ ...s, isOpen }));
|
||||
|
||||
const onCloseAction = React.useCallback(() => {
|
||||
setState((s) => ({ ...s, isOpen: !s.isOpen }));
|
||||
onClose?.();
|
||||
}, [onClose]);
|
||||
|
||||
const popstate = React.useCallback(() => {
|
||||
window.removeEventListener("popstate", popstate, false);
|
||||
onCloseAction();
|
||||
window.history.go(1);
|
||||
}, [onCloseAction]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
window.removeEventListener("popstate", popstate, false);
|
||||
};
|
||||
}, [popstate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
toggle(opened || false);
|
||||
}, [opened]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (opened && state.displayType === "aside") {
|
||||
window.addEventListener("popstate", popstate, false);
|
||||
}
|
||||
}, [opened, popstate, state.displayType]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setState((s) => ({ ...s, displayType: getTypeByWidth() }));
|
||||
}, [displayType, getTypeByWidth]);
|
||||
|
||||
const onIconButtonClick = (e: React.MouseEvent) => {
|
||||
if (isDisabled || state.displayType === "toggle") {
|
||||
stopAction(e);
|
||||
return;
|
||||
}
|
||||
|
||||
setState((s) => ({ ...s, data: getData(), isOpen: !state.isOpen }));
|
||||
|
||||
if (!isDisabled && state.isOpen) onClick?.(e);
|
||||
};
|
||||
|
||||
const clickOutsideAction = (e: Event) => {
|
||||
const path = e.composedPath();
|
||||
const dropDownItem = path
|
||||
? path.find((x: EventTarget) => x === ref.current)
|
||||
: null;
|
||||
if (dropDownItem) return;
|
||||
|
||||
onCloseAction();
|
||||
};
|
||||
|
||||
const onDropDownItemClick = (
|
||||
item: ContextMenuModel,
|
||||
e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
if ("onClick" in item) {
|
||||
const open = state.displayType === "dropdown";
|
||||
item.onClick?.({ originalEvent: e, action: open, item });
|
||||
toggle(!state.isOpen);
|
||||
}
|
||||
};
|
||||
|
||||
const callNewMenu = (e: React.MouseEvent) => {
|
||||
if (isDisabled || state.displayType !== "toggle") {
|
||||
stopAction(e);
|
||||
return;
|
||||
}
|
||||
|
||||
setState((s) => ({ ...s, data: getData() }));
|
||||
onClick?.(e);
|
||||
};
|
||||
|
||||
const iconButtonName = state.isOpen && iconOpenName ? iconOpenName : iconName;
|
||||
|
||||
return (
|
||||
<StyledOuter
|
||||
ref={ref}
|
||||
className={className}
|
||||
id={id}
|
||||
style={style}
|
||||
onClick={callNewMenu}
|
||||
displayIconBorder={displayIconBorder}
|
||||
data-testid="context-menu-button"
|
||||
>
|
||||
<IconButton
|
||||
className={iconClassName}
|
||||
color={color}
|
||||
hoverColor={hoverColor}
|
||||
clickColor={clickColor}
|
||||
size={size}
|
||||
iconName={iconButtonName}
|
||||
iconHoverName={iconHoverName}
|
||||
iconClickName={iconClickName}
|
||||
isFill={isFill}
|
||||
isDisabled={isDisabled}
|
||||
onClick={onIconButtonClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseDown={onMouseOver}
|
||||
onMouseUp={onMouseOut}
|
||||
title={title}
|
||||
/>
|
||||
{displayType === "dropdown" ? (
|
||||
<DropDown
|
||||
className={dropDownClassName}
|
||||
directionX={directionX}
|
||||
directionY={directionY}
|
||||
open={state.isOpen}
|
||||
forwardedRef={ref}
|
||||
clickOutsideAction={clickOutsideAction}
|
||||
columnCount={columnCount}
|
||||
withBackdrop={isTablet() || isMobile() || Tablet}
|
||||
zIndex={zIndex}
|
||||
isDefaultMode={usePortal}
|
||||
>
|
||||
{state.data?.map(
|
||||
(item: ContextMenuModel, index: number) =>
|
||||
item && (
|
||||
<DropDownItem
|
||||
{...item}
|
||||
id={item.id}
|
||||
key={item.key || index}
|
||||
label={"label" in item ? item.label : ""}
|
||||
onClick={(
|
||||
e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>,
|
||||
) => onDropDownItemClick(item, e)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</DropDown>
|
||||
) : (
|
||||
displayType === "aside" && (
|
||||
<>
|
||||
<Backdrop
|
||||
onClick={onCloseAction}
|
||||
visible={state.isOpen}
|
||||
zIndex={310}
|
||||
isAside
|
||||
/>
|
||||
<Aside
|
||||
visible={state.isOpen || false}
|
||||
scale={false}
|
||||
zIndex={310}
|
||||
onClose={onCloseAction}
|
||||
>
|
||||
<StyledContent>
|
||||
<StyledHeaderContent>
|
||||
<Heading
|
||||
className="header"
|
||||
size={HeadingSize.medium}
|
||||
truncate
|
||||
>
|
||||
{asideHeader}
|
||||
</Heading>
|
||||
</StyledHeaderContent>
|
||||
<StyledBodyContent>
|
||||
{state.data.map(
|
||||
(item: ContextMenuModel, index: number) =>
|
||||
item && (
|
||||
<Link
|
||||
className={`context-menu-button_link${
|
||||
"isHeader" in item && item.isHeader ? "-header" : ""
|
||||
}`}
|
||||
key={item.key || index}
|
||||
fontSize={
|
||||
"isHeader" in item && item.isHeader
|
||||
? "15px"
|
||||
: "13px"
|
||||
}
|
||||
noHover={"isHeader" in item ? item.isHeader : false}
|
||||
fontWeight={600}
|
||||
onClick={(e) => onDropDownItemClick(item, e)}
|
||||
>
|
||||
{"label" in item ? item.label : ""}
|
||||
</Link>
|
||||
),
|
||||
)}
|
||||
</StyledBodyContent>
|
||||
</StyledContent>
|
||||
</Aside>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</StyledOuter>
|
||||
);
|
||||
};
|
||||
|
||||
ContextMenuButtonPure.defaultProps = {
|
||||
opened: false,
|
||||
data: [],
|
||||
title: "",
|
||||
size: 16,
|
||||
isDisabled: false,
|
||||
directionX: "left",
|
||||
isFill: false,
|
||||
|
||||
usePortal: true,
|
||||
displayIconBorder: false,
|
||||
};
|
||||
|
||||
export { ContextMenuButtonPure };
|
||||
|
||||
const compare = (
|
||||
prevProps: ContextMenuButtonProps,
|
||||
nextProps: ContextMenuButtonProps,
|
||||
) => {
|
||||
if (
|
||||
prevProps.opened === nextProps.opened &&
|
||||
prevProps.displayType === nextProps.displayType &&
|
||||
prevProps.isDisabled === nextProps.isDisabled
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const ContextMenuButton = React.memo(ContextMenuButtonPure, compare);
|
@ -0,0 +1,68 @@
|
||||
import { TDirectionX, TDirectionY } from "../../types";
|
||||
import { ContextMenuModel } from "../context-menu";
|
||||
import { ContextMenuButtonDisplayType } from "./ContextMenuButton.enums";
|
||||
|
||||
export interface ContextMenuButtonProps {
|
||||
/** Sets the button to present an opened state */
|
||||
opened?: boolean;
|
||||
/** Array of options for display */
|
||||
data: ContextMenuModel[];
|
||||
/** Function for converting to inner data */
|
||||
getData: () => ContextMenuModel[];
|
||||
/** Specifies the icon title */
|
||||
title?: string;
|
||||
/** Specifies the icon name */
|
||||
iconName?: string;
|
||||
/** Specifies the icon size */
|
||||
size?: number;
|
||||
/** Specifies the icon color */
|
||||
color?: string;
|
||||
/** Sets the button to present a disabled state */
|
||||
isDisabled?: boolean;
|
||||
/** Specifies the icon hover color */
|
||||
hoverColor?: string;
|
||||
/** Specifies the icon click color */
|
||||
clickColor?: string;
|
||||
/** Specifies the icon hover name */
|
||||
iconHoverName?: string;
|
||||
/** Specifies the icon click name */
|
||||
iconClickName?: string;
|
||||
/** Specifies the icon open name */
|
||||
iconOpenName?: string;
|
||||
/** Triggers a callback function when the mouse enters the button borders */
|
||||
onMouseEnter?: (e: React.MouseEvent) => void;
|
||||
/** Triggers a callback function when the mouse leaves the button borders */
|
||||
onMouseLeave?: (e: React.MouseEvent) => void;
|
||||
/** Triggers a callback function when the mouse moves over the button borders */
|
||||
onMouseOver?: (e: React.MouseEvent) => void;
|
||||
/** Triggers a callback function when the mouse moves out of the button borders */
|
||||
onMouseOut?: (e: React.MouseEvent) => void;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
/** Direction X */
|
||||
directionX?: TDirectionX;
|
||||
/** Direction Y */
|
||||
directionY?: TDirectionY;
|
||||
/** Accepts class */
|
||||
className?: string;
|
||||
/** Accepts id */
|
||||
id?: string;
|
||||
/** Accepts css style */
|
||||
style?: React.CSSProperties;
|
||||
/** Sets the number of columns */
|
||||
columnCount?: number;
|
||||
/** Sets the display type */
|
||||
displayType: ContextMenuButtonDisplayType;
|
||||
/** Closing event */
|
||||
onClose?: () => void;
|
||||
/** Sets the drop down open with the portal */
|
||||
usePortal?: boolean;
|
||||
/** Sets the class of the drop down element */
|
||||
dropDownClassName?: string;
|
||||
/** Sets the class of the icon button */
|
||||
iconClassName?: string;
|
||||
/** Enables displaying the icon borders */
|
||||
displayIconBorder?: boolean;
|
||||
isFill?: boolean;
|
||||
zIndex?: number;
|
||||
asideHeader?: React.ReactNode;
|
||||
}
|
@ -5,7 +5,7 @@ ContextMenuButton is used for displaying context menu actions on a list's item
|
||||
### Usage
|
||||
|
||||
```js
|
||||
import ContextMenuButton from "@docspace/components/context-menu-button";
|
||||
import { ContextMenuButton } from "@docspace/shared/components";
|
||||
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
|
||||
```
|
||||
|
@ -10,7 +10,7 @@ export type ContextMenuType = {
|
||||
item,
|
||||
}: {
|
||||
originalEvent: React.MouseEvent | React.ChangeEvent<HTMLInputElement>;
|
||||
action?: string;
|
||||
action?: string | boolean;
|
||||
item: ContextMenuType;
|
||||
}) => VoidFunction;
|
||||
isSeparator?: undefined;
|
||||
@ -22,6 +22,7 @@ export type ContextMenuType = {
|
||||
style?: React.CSSProperties;
|
||||
target?: string;
|
||||
isLoader?: boolean;
|
||||
isHeader?: boolean;
|
||||
onLoad?: () => Promise<ContextMenuModel[]>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
template?: any;
|
||||
@ -31,6 +32,7 @@ export type ContextMenuType = {
|
||||
};
|
||||
|
||||
export type SeparatorType = {
|
||||
id?: string;
|
||||
key: string | number;
|
||||
isSeparator: boolean;
|
||||
disabled?: boolean;
|
||||
|
@ -10,7 +10,7 @@ export interface DropDownItemProps {
|
||||
/** Accepts tab-index */
|
||||
tabIndex?: number;
|
||||
/** Dropdown item text */
|
||||
label?: string;
|
||||
label?: string | React.ReactNode;
|
||||
/** Sets the dropdown item to display as disabled */
|
||||
disabled?: boolean;
|
||||
/** Dropdown item icon */
|
||||
|
@ -34,6 +34,7 @@ const DropDown = ({
|
||||
enableKeyboardEvents,
|
||||
appendTo,
|
||||
eventTypes,
|
||||
zIndex,
|
||||
clickOutsideAction,
|
||||
}: DropDownProps) => {
|
||||
const theme = useTheme();
|
||||
@ -378,6 +379,7 @@ const DropDown = ({
|
||||
directionXStylesDisabled={directionXStylesDisabled}
|
||||
isDropdownReady={state.isDropdownReady}
|
||||
open={open}
|
||||
zIndex={zIndex}
|
||||
>
|
||||
<VirtualList
|
||||
Row={Row}
|
||||
|
@ -57,6 +57,7 @@ export interface DropDownProps {
|
||||
eventTypes?: string[];
|
||||
forceCloseClickOutside?: boolean;
|
||||
withoutBackground?: boolean;
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
export interface VirtualListProps {
|
||||
|
Loading…
Reference in New Issue
Block a user