mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-18 14:13:37 +08:00
feat: Implement Table.Column and Table.ColumnGroup (#3868)
* Implement Table.Column and Table.ColumnGroup * Upgrade rc-table
This commit is contained in:
parent
1c26bca8fe
commit
ed455c01d1
22
components/table/Column.tsx
Normal file
22
components/table/Column.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import RcTable from 'rc-table';
|
||||
|
||||
export interface ColumnProps<T> {
|
||||
title?: React.ReactNode;
|
||||
key?: string;
|
||||
dataIndex?: string;
|
||||
render?: (text: any, record: T, index: number) => React.ReactNode;
|
||||
filters?: { text: string; value: string }[];
|
||||
onFilter?: (value: any, record: T) => boolean;
|
||||
filterMultiple?: boolean;
|
||||
filterDropdown?: React.ReactNode;
|
||||
sorter?: boolean | ((a: any, b: any) => number);
|
||||
colSpan?: number;
|
||||
width?: string | number;
|
||||
className?: string;
|
||||
fixed?: boolean | ('left' | 'right');
|
||||
filteredValue?: any[];
|
||||
sortOrder?: boolean | ('ascend' | 'descend');
|
||||
}
|
||||
|
||||
export default class Column<T> extends (RcTable.Column as React.ComponentClass<ColumnProps<T>>) {}
|
8
components/table/ColumnGroup.tsx
Normal file
8
components/table/ColumnGroup.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import RcTable from 'rc-table';
|
||||
|
||||
export interface ColumnGroupProps {
|
||||
title?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default class ColumnGroup extends (RcTable.ColumnGroup as React.ComponentClass<ColumnGroupProps>) {}
|
@ -5,13 +5,15 @@ import Pagination, { PaginationProps } from '../pagination';
|
||||
import Icon from '../icon';
|
||||
import Spin from '../spin';
|
||||
import classNames from 'classnames';
|
||||
import { flatArray, treeMap } from './util';
|
||||
import { flatArray, treeMap, normalizeColumns } from './util';
|
||||
import assign from 'object-assign';
|
||||
import splitObject from '../_util/splitObject';
|
||||
import warning from '../_util/warning';
|
||||
import createStore, { Store } from './createStore';
|
||||
import SelectionBox from './SelectionBox';
|
||||
import SelectionCheckboxAll from './SelectionCheckboxAll';
|
||||
import Column, { ColumnProps } from './Column';
|
||||
import ColumnGroup from './ColumnGroup';
|
||||
|
||||
function noop() {
|
||||
}
|
||||
@ -44,24 +46,6 @@ export interface TableRowSelection<T> {
|
||||
onSelectAll?: (selected: boolean, selectedRows: Object[], changeRows: Object[]) => any;
|
||||
}
|
||||
|
||||
export interface TableColumnConfig<T> {
|
||||
title?: React.ReactNode;
|
||||
key?: string;
|
||||
dataIndex?: string;
|
||||
render?: (text: any, record: T, index: number) => React.ReactNode;
|
||||
filters?: { text: string; value: string }[];
|
||||
onFilter?: (value: any, record: T) => boolean;
|
||||
filterMultiple?: boolean;
|
||||
filterDropdown?: React.ReactNode;
|
||||
sorter?: boolean | ((a: any, b: any) => number);
|
||||
colSpan?: number;
|
||||
width?: string | number;
|
||||
className?: string;
|
||||
fixed?: boolean | ('left' | 'right');
|
||||
filteredValue?: any[];
|
||||
sortOrder?: boolean | ('ascend' | 'descend');
|
||||
}
|
||||
|
||||
export interface TableProps<T> {
|
||||
prefixCls?: string;
|
||||
dropdownPrefixCls?: string;
|
||||
@ -69,7 +53,7 @@ export interface TableProps<T> {
|
||||
pagination?: PaginationProps | boolean;
|
||||
size?: 'default' | 'small';
|
||||
dataSource?: T[];
|
||||
columns: TableColumnConfig<T>[];
|
||||
columns?: ColumnProps<T>[];
|
||||
rowKey?: string | ((record: T, index: number) => string);
|
||||
rowClassName?: (record: T, index: number) => string;
|
||||
expandedRowRender?: any;
|
||||
@ -100,9 +84,12 @@ export interface TableContext {
|
||||
}
|
||||
|
||||
export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
static Column = Column;
|
||||
static ColumnGroup = ColumnGroup;
|
||||
|
||||
static propTypes = {
|
||||
dataSource: React.PropTypes.array,
|
||||
columns: React.PropTypes.array.isRequired,
|
||||
columns: React.PropTypes.array,
|
||||
prefixCls: React.PropTypes.string,
|
||||
useFixedHeader: React.PropTypes.bool,
|
||||
rowSelection: React.PropTypes.object,
|
||||
@ -136,6 +123,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
context: TableContext;
|
||||
CheckboxPropsCache: Object;
|
||||
store: Store;
|
||||
columns: ColumnProps<T>[];
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -149,6 +137,8 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
|
||||
const pagination = props.pagination || {};
|
||||
|
||||
this.columns = props.columns || normalizeColumns(props.children);
|
||||
|
||||
this.state = assign({}, this.getSortStateFromColumns(), {
|
||||
// 减少状态
|
||||
filters: this.getFiltersFromColumns(),
|
||||
@ -225,6 +215,8 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
)) {
|
||||
this.CheckboxPropsCache = {};
|
||||
}
|
||||
|
||||
this.columns = nextProps.columns || normalizeColumns(nextProps.children);
|
||||
}
|
||||
|
||||
if (this.getSortOrderColumns(nextProps.columns).length > 0) {
|
||||
@ -292,11 +284,11 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
}
|
||||
|
||||
getSortOrderColumns(columns?) {
|
||||
return (columns || this.props.columns || []).filter(column => 'sortOrder' in column);
|
||||
return (columns || this.columns || []).filter(column => 'sortOrder' in column);
|
||||
}
|
||||
|
||||
getFilteredValueColumns(columns?) {
|
||||
return (columns || this.props.columns || []).filter(column => column.filteredValue);
|
||||
return (columns || this.columns || []).filter(column => column.filteredValue);
|
||||
}
|
||||
|
||||
getFiltersFromColumns(columns?) {
|
||||
@ -376,7 +368,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
[this.getColumnKey(column)]: nextFilters,
|
||||
});
|
||||
// Remove filters not in current columns
|
||||
const currentColumnKeys = props.columns.map(c => this.getColumnKey(c));
|
||||
const currentColumnKeys = this.columns.map(c => this.getColumnKey(c));
|
||||
Object.keys(filters).forEach((columnKey) => {
|
||||
if (currentColumnKeys.indexOf(columnKey) < 0) {
|
||||
delete filters[columnKey];
|
||||
@ -571,7 +563,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
|
||||
renderRowSelection() {
|
||||
const { prefixCls, rowSelection } = this.props;
|
||||
const columns = this.props.columns.concat();
|
||||
const columns = this.columns.concat();
|
||||
if (rowSelection) {
|
||||
const data = this.getFlatCurrentPageData().filter((item) => {
|
||||
if (rowSelection.getCheckboxProps) {
|
||||
@ -579,7 +571,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const selectionColumn: TableColumnConfig<any> = {
|
||||
const selectionColumn: ColumnProps<any> = {
|
||||
key: 'selection-column',
|
||||
render: this.renderSelectionBox(rowSelection.type),
|
||||
className: `${prefixCls}-selection-column`,
|
||||
@ -743,7 +735,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
|
||||
}
|
||||
|
||||
findColumn(myKey) {
|
||||
return this.props.columns.filter(c => this.getColumnKey(c) === myKey)[0];
|
||||
return this.columns.filter(c => this.getColumnKey(c) === myKey)[0];
|
||||
}
|
||||
|
||||
getCurrentPageData() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 10
|
||||
order: 11
|
||||
title:
|
||||
en-US: border, title and footer
|
||||
zh-CN: 带边框
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 13
|
||||
order: 14
|
||||
title:
|
||||
en-US: colSpan and rowSpan
|
||||
zh-CN: 表格行/列合并
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 21
|
||||
order: 22
|
||||
title:
|
||||
en-US: Dynamic Settings
|
||||
zh-CN: 动态控制表格属性
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 15
|
||||
order: 16
|
||||
title:
|
||||
en-US: Tree data
|
||||
zh-CN: 树形数据展示
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 12
|
||||
order: 13
|
||||
title:
|
||||
en-US: Expandable Row
|
||||
zh-CN: 可展开
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 18
|
||||
order: 19
|
||||
title:
|
||||
en-US: Fixed Columns and Header
|
||||
zh-CN: 固定头和列
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 17
|
||||
order: 18
|
||||
title:
|
||||
en-US: Fixed Columns
|
||||
zh-CN: 固定列
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 16
|
||||
order: 17
|
||||
title:
|
||||
en-US: Fixed Header
|
||||
zh-CN: 固定表头
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 20
|
||||
order: 21
|
||||
title:
|
||||
en-US: Grouping table head
|
||||
zh-CN: 表头分组
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 6
|
||||
order: 7
|
||||
title:
|
||||
en-US: Filter and sorter
|
||||
zh-CN: 筛选和排序
|
||||
|
82
components/table/demo/jsx.md
Normal file
82
components/table/demo/jsx.md
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
order: 1
|
||||
title:
|
||||
en-US: JSX style API
|
||||
zh-CN: JSX 风格的 API
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
使用 JSX 风格的 API(2.5.0 以后引入)
|
||||
|
||||
## en-US
|
||||
|
||||
Using JSX style API (introduced in 2.5.0)
|
||||
|
||||
````jsx
|
||||
import { Table, Icon } from 'antd';
|
||||
|
||||
const { Column, ColumnGroup } = Table;
|
||||
|
||||
const data = [{
|
||||
key: '1',
|
||||
firstName: 'John',
|
||||
lastName: 'Brown',
|
||||
age: 32,
|
||||
address: 'New York No. 1 Lake Park',
|
||||
}, {
|
||||
key: '2',
|
||||
firstName: 'Jim',
|
||||
lastName: 'Green',
|
||||
age: 42,
|
||||
address: 'London No. 1 Lake Park',
|
||||
}, {
|
||||
key: '3',
|
||||
firstName: 'Joe',
|
||||
lastName: 'Black',
|
||||
age: 32,
|
||||
address: 'Sidney No. 1 Lake Park',
|
||||
}];
|
||||
|
||||
ReactDOM.render(
|
||||
<Table dataSource={data}>
|
||||
<ColumnGroup title="Name">
|
||||
<Column
|
||||
title="First Name"
|
||||
dataIndex="firstName"
|
||||
key="firstName"
|
||||
/>
|
||||
<Column
|
||||
title="Last Name"
|
||||
dataIndex="lastName"
|
||||
key="lastName"
|
||||
/>
|
||||
</ColumnGroup>
|
||||
<Column
|
||||
title="Age"
|
||||
dataIndex="age"
|
||||
key="age"
|
||||
/>
|
||||
<Column
|
||||
title="Address"
|
||||
dataIndex="address"
|
||||
key="address"
|
||||
/>
|
||||
<Column
|
||||
title="Action"
|
||||
key="action"
|
||||
render={(text, record) => (
|
||||
<span>
|
||||
<a href="#">Action 一 {record.name}</a>
|
||||
<span className="ant-divider" />
|
||||
<a href="#">Delete</a>
|
||||
<span className="ant-divider" />
|
||||
<a href="#" className="ant-dropdown-link">
|
||||
More actions<Icon type="down" />
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
, mountNode);
|
||||
````
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 8
|
||||
order: 9
|
||||
title:
|
||||
en-US: No pagination
|
||||
zh-CN: 不显示分页
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 5
|
||||
order: 6
|
||||
title:
|
||||
en-US: pagination
|
||||
zh-CN: 分页
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 6
|
||||
order: 7
|
||||
title:
|
||||
en-US: Reset filters and sorters
|
||||
zh-CN: 可控的筛选和排序
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 2
|
||||
order: 3
|
||||
title:
|
||||
en-US: Selection and operation
|
||||
zh-CN: 选择和操作
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 3
|
||||
order: 4
|
||||
title:
|
||||
en-US: Checkbox props
|
||||
zh-CN: 选择框属性
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 1
|
||||
order: 2
|
||||
title:
|
||||
en-US: selection
|
||||
zh-CN: 可选择
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 9
|
||||
order: 10
|
||||
title:
|
||||
en-US: size
|
||||
zh-CN: 紧凑型
|
||||
|
@ -78,7 +78,7 @@ const columns = [{
|
||||
|
||||
### Column
|
||||
|
||||
One of Property `columns` for describing column.
|
||||
One of Property `columns` for descriping column, Column has the same API.
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
|---------------|--------------------------|-----------------|--------------|
|
||||
@ -100,6 +100,12 @@ One of Property `columns` for describing column.
|
||||
| fixed | set column to be fixed: `true`(same as left) `'left'` `'right'` | Boolean or String | false |
|
||||
| sortOrder | controlled sorted value: `'ascend'` `'descend'` `false` | Boolean or String | - |
|
||||
|
||||
### ColumnGroup
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
|---------------|--------------------------|-----------------|--------------|
|
||||
| title | title of the column group | String or React.Element | - |
|
||||
|
||||
### rowSelection
|
||||
|
||||
Properties for selection.
|
||||
|
@ -79,7 +79,7 @@ const columns = [{
|
||||
|
||||
### Column
|
||||
|
||||
列描述数据对象,是 columns 中的一项。
|
||||
列描述数据对象,是 columns 中的一项,Column 使用相同的 API。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|-----------|----------------------------|-----------------|---------|
|
||||
@ -101,6 +101,12 @@ const columns = [{
|
||||
| fixed | 列是否固定,可选 `true`(等效于 left) `'left'` `'right'` | Boolean or String | false |
|
||||
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `false` | Boolean or String | - |
|
||||
|
||||
### ColumnGroup
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|-----------|----------------------------|-----------------|---------|
|
||||
| title | 列头显示文字 | String or React.Element | - |
|
||||
|
||||
### rowSelection
|
||||
|
||||
选择功能的配置。
|
||||
|
@ -1,4 +1,8 @@
|
||||
import React from 'react';
|
||||
import assign from 'object-assign';
|
||||
import Column from './Column';
|
||||
import ColumnGroup from './ColumnGroup';
|
||||
|
||||
export function flatArray(data: Object[] = [], childrenName = 'children') {
|
||||
const result: Object[] = [];
|
||||
const loop = (array) => {
|
||||
@ -24,3 +28,25 @@ export function treeMap(tree: Object[], mapper: Function, childrenName = 'childr
|
||||
return assign({}, mapper(node, index), extra);
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeColumns(elements) {
|
||||
const columns: any[] = [];
|
||||
React.Children.forEach(elements, (element: React.ReactElement<any>) => {
|
||||
if (!isColumnElement(element)) {
|
||||
return;
|
||||
}
|
||||
const column = assign({}, element.props);
|
||||
if (element.key) {
|
||||
column.key = element.key;
|
||||
}
|
||||
if (element.type as any === ColumnGroup) {
|
||||
column.children = normalizeColumns(column.children);
|
||||
}
|
||||
columns.push(column);
|
||||
});
|
||||
return columns;
|
||||
}
|
||||
|
||||
function isColumnElement(element) {
|
||||
return element && (element.type === Column || element.type === ColumnGroup);
|
||||
}
|
||||
|
@ -62,7 +62,7 @@
|
||||
"rc-slider": "~5.3.0",
|
||||
"rc-steps": "~2.2.0",
|
||||
"rc-switch": "~1.4.2",
|
||||
"rc-table": "~5.0.0",
|
||||
"rc-table": "~5.2.0",
|
||||
"rc-tabs": "~7.0.5",
|
||||
"rc-time-picker": "~2.2.1",
|
||||
"rc-tooltip": "~3.4.2",
|
||||
|
@ -2,6 +2,10 @@ import React from 'react';
|
||||
import createStore from '../../components/table/createStore';
|
||||
import Table from '../../components/table';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import { render } from 'enzyme';
|
||||
import { renderToJson } from 'enzyme-to-json';
|
||||
|
||||
const { Column, ColumnGroup } = Table;
|
||||
|
||||
describe('Table', () => {
|
||||
describe('row selection', () => {
|
||||
@ -88,4 +92,44 @@ describe('Table', () => {
|
||||
expect(checkboxes[2].disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSX style API', () => {
|
||||
it('renders correctly', () => {
|
||||
const data = [{
|
||||
key: '1',
|
||||
firstName: 'John',
|
||||
lastName: 'Brown',
|
||||
age: 32,
|
||||
}, {
|
||||
key: '2',
|
||||
firstName: 'Jim',
|
||||
lastName: 'Green',
|
||||
age: 42,
|
||||
}];
|
||||
|
||||
const wrapper = render(
|
||||
<Table dataSource={data} pagination={false}>
|
||||
<ColumnGroup title="Name">
|
||||
<Column
|
||||
title="First Name"
|
||||
dataIndex="firstName"
|
||||
key="firstName"
|
||||
/>
|
||||
<Column
|
||||
title="Last Name"
|
||||
dataIndex="lastName"
|
||||
key="lastName"
|
||||
/>
|
||||
</ColumnGroup>
|
||||
<Column
|
||||
title="Age"
|
||||
dataIndex="age"
|
||||
key="age"
|
||||
/>
|
||||
</Table>
|
||||
);
|
||||
|
||||
expect(renderToJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
105
tests/table/__snapshots__/Table.test.js.snap
Normal file
105
tests/table/__snapshots__/Table.test.js.snap
Normal file
@ -0,0 +1,105 @@
|
||||
exports[`Table JSX style API renders correctly 1`] = `
|
||||
<div
|
||||
class=" clearfix">
|
||||
<div
|
||||
class="ant-spin-nested-loading">
|
||||
<div
|
||||
class="ant-spin-container">
|
||||
<div
|
||||
class="ant-table ant-table-large ant-table-scroll-position-left">
|
||||
<div
|
||||
class="ant-table-content">
|
||||
<div
|
||||
class="">
|
||||
<span>
|
||||
<div
|
||||
class="ant-table-body">
|
||||
<table
|
||||
class="">
|
||||
<colgroup>
|
||||
<col />
|
||||
<col />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead
|
||||
class="ant-table-thead">
|
||||
<tr>
|
||||
<th
|
||||
class=""
|
||||
colspan="2">
|
||||
<span>
|
||||
Name
|
||||
</span>
|
||||
</th>
|
||||
<th
|
||||
class=""
|
||||
rowspan="2">
|
||||
<span>
|
||||
Age
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
class="">
|
||||
<span>
|
||||
First Name
|
||||
</span>
|
||||
</th>
|
||||
<th
|
||||
class="">
|
||||
<span>
|
||||
Last Name
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="ant-table-tbody">
|
||||
<tr
|
||||
class="ant-table-row ant-table-row-level-0">
|
||||
<td
|
||||
class="">
|
||||
<span
|
||||
class="ant-table-row-indent indent-level-0"
|
||||
style="padding-left:0px;" />
|
||||
John
|
||||
</td>
|
||||
<td
|
||||
class="">
|
||||
Brown
|
||||
</td>
|
||||
<td
|
||||
class="">
|
||||
32
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="ant-table-row ant-table-row-level-0">
|
||||
<td
|
||||
class="">
|
||||
<span
|
||||
class="ant-table-row-indent indent-level-0"
|
||||
style="padding-left:0px;" />
|
||||
Jim
|
||||
</td>
|
||||
<td
|
||||
class="">
|
||||
Green
|
||||
</td>
|
||||
<td
|
||||
class="">
|
||||
42
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
Loading…
Reference in New Issue
Block a user