Skip to content

Commit a81f4ac

Browse files
author
mumiao
committed
Merge branch 'feat_v4' of https://github.com/DTStack/dt-react-component into feat_v4
2 parents 2022f1e + 9f57efc commit a81f4ac

File tree

27 files changed

+1169
-371
lines changed

27 files changed

+1169
-371
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import React from 'react';
2+
import CollapsibleActionItems from '../index';
3+
import { cleanup, fireEvent, render } from '@testing-library/react';
4+
import '@testing-library/jest-dom/extend-expect';
5+
import { act } from 'react-dom/test-utils';
6+
import { Button } from 'antd';
7+
8+
describe('test CollapsibleActionItems', () => {
9+
const items = [
10+
{ key: 'edit', name: '编辑' },
11+
{ key: 'delete', name: '删除' },
12+
{ key: 'remove', name: '移除' },
13+
];
14+
15+
const clickHandler = jest.fn();
16+
17+
const testClick = (el: Element, index: number) => {
18+
const { key, name } = items[index];
19+
expect(el).toHaveTextContent(name);
20+
fireEvent.click(el);
21+
expect(clickHandler).toHaveBeenLastCalledWith(key);
22+
};
23+
24+
afterEach(() => {
25+
cleanup();
26+
clickHandler.mockReset();
27+
});
28+
29+
test('should render link button when item count less than maxCount', () => {
30+
const { container } = render(
31+
<CollapsibleActionItems
32+
actionItems={items}
33+
maxCount={3}
34+
onItemClick={(key) => {
35+
clickHandler(key);
36+
}}
37+
/>
38+
);
39+
const btns = container.querySelectorAll('.dtc-action-btn-wrapper');
40+
expect(btns).toHaveLength(3);
41+
btns.forEach(testClick);
42+
});
43+
44+
test('should render link button when item count more than maxCount', () => {
45+
const { container, getByTestId } = render(
46+
<CollapsibleActionItems
47+
actionItems={items}
48+
maxCount={2}
49+
onItemClick={(key) => {
50+
clickHandler(key);
51+
}}
52+
dropdownProps={{ trigger: ['click'] }}
53+
/>
54+
);
55+
const btns = container.querySelectorAll('.dtc-action-btn-wrapper');
56+
const dropdownEl = getByTestId('action-dropdown-link');
57+
expect(btns).toHaveLength(1);
58+
expect(dropdownEl).toBeInTheDocument();
59+
60+
act(() => {
61+
fireEvent.click(dropdownEl);
62+
});
63+
64+
const dropdownMenuItems = container.querySelectorAll(
65+
'.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item'
66+
);
67+
expect(dropdownMenuItems).toHaveLength(2);
68+
69+
btns.forEach(testClick);
70+
act(() => {
71+
dropdownMenuItems.forEach((el, index) => testClick(el, index + 1));
72+
});
73+
});
74+
75+
test('should support item customRender and disabled attribute', () => {
76+
const customItems = [
77+
{
78+
key: 'reset',
79+
name: '重置',
80+
render: () => {
81+
return (
82+
<Button type="primary" htmlType="submit">
83+
重置Reset
84+
</Button>
85+
);
86+
},
87+
},
88+
{
89+
key: 'open',
90+
name: '开启',
91+
disabled: true,
92+
},
93+
...items,
94+
];
95+
const { getByText } = render(
96+
<CollapsibleActionItems
97+
actionItems={customItems}
98+
onItemClick={(key) => {
99+
clickHandler(key);
100+
}}
101+
dropdownProps={{ trigger: ['click'] }}
102+
/>
103+
);
104+
const resetEl = getByText('重置Reset');
105+
const openEl = getByText('开启');
106+
107+
expect(resetEl.parentElement?.className).toContain('ant-btn-primary');
108+
expect(resetEl.parentElement).toHaveAttribute('type', 'submit');
109+
110+
fireEvent.click(openEl);
111+
expect(clickHandler).not.toBeCalled();
112+
});
113+
});
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
---
2+
title: CollapsibleActionItems 可折叠操作项
3+
group: 组件
4+
toc: content
5+
demo:
6+
cols: 2
7+
---
8+
9+
# CollapsibleActionItems 可折叠操作项
10+
11+
## 何时使用
12+
13+
当操作项过多时,将多余的操作项展示在下拉菜单中,一般用于表格的操作栏
14+
15+
## 示例
16+
17+
```jsx
18+
/**
19+
* title: "表格内使用"
20+
*/
21+
import React, { useState } from 'react';
22+
import { CollapsibleActionItems } from 'dt-react-component';
23+
import { Table, message, Popconfirm, Button } from 'antd';
24+
25+
export default () => {
26+
const [dataSource, setDataSource] = useState([
27+
{
28+
id: 1,
29+
name: '我是测试数据',
30+
},
31+
{
32+
id: 2,
33+
name: '我是样本数据',
34+
},
35+
]);
36+
37+
const handleClick = (key) => {
38+
switch (key) {
39+
case 'edit':
40+
message.info('编辑被点击');
41+
break;
42+
case 'close':
43+
message.info('关闭被点击');
44+
break;
45+
case 'open':
46+
message.info('开启被点击');
47+
default:
48+
break;
49+
}
50+
};
51+
52+
const cols = [
53+
{
54+
title: 'id',
55+
dataIndex: 'id',
56+
},
57+
{
58+
title: '名称',
59+
dataIndex: 'name',
60+
},
61+
{
62+
title: '操作',
63+
dataIndex: '',
64+
width: 230,
65+
render: () => {
66+
const actions = [
67+
{ key: 'edit', name: '编辑' },
68+
{
69+
key: 'delete',
70+
name: '删除',
71+
render: () => (
72+
<Popconfirm title="确认删除?">
73+
<Button type="link" style={{ color: 'red' }}>
74+
删除
75+
</Button>
76+
</Popconfirm>
77+
),
78+
},
79+
{ key: 'close', name: '关闭' },
80+
{ key: 'open', name: '开启', disabled: true },
81+
];
82+
return (
83+
<CollapsibleActionItems
84+
maxCount={3}
85+
actionItems={actions}
86+
onItemClick={handleClick}
87+
/>
88+
);
89+
},
90+
},
91+
];
92+
93+
return <Table rowKey="id" dataSource={dataSource} columns={cols} />;
94+
};
95+
```
96+
97+
```jsx
98+
/**
99+
* title: "自定义分割符与下拉图标"
100+
*/
101+
import React, { useState } from 'react';
102+
import { CollapsibleActionItems } from 'dt-react-component';
103+
import { Table, message, Popconfirm } from 'antd';
104+
import { DownOutlined } from '@ant-design/icons';
105+
106+
export default () => {
107+
const [dataSource, setDataSource] = useState([
108+
{
109+
id: 1,
110+
name: '我是测试数据',
111+
},
112+
]);
113+
114+
const cols = [
115+
{
116+
title: 'id',
117+
dataIndex: 'id',
118+
},
119+
{
120+
title: '名称',
121+
dataIndex: 'name',
122+
},
123+
{
124+
title: '操作',
125+
dataIndex: '',
126+
width: 230,
127+
render: () => {
128+
const actions = [
129+
{ key: 'edit', name: '编辑' },
130+
{ key: 'delete', name: '删除' },
131+
{ key: 'close', name: '关闭' },
132+
{ key: 'open', name: '开启' },
133+
];
134+
return (
135+
<CollapsibleActionItems
136+
maxCount={3}
137+
actionItems={actions}
138+
divider={<span style={{ color: '#eee' }}>-</span>}
139+
collapseIcon={<DownOutlined style={{ marginLeft: 16 }} />}
140+
/>
141+
);
142+
},
143+
},
144+
];
145+
146+
return <Table rowKey="id" dataSource={dataSource} columns={cols} />;
147+
};
148+
```
149+
150+
## API
151+
152+
### CollapsibleActionItems
153+
154+
| 参数 | 说明 | 类型 | 默认值 |
155+
| ------------- | ----------------------------------------------------------------------------------------------------- | --------------------------- | ---------------------------- |
156+
| actionItems | 操作项 | [ActionItem](#actionitem)[] | - |
157+
| maxCount | 最大展示数量,超出部分会折叠至下拉菜单中 | `number` | 3 |
158+
| divider | 操作项分割符 | `React.ReactNode` | `<Divider type='vertical'/>` |
159+
| collapseIcon | 下拉菜单折叠图标 | `React.ReactNode` | `<EllipsisOutlined />` |
160+
| dropdownProps | 折叠菜单额外的 Props, 详细请参考 antd 的[Dropdown](https://ant.design/components/dropdown-cn#api)组件 | `DropDownProps` | - |
161+
| buttonProps | 按钮额外的 Props, 详细请参考 antd 的[Button](https://ant.design/components/button-cn#api)组件 | `ButtonProps` | - |
162+
| onItemClick | 操作项点击事件 | `(key: React.Key) => void` | - |
163+
164+
### ActionItem
165+
166+
| 参数 | 说明 | 类型 | 默认值 |
167+
| -------- | --------------------------------------------------------------- | ----------------------- | ------- |
168+
| key | 唯一标识 | `React.Key` | - |
169+
| name | 显示的名称 | `string` | - |
170+
| disabled | 是否禁用 | `boolean` | `false` |
171+
| render | 自定义渲染,未折叠的操作项默认会以`link`类型的 `Button`形式展示 | `() => React.ReactNode` | - |
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React from 'react';
2+
import { Button, ButtonProps, Divider, Dropdown, DropDownProps, Menu } from 'antd';
3+
import { EllipsisOutlined } from '@ant-design/icons';
4+
5+
type ActionItem = {
6+
key: React.Key;
7+
name: React.ReactNode;
8+
disabled?: boolean;
9+
render?: () => React.ReactNode;
10+
[propName: string]: any;
11+
};
12+
13+
interface ICollapsibleActionItems {
14+
maxCount?: number; // 最多展示数量,超出折叠到下拉菜单中
15+
actionItems: ActionItem[];
16+
className?: string;
17+
divider?: React.ReactNode; // 分隔符
18+
collapseIcon?: React.ReactNode; // 折叠菜单图标
19+
dropdownProps?: Partial<DropDownProps>;
20+
buttonProps?: Partial<ButtonProps>;
21+
onItemClick?(key: React.Key): void;
22+
}
23+
24+
const CollapsibleActionItems: React.FC<ICollapsibleActionItems> = (props) => {
25+
const {
26+
actionItems,
27+
maxCount = 3,
28+
className,
29+
divider = <Divider type="vertical" />,
30+
collapseIcon = <EllipsisOutlined />,
31+
dropdownProps,
32+
buttonProps,
33+
onItemClick,
34+
} = props;
35+
const isOverMaxCount = actionItems.length > maxCount;
36+
const getActionItemNode = (item: ActionItem, isCollapse = false) => {
37+
const customRender = item.render ? item.render() : null;
38+
if (!isCollapse)
39+
return (
40+
<span
41+
className="dtc-action-btn-wrapper"
42+
key={item.key}
43+
onClick={() => !item.disabled && onItemClick?.(item.key)}
44+
>
45+
{customRender || (
46+
<Button type="link" disabled={item.disabled} {...buttonProps}>
47+
{item.name}
48+
</Button>
49+
)}
50+
</span>
51+
);
52+
53+
return (
54+
<Menu.Item key={item.key} disabled={item.disabled}>
55+
{customRender || item.name}
56+
</Menu.Item>
57+
);
58+
};
59+
60+
// 直接展示的操作项
61+
const displayAction = actionItems
62+
.slice(0, isOverMaxCount ? maxCount - 1 : maxCount)
63+
.map((item) => getActionItemNode(item, false));
64+
65+
// 折叠展示的下拉菜单
66+
const dropdownMenu = isOverMaxCount ? (
67+
<Menu data-testid="action-dropdown-menu" onClick={(info) => onItemClick?.(info.key)}>
68+
{actionItems.slice(maxCount - 1).map((item) => getActionItemNode(item, true))}
69+
</Menu>
70+
) : null;
71+
72+
return (
73+
<div className={className}>
74+
{displayAction.map((actionItem, index) => {
75+
const showDivider = index < actionItems.length - 1;
76+
return (
77+
<React.Fragment key={actionItem.key}>
78+
{actionItem}
79+
{showDivider && divider}
80+
</React.Fragment>
81+
);
82+
})}
83+
{dropdownMenu && (
84+
<Dropdown
85+
overlay={dropdownMenu}
86+
getPopupContainer={(triggerNode) => triggerNode.parentElement ?? document.body}
87+
{...dropdownProps}
88+
>
89+
<a data-testid="action-dropdown-link">{collapseIcon}</a>
90+
</Dropdown>
91+
)}
92+
</div>
93+
);
94+
};
95+
96+
export default CollapsibleActionItems;

0 commit comments

Comments
 (0)