diff --git a/components/menu-button/__docs__/adaptor/index.jsx b/components/menu-button/__docs__/adaptor/index.jsx
deleted file mode 100644
index 768564f7db..0000000000
--- a/components/menu-button/__docs__/adaptor/index.jsx
+++ /dev/null
@@ -1,207 +0,0 @@
-/* eslint-disable no-use-before-define */
-import React from 'react';
-import { MenuButton, Menu, Icon } from '@alifd/next';
-import { Types, parseData, ContentType } from '@alifd/adaptor-helper';
-
-const createDataSouce = (list, keys = { selected: [], expanded: {} }, level = 0, prefix='') => {
- const array = [];
- let group = [];
- let grouping = false;
- let index = 0;
-
- list.forEach((item) => {
- switch(item.type) {
- // eslint-disable-next-line no-case-declarations
- case 'node':
- const key = `${prefix || level }-${index++}`;
-
- if (item.children && item.children.length > 0) {
- item.children = createDataSouce(item.children, keys, level + 1, key);
- }
-
- if (grouping) {
- group.push({
- ...item,
- key,
- });
- } else {
- array.push({
- ...item,
- key
- });
- }
-
- if (item.state === 'active') {
- if (item.children && item.children.length > 0) {
- keys.expanded.push(key);
- } else {
- keys.selected.push(key);
- }
- }
-
- return;
- case 'comment':
- if (group.length > 0) {
- array.push({
- type: 'group',
- value: grouping,
- children: group,
- key: `${prefix || level }-${index++}`
- });
- group = [];
- }
- grouping = item.value;
- return;
- case 'divider':
- if (group.length > 0) {
- array.push({
- type: 'group',
- value: grouping,
- children: group,
- key: `${prefix || level }-${index++}`
- });
- group = [];
- }
- grouping = false;
- array.push({
- type: 'divider',
- key: `${prefix || level }-${index++}`,
- });
- return;
- default: return;
- }
- });
-
- if (group.length > 0) {
- array.push({
- type: 'group',
- value: grouping,
- children: group,
- key: `${prefix || level }-${index++}`
- });
- group = [];
- }
-
- return array;
-};
-
-const createMenuItem = (item) => {
- if (item.children.length > 0) {
- return (
-
type === ContentType.text).map(({ value }) => value).join('') : ''}>
- {createContents(item.children)}
-
- );
- }
-
- return type === 'icon' ? : value)} />;
-};
-
-const createContents = (array = []) => {
-
- return array.map((item) => {
- if (item.type === 'group' && item.children.length > 0) {
- return {item.children.map(it => createMenuItem(it))};
- }
-
- if (item.type === 'divider') {
- return ;
- }
-
- return createMenuItem(item);
- });
-};
-
-const _propsValue = ({ shape, level, size, data, ...others}) => {
- const list = parseData(data, { parseContent: true });
- const buttonItem = list[0] ? list[0] : { value: []};
-
- return {
- ...others,
- size,
- disabled: buttonItem.state === 'disabled',
- visible: buttonItem.state === 'active',
- type: shape === 'ghost' ? 'normal' : level,
- popupProps: { needAdjust: false },
- ghost: shape === 'ghost' ? level : false,
- selectMode: "multiple",
- };
-};
-
-export default {
- name: 'MenuButton',
- shape: ['normal', 'text', 'ghost'],
- propsValue: _propsValue,
- editor: (shape = 'normal') => ({
- props: [{
- name: 'level',
- type: Types.enum,
- options: shape === 'text' ? ['normal', 'primary'] : shape === 'ghost' ? ['light', 'dark'] : ['normal', 'primary', 'secondary'],
- default: shape === 'ghost' ? 'light' : 'normal',
- }, {
- name: 'size',
- type: Types.enum,
- options: ['large', 'medium', 'small'],
- default: 'medium'
- }],
- data: {
- icon: true,
- active: true,
- disable: true,
- default: 'Edit Document\n\tUndo\n\t*Redo\n\tCut\n\tCopy\n\tPaste'
- }
- }),
- adaptor: ({ shape, level, size, data, ...others}) => {
- const list = parseData(data, { parseContent: true });
- const buttonItem = list[0] ? list[0] : { value: []};
-
- if (buttonItem.type !== 'node') return null;
-
- const keys = { selected: [], expanded: [] };
- const dataSouce = createDataSouce(list[0] ? list[0].children : [], keys);
-
- const label = buttonItem.value.map(({ type, value}) => {
- if (type === 'icon') return ;
- return value;
- });
-
- const props = _propsValue({ shape, level, size, data, ...others});
-
- return (
- node}
- text={shape === 'text'}
- menuProps={{ openKeys: keys.expanded, style: { textAlign: 'left' } }}
- selectedKeys={keys.selected}
- label={label}
- >
- {createContents(dataSouce)}
-
- );
- },
- demoOptions: (demo) => {
- const { node = { props: {} } } = demo;
- const { level, data } = node.props;
- if (data.indexOf('*') === 0) {
- demo = {
- ...demo,
- height: 250
- };
- }
-
- if (level === 'dark') {
- demo = {
- ...demo,
- background: '#333'
- };
- } else if(level === 'light') {
- demo = {
- ...demo,
- background: 'rgb(235, 236, 240)',
- };
- }
-
- return demo;
- }
-};
diff --git a/components/menu-button/__docs__/adaptor/index.tsx b/components/menu-button/__docs__/adaptor/index.tsx
new file mode 100644
index 0000000000..b1112fc2d6
--- /dev/null
+++ b/components/menu-button/__docs__/adaptor/index.tsx
@@ -0,0 +1,277 @@
+import * as React from 'react';
+import { MenuButton, Menu, Icon } from '@alifd/next';
+import { Types, parseData, ContentType } from '@alifd/adaptor-helper';
+
+const createDataSource = (
+ list: any = [],
+ keys = { selected: [] as string[], expanded: [] as string[] },
+ level = 0,
+ prefix = ''
+) => {
+ const array = [];
+ let group: any = [];
+ let grouping = false;
+ let index = 0;
+
+ list.forEach((item: any) => {
+ let key;
+ switch (item.type) {
+ case 'node':
+ key = `${prefix || level}-${index++}`;
+
+ if (item.children && item.children.length > 0) {
+ item.children = createDataSource(item.children, keys, level + 1, key);
+ }
+
+ if (grouping) {
+ group.push({
+ ...item,
+ key,
+ });
+ } else {
+ array.push({
+ ...item,
+ key,
+ });
+ }
+
+ if (item.state === 'active') {
+ if (item.children && item.children.length > 0) {
+ keys.expanded.push(key);
+ } else {
+ keys.selected.push(key);
+ }
+ }
+
+ return;
+ case 'comment':
+ if (group.length > 0) {
+ array.push({
+ type: 'group',
+ value: grouping,
+ children: group,
+ key: `${prefix || level}-${index++}`,
+ });
+ group = [];
+ }
+ grouping = item.value;
+ return;
+ case 'divider':
+ if (group.length > 0) {
+ array.push({
+ type: 'group',
+ value: grouping,
+ children: group,
+ key: `${prefix || level}-${index++}`,
+ });
+ group = [];
+ }
+ grouping = false;
+ array.push({
+ type: 'divider',
+ key: `${prefix || level}-${index++}`,
+ });
+ return;
+ default:
+ return;
+ }
+ });
+
+ if (group.length > 0) {
+ array.push({
+ type: 'group',
+ value: grouping,
+ children: group,
+ key: `${prefix || level}-${index++}`,
+ });
+ group = [];
+ }
+
+ return array;
+};
+
+const createChildMenuItem = (childItem: any) => {
+ return (
+
+ type === 'icon' ? (
+
+ ) : (
+ value
+ )
+ )}
+ />
+ );
+};
+
+const createMenuItem = (item: any) => {
+ if (item.children && item.children.length > 0) {
+ return (
+ type === ContentType.text)
+ .map(({ value }: any) => value)
+ .join('')
+ : ''
+ }
+ >
+ {item.children.map((childItem: any) => createChildMenuItem(childItem))}
+
+ );
+ }
+ return (
+
+ type === 'icon' ? (
+
+ ) : (
+ value
+ )
+ )}
+ />
+ );
+};
+
+const createContents = (items: any) => {
+ return items.map((item: any) => {
+ if (item.type === 'group' && item.children.length > 0) {
+ return (
+
+ {item.children.map(createChildMenuItem)}
+
+ );
+ }
+ if (item.type === 'divider') {
+ return ;
+ }
+ return createMenuItem(item);
+ });
+};
+
+const _propsValue = ({ shape, level, size, data, ...others }: any) => {
+ const list = parseData(data, { parseContent: true });
+ const buttonItem = list[0] ? list[0] : ({ value: [] } as any);
+
+ return {
+ ...others,
+ size,
+ disabled: buttonItem.state === 'disabled',
+ visible: buttonItem.state === 'active',
+ type: shape === 'ghost' ? 'normal' : level,
+ popupProps: { needAdjust: false },
+ ghost: shape === 'ghost' ? level : false,
+ selectMode: 'multiple',
+ };
+};
+
+export default {
+ name: 'MenuButton',
+ shape: ['normal', 'text', 'ghost'],
+ propsValue: _propsValue,
+ editor: (shape = 'normal') => ({
+ props: [
+ {
+ name: 'level',
+ type: Types.enum,
+ options:
+ shape === 'text'
+ ? ['normal', 'primary']
+ : shape === 'ghost'
+ ? ['light', 'dark']
+ : ['normal', 'primary', 'secondary'],
+ default: shape === 'ghost' ? 'light' : 'normal',
+ },
+ {
+ name: 'size',
+ type: Types.enum,
+ options: ['large', 'medium', 'small'],
+ default: 'medium',
+ },
+ ],
+ data: {
+ icon: true,
+ active: true,
+ disable: true,
+ default: 'Edit Document\n\tUndo\n\t*Redo\n\tCut\n\tCopy\n\tPaste',
+ },
+ }),
+ adaptor: ({ shape, level, size, data, ...others }: any) => {
+ const list = parseData(data, { parseContent: true });
+ const buttonItem = list[0] ? list[0] : ({ value: [] } as any);
+
+ if (buttonItem.type !== 'node') return null;
+
+ const keys = { selected: [], expanded: [] };
+ const dataSource = createDataSource(list[0] ? list[0].children : [], keys);
+
+ let label;
+ if (typeof buttonItem.value === 'object') {
+ label = buttonItem.value.map(({ type, value }: any) => {
+ if (type === 'icon') return ;
+ return value;
+ });
+ }
+
+ const props = _propsValue({ shape, level, size, data, ...others });
+
+ return (
+ node}
+ text={shape === 'text'}
+ menuProps={{ openKeys: keys.expanded, style: { textAlign: 'left' } }}
+ selectedKeys={keys.selected}
+ label={label}
+ >
+ {createContents(dataSource)}
+
+ );
+ },
+ demoOptions: (demo: any) => {
+ const { node = { props: {} } } = demo;
+ const { level, data } = node.props;
+ if (data.indexOf('*') === 0) {
+ demo = {
+ ...demo,
+ height: 250,
+ };
+ }
+
+ if (level === 'dark') {
+ demo = {
+ ...demo,
+ background: '#333',
+ };
+ } else if (level === 'light') {
+ demo = {
+ ...demo,
+ background: 'rgb(235, 236, 240)',
+ };
+ }
+
+ return demo;
+ },
+};
diff --git a/components/menu-button/__docs__/demo/menu/index.tsx b/components/menu-button/__docs__/demo/menu/index.tsx
index c241742f05..ae696b6351 100644
--- a/components/menu-button/__docs__/demo/menu/index.tsx
+++ b/components/menu-button/__docs__/demo/menu/index.tsx
@@ -4,7 +4,7 @@ import { MenuButton } from '@alifd/next';
const { Item, Group, Divider } = MenuButton;
-function selectItem(id) {
+function selectItem(id: any) {
console.log(id);
}
diff --git a/components/menu-button/__docs__/theme/index.jsx b/components/menu-button/__docs__/theme/index.jsx
deleted file mode 100644
index 2f7d6b9266..0000000000
--- a/components/menu-button/__docs__/theme/index.jsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import React from 'react';
-import MenuButton from '../../index';
-import { Demo, DemoGroup, DemoHead, initDemo } from '../../../demo-helper';
-import ConfigProvider from '../../../config-provider';
-import '../../../demo-helper/style';
-import '../../style';
-
-/* eslint-disable */
-const i18nMap = {
- 'zh-cn': {
- menuButton: '菜单按钮',
- textMenuButton: '文本菜单按钮',
- ghostMenuButton: '幽灵菜单按钮',
- normal: '普通',
- expand: '展开',
- disable: '禁用',
- large: '大',
- medium: '中',
- small: '小',
- editDocument: '编辑文档',
- undo: '撤销',
- redo: '重做',
- cut: '剪切',
- copy: '复制',
- paste: '粘贴',
- },
- 'en-us': {
- menuButton: 'Menu Button',
- textMenuButton: 'Text Menu Button',
- ghostMenuButton: 'Ghost Menu Button',
- normal: 'Normal',
- expand: 'Expanded',
- disable: 'Disabled',
- large: 'L',
- medium: 'M',
- small: 'S',
- editDocument: 'Edit Document',
- undo: 'Undo',
- redo: 'Redo',
- cut: 'Cut',
- copy: 'Copy',
- paste: 'Paste',
- },
-};
-
-function renderButton(type, locale, props) {
- const menu = ['undo', 'redo', 'cut', 'copy', 'paste'].map(item => {locale[item]});
- const cols = [locale.large, locale.medium, locale.small];
- const newLabel =
- {locale.editDocument}
- {/* --------- this is for config platform ----------- */}
-
- {/* --------- this is for config platform ----------- */}
-
;
-
- const commonProps = {
- ...props,
- type: type.toLowerCase(),
- label: newLabel,
- };
-
- let style;
-
- if (props) {
- switch (props.ghost) {
- case 'light': {
- style = {
- backgroundColor: '#ebecf0',
- };
- break;
- }
- case 'dark': {
- style = {
- backgroundColor: '#333',
- };
- break;
- }
- }
- }
-
- return (
-
-
- {menu}
- {menu}
- {menu}
-
-
- {menu}
- {menu}
- {menu}
-
-
- {menu}
- {menu}
- {menu}
-
- )
-}
-
-function render(locale, lang) {
- return ReactDOM.render((
-
-
- {renderButton('Normal', locale)}
- {renderButton('Primary', locale)}
- {renderButton('Secondary', locale)}
-
-
- {renderButton('Normal', locale, { text: true })}
- {renderButton('Primary', locale, { text: true })}
-
-
- {renderButton('Normal', locale, { ghost: 'light' })}
- {renderButton('Normal', locale, { ghost: 'dark' })}
-
-
- ), document.getElementById('container'));
-}
-
-window.renderDemo = function(lang = 'en-us') {
- render(i18nMap[lang], lang);
-};
-
-renderDemo();
-
-initDemo('menu-button');
diff --git a/components/menu-button/__docs__/theme/index.tsx b/components/menu-button/__docs__/theme/index.tsx
new file mode 100644
index 0000000000..39af36a95a
--- /dev/null
+++ b/components/menu-button/__docs__/theme/index.tsx
@@ -0,0 +1,172 @@
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+import MenuButton from '../../index';
+import { Demo, DemoGroup, DemoHead, initDemo } from '../../../demo-helper';
+import ConfigProvider from '../../../config-provider';
+import '../../../demo-helper/style';
+import '../../style';
+
+/* eslint-disable */
+const i18nMap = {
+ 'zh-cn': {
+ menuButton: '菜单按钮',
+ textMenuButton: '文本菜单按钮',
+ ghostMenuButton: '幽灵菜单按钮',
+ normal: '普通',
+ expand: '展开',
+ disable: '禁用',
+ large: '大',
+ medium: '中',
+ small: '小',
+ editDocument: '编辑文档',
+ undo: '撤销',
+ redo: '重做',
+ cut: '剪切',
+ copy: '复制',
+ paste: '粘贴',
+ },
+ 'en-us': {
+ menuButton: 'Menu Button',
+ textMenuButton: 'Text Menu Button',
+ ghostMenuButton: 'Ghost Menu Button',
+ normal: 'Normal',
+ expand: 'Expanded',
+ disable: 'Disabled',
+ large: 'L',
+ medium: 'M',
+ small: 'S',
+ editDocument: 'Edit Document',
+ undo: 'Undo',
+ redo: 'Redo',
+ cut: 'Cut',
+ copy: 'Copy',
+ paste: 'Paste',
+ },
+};
+
+function renderButton(type: string, locale: any, props: any = {}) {
+ const menu = ['undo', 'redo', 'cut', 'copy', 'paste'].map(item => (
+ {locale[item]}
+ ));
+ const cols = [locale.large, locale.medium, locale.small];
+ const newLabel = (
+
+ {locale.editDocument}
+ {/* --------- this is for config platform ----------- */}
+
+ {/* --------- this is for config platform ----------- */}
+
+ );
+
+ const commonProps = {
+ ...props,
+ type: type.toLowerCase(),
+ label: newLabel,
+ };
+
+ let style;
+
+ if (props) {
+ switch (props.ghost) {
+ case 'light': {
+ style = {
+ backgroundColor: '#ebecf0',
+ };
+ break;
+ }
+ case 'dark': {
+ style = {
+ backgroundColor: '#333',
+ };
+ break;
+ }
+ }
+ }
+
+ return (
+
+
+
+
+ {menu}
+
+
+ {menu}
+
+
+ {menu}
+
+
+
+
+ {menu}
+
+
+ {menu}
+
+
+ {menu}
+
+
+
+
+ {menu}
+
+
+ {menu}
+
+
+ {menu}
+
+
+
+ );
+}
+
+function render(locale: any, lang: string) {
+ return ReactDOM.render(
+
+
+
+ {renderButton('Normal', locale)}
+ {renderButton('Primary', locale)}
+ {renderButton('Secondary', locale)}
+
+
+ {renderButton('Normal', locale, { text: true })}
+ {renderButton('Primary', locale, { text: true })}
+
+
+ {renderButton('Normal', locale, { ghost: 'light' })}
+ {renderButton('Normal', locale, { ghost: 'dark' })}
+
+
+ ,
+ document.getElementById('container')
+ );
+}
+
+window.renderDemo = function (lang = 'en-us') {
+ render(i18nMap[lang], lang);
+};
+
+renderDemo();
+
+initDemo('menu-button');
diff --git a/components/menu-button/__tests__/a11y-spec.js b/components/menu-button/__tests__/a11y-spec.tsx
similarity index 61%
rename from components/menu-button/__tests__/a11y-spec.js
rename to components/menu-button/__tests__/a11y-spec.tsx
index e1bbcbd5e4..aac615316e 100644
--- a/components/menu-button/__tests__/a11y-spec.js
+++ b/components/menu-button/__tests__/a11y-spec.tsx
@@ -1,11 +1,7 @@
-import React from 'react';
-import Enzyme from 'enzyme';
-import Adapter from 'enzyme-adapter-react-16';
+import * as React from 'react';
import MenuButton from '../index';
import '../style';
-import { unmount, test, mountReact } from '../../util/__tests__/legacy/a11y/validate';
-
-Enzyme.configure({ adapter: new Adapter() });
+import { testReact } from '../../util/__tests__/a11y/validate';
const wrapperClassName = 'js-a11y-test';
const popupProps = { wrapperClassName };
@@ -15,27 +11,16 @@ const menu = ['a', 'b'].map(item => {item} {
- let wrapper;
-
- afterEach(() => {
- if (wrapper) {
- wrapper.unmount();
- wrapper = null;
- }
- unmount();
- });
-
it('should not have any violations', async () => {
- wrapper = await mountReact(
+ await testReact(
{menu}
);
- return test(`.${wrapperClassName}`);
});
it('should not have any violations when text button', async () => {
- wrapper = await mountReact(
+ await testReact(
{menu}
@@ -43,6 +28,5 @@ describe.skip('MenuButton A11y', () => {
);
- return test(`.${wrapperClassName}`);
});
});
diff --git a/components/menu-button/__tests__/index-spec.js b/components/menu-button/__tests__/index-spec.js
deleted file mode 100644
index 4419172adc..0000000000
--- a/components/menu-button/__tests__/index-spec.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from 'react';
-import Enzyme, { mount } from 'enzyme';
-import sinon from 'sinon';
-import Adapter from 'enzyme-adapter-react-16';
-import assert from 'power-assert';
-import MenuButton from '../index';
-import '../style';
-
-Enzyme.configure({ adapter: new Adapter() });
-
-/* eslint-disable no-undef,react/jsx-filename-extension */
-describe('MenuButton', () => {
- const menu = ['a', 'b'].map(item => {item});
-
- let wrapper;
-
- afterEach(() => {
- if (wrapper) {
- wrapper.unmount();
- wrapper = null;
- }
- });
-
- describe('render', () => {
- it('should render', () => {
- const wrapper = mount({menu});
- assert(wrapper.find('button.next-menu-btn').length === 1);
- });
-
- it('should controlled selectedKeys', () => {
- const wrapper = mount(
-
- {menu}
-
- );
- wrapper.setProps({ selectedKeys: ['b'] });
- assert(wrapper.find('li[title="b"][role="option"]').hasClass('next-selected'));
- });
-
- it('should controlled popup visible', () => {
- const wrapper = mount({menu});
- assert(wrapper.find('.next-menu').length === 0);
- wrapper.setProps({ visible: true });
- assert(wrapper.find('.next-menu').length === 1);
- });
- });
-
- describe('action', () => {
- it('should click trigger to open the popup', () => {
- let visible;
- const wrapper = mount(
- (visible = vis)}>
- {menu}
-
- );
- wrapper.find('button.next-menu-btn').simulate('click');
- assert(wrapper.find('.next-menu').length === 1);
- assert(visible);
- });
-
- it('should select in uncontrolled mode', () => {
- const wrapper = mount(
-
- {menu}
-
- );
- wrapper.find('li[title="b"][role="option"]').simulate('click');
- assert(wrapper.find('li[title="b"][role="option"]').hasClass('next-selected'));
- });
-
- it('should select in controlled mode', () => {
- const wrapper = mount(
-
- {menu}
-
- );
- wrapper.find('li[title="b"][role="option"]').simulate('click');
- assert(wrapper.find('li[title="a"][role="option"]').hasClass('next-selected'));
- });
-
- it('should mulitple select can`t close', () => {
- const onVisibleChange = sinon.spy();
- const onItemClick = sinon.spy();
- const wrapper = mount(
-
- {menu}
-
- );
- wrapper.find('li[title="b"][role="option"]').simulate('click');
- assert(onItemClick.calledOnce);
- assert(onVisibleChange.notCalled);
- });
- });
-});
diff --git a/components/menu-button/__tests__/index-spec.tsx b/components/menu-button/__tests__/index-spec.tsx
new file mode 100644
index 0000000000..51f01d2591
--- /dev/null
+++ b/components/menu-button/__tests__/index-spec.tsx
@@ -0,0 +1,91 @@
+import * as React from 'react';
+import { MountReturn } from 'cypress/react';
+import '../style';
+import MenuButton from '../index';
+
+describe('MenuButton', () => {
+ const menu = ['a', 'b'].map(item => {item});
+
+ describe('render', () => {
+ it('should render', () => {
+ cy.mount({menu});
+ cy.get('button.next-menu-btn').should('have.length', 1);
+ });
+
+ it('should controlled selectedKeys', () => {
+ cy.mount(
+
+ {menu}
+
+ ).as('menu-btn');
+ cy.get('@menu-btn').then(({ component, rerender }) => {
+ return rerender(React.cloneElement(component, { selectedKeys: ['b'] }));
+ });
+ cy.get('li[title="b"][role="option"]').should('have.class', 'next-selected');
+ });
+
+ it('should controlled popup visible', () => {
+ cy.mount({menu}).as('menu-btn');
+ cy.get('.next-menu').should('have.length', 0);
+ cy.get('@menu-btn').then(({ component, rerender }) => {
+ return rerender(React.cloneElement(component, { visible: true }));
+ });
+ cy.get('.next-menu').should('have.length', 1);
+ });
+ });
+
+ describe('action', () => {
+ it('should click trigger to open the popup', () => {
+ let visible: boolean;
+ cy.mount(
+ (visible = vis)}>
+ {menu}
+
+ );
+ cy.get('button.next-menu-btn').click();
+ cy.then(() => {
+ cy.get('.next-menu').should('have.length', 1);
+ cy.wrap(visible).should('be.true');
+ });
+ });
+
+ it('should select in uncontrolled mode', () => {
+ cy.mount(
+
+ {menu}
+
+ );
+ cy.get('li[title="b"][role="option"]').click();
+ cy.get('li[title="b"][role="option"]').should('have.class', 'next-selected');
+ });
+
+ it('should select in controlled mode', () => {
+ cy.mount(
+
+ {menu}
+
+ );
+ cy.get('li[title="b"][role="option"]').click();
+ cy.get('li[title="a"][role="option"]').should('have.class', 'next-selected');
+ });
+
+ it('should mulitple select can`t close', () => {
+ const onVisibleChange = cy.spy();
+ const onItemClick = cy.spy();
+ cy.mount(
+
+ {menu}
+
+ );
+ cy.get('li[title="b"][role="option"]').click();
+ cy.wrap(onItemClick).should('be.calledOnce');
+ cy.wrap(onVisibleChange).should('not.have.been.called');
+ });
+ });
+});
diff --git a/components/menu-button/index.jsx b/components/menu-button/index.tsx
similarity index 68%
rename from components/menu-button/index.jsx
rename to components/menu-button/index.tsx
index 83e3e367e2..b164840e38 100644
--- a/components/menu-button/index.jsx
+++ b/components/menu-button/index.tsx
@@ -1,90 +1,44 @@
-import React from 'react';
+import * as React from 'react';
import { findDOMNode } from 'react-dom';
-import PropTypes from 'prop-types';
+import * as PropTypes from 'prop-types';
import { polyfill } from 'react-lifecycles-compat';
-import classnames from 'classnames';
+import * as classnames from 'classnames';
import Button from '../button';
import Icon from '../icon';
-import Menu from '../menu';
+import Menu, { MenuProps } from '../menu';
import Overlay from '../overlay';
import ConfigProvider from '../config-provider';
import { obj, func } from '../util';
+import type { MenuButtonProps, MenuButtonState } from './types';
const { Popup } = Overlay;
/**
* MenuButton
*/
-class MenuButton extends React.Component {
+class MenuButton extends React.Component {
+ static Item = Menu.Item;
+ static Group = Menu.Group;
+ static Divider = Menu.Divider;
+
static propTypes = {
prefix: PropTypes.string,
- /**
- * 按钮上的文本内容
- */
label: PropTypes.node,
- /**
- * 弹层是否与按钮宽度相同
- */
autoWidth: PropTypes.bool,
- /**
- * 弹层触发方式
- */
popupTriggerType: PropTypes.oneOf(['click', 'hover']),
- /**
- * 弹层容器
- */
popupContainer: PropTypes.any,
- /**
- * 弹层展开状态
- */
visible: PropTypes.bool,
- /**
- * 弹层默认是否展开
- */
defaultVisible: PropTypes.bool,
- /**
- * 弹层在显示和隐藏触发的事件
- */
onVisibleChange: PropTypes.func,
- /**
- * 弹层自定义样式
- */
popupStyle: PropTypes.object,
- /**
- * 弹层自定义样式类
- */
popupClassName: PropTypes.string,
- /**
- * 弹层属性透传
- */
popupProps: PropTypes.object,
- /**
- * 是否跟随滚动
- */
followTrigger: PropTypes.bool,
- /**
- * 默认激活的菜单项(用法同 Menu 非受控)
- */
defaultSelectedKeys: PropTypes.array,
- /**
- * 激活的菜单项(用法同 Menu 受控)
- */
selectedKeys: PropTypes.array,
- /**
- * 菜单的选择模式,同 Menu
- */
selectMode: PropTypes.oneOf(['single', 'multiple']),
- /**
- * 点击菜单项后的回调,同 Menu
- */
onItemClick: PropTypes.func,
- /**
- * 选择菜单后的回调,同 Menu
- */
onSelect: PropTypes.func,
- /**
- * 菜单属性透传
- */
menuProps: PropTypes.object,
style: PropTypes.object,
className: PropTypes.string,
@@ -102,7 +56,7 @@ class MenuButton extends React.Component {
menuProps: {},
};
- constructor(props, context) {
+ constructor(props: MenuButtonProps, context?: unknown) {
super(props, context);
this.state = {
selectedKeys: props.defaultSelectedKeys,
@@ -110,8 +64,8 @@ class MenuButton extends React.Component {
};
}
- static getDerivedStateFromProps(props) {
- const st = {};
+ static getDerivedStateFromProps(props: MenuButtonProps) {
+ const st: MenuButtonProps = {};
if ('visible' in props) {
st.visible = props.visible;
@@ -123,11 +77,17 @@ class MenuButton extends React.Component {
return st;
}
+ menu: HTMLElement | undefined;
- clickMenuItem = (key, ...others) => {
+ clickMenuItem: MenuProps['onItemClick'] = (key, item, event) => {
const { selectMode } = this.props;
- this.props.onItemClick(key, ...others);
+ if (typeof this.props.onItemClick === 'function') {
+ this.props.onItemClick(key, item, event);
+ }
+ if (selectMode === 'single') {
+ this.onPopupVisibleChange(false, 'menuSelect');
+ }
if (selectMode === 'multiple') {
return;
@@ -136,35 +96,40 @@ class MenuButton extends React.Component {
this.onPopupVisibleChange(false, 'menuSelect');
};
- selectMenu = (keys, ...others) => {
+ selectMenu: MenuProps['onSelect'] = (keys, ...others) => {
if (!('selectedKeys' in this.props)) {
this.setState({
selectedKeys: keys,
});
}
- this.props.onSelect(keys, ...others);
+ if (typeof this.props.onSelect === 'function') {
+ this.props.onSelect(keys, ...others);
+ }
};
onPopupOpen = () => {
- const button = findDOMNode(this);
- if (this.props.autoWidth && button && this.menu) {
- this.menu.style.width = `${button.offsetWidth}px`;
+ const button = findDOMNode(this) as HTMLElement;
+ if (this.props.autoWidth && button && this?.menu) {
+ this.menu.style.width = `${button?.offsetWidth}px`;
}
};
- onPopupVisibleChange = (visible, type) => {
+ onPopupVisibleChange = (visible: boolean, type: string) => {
if (!('visible' in this.props)) {
this.setState({
visible,
});
}
- this.props.onVisibleChange(visible, type);
+ if (typeof this.props.onVisibleChange === 'function') {
+ this.props.onVisibleChange(visible, type);
+ }
};
- _menuRefHandler = ref => {
- this.menu = findDOMNode(ref);
+ _menuRefHandler = (ref: React.ComponentRef | null) => {
+ this.menu = findDOMNode(ref) as HTMLElement;
- const refFn = this.props.menuProps.ref;
+ //@ts-expect-error menuProps 缺少 ref 类型 React.ComponentRef | null
+ const refFn = this.props?.menuProps?.ref;
if (typeof refFn === 'function') {
refFn(ref);
}
@@ -207,7 +172,11 @@ class MenuButton extends React.Component {
);
const trigger = (
-