Browse Source

add table

pull/165/head
tjz 7 years ago
parent
commit
a01905cc6e
  1. 6
      components/pagination/Pagination.jsx
  2. 25
      components/spin/Spin.jsx
  3. 1
      components/spin/index.js
  4. 6
      components/table/Column.jsx
  5. 10
      components/table/ColumnGroup.jsx
  6. 16
      components/table/FilterDropdownMenuWrapper.jsx
  7. 68
      components/table/SelectionBox.tsx
  8. 183
      components/table/SelectionCheckboxAll.tsx
  9. 1002
      components/table/Table.tsx
  10. 59
      components/table/createBodyRow.jsx
  11. 11
      components/table/createStore.jsx
  12. 228
      components/table/filterDropdown.tsx
  13. 210
      components/table/index.en-US.md
  14. 5
      components/table/index.jsx
  15. 210
      components/table/index.zh-CN.md
  16. 186
      components/table/interface.js
  17. 9
      components/table/style/index.js
  18. 568
      components/table/style/index.less
  19. 109
      components/table/style/size.less
  20. 66
      components/table/util.js

6
components/pagination/Pagination.jsx

@ -7,7 +7,7 @@ import LocaleReceiver from '../locale-provider/LocaleReceiver'
import { getOptionProps } from '../_util/props-util'
import VcPagination from '../vc-pagination'
export const PaginationProps = {
export const PaginationProps = () => ({
total: PropTypes.number,
defaultCurrent: PropTypes.number,
current: PropTypes.number,
@ -29,11 +29,11 @@ export const PaginationProps = {
prefixCls: PropTypes.string,
selectPrefixCls: PropTypes.string,
itemRender: PropTypes.any,
}
})
export default {
props: {
...PaginationProps,
...PaginationProps(),
prefixCls: PropTypes.string.def('ant-pagination'),
selectPrefixCls: PropTypes.string.def('ant-select'),
},

25
components/spin/Spin.jsx

@ -2,20 +2,27 @@
import PropTypes from '../_util/vue-types'
import BaseMixin from '../_util/BaseMixin'
import isCssAnimationSupported from '../_util/isCssAnimationSupported'
import { filterEmpty } from '../_util/props-util'
import { filterEmpty, initDefaultProps } from '../_util/props-util'
import getTransitionProps from '../_util/getTransitionProps'
export const SpinProps = () => ({
prefixCls: PropTypes.string,
spinning: PropTypes.bool,
size: PropTypes.oneOf(['small', 'default', 'large']),
wrapperClassName: PropTypes.string,
tip: PropTypes.string,
delay: PropTypes.number,
})
export default {
name: 'Spin',
mixins: [BaseMixin],
props: {
prefixCls: PropTypes.string.def('ant-spin'),
spinning: PropTypes.bool.def(true),
size: PropTypes.oneOf(['small', 'default', 'large']).def('default'),
wrapperClassName: PropTypes.string.def(''),
tip: PropTypes.string,
delay: PropTypes.number,
},
props: initDefaultProps(SpinProps(), {
prefixCls: 'ant-spin',
size: 'default',
spinning: true,
wrapperClassName: '',
}),
data () {
const { spinning } = this
return {

1
components/spin/index.js

@ -1,3 +1,4 @@
import Spin from './Spin'
export { SpinProps } from './Spin'
export default Spin

6
components/table/Column.jsx

@ -0,0 +1,6 @@
import { ColumnProps } from './interface'
export default {
name: 'Column',
props: ColumnProps,
}

10
components/table/ColumnGroup.jsx

@ -0,0 +1,10 @@
import PropTypes from '../_util/vue-types'
export default {
name: 'ColumnGroup',
props: {
title: PropTypes.any,
},
__ANT_TABLE_COLUMN_GROUP: true,
}

16
components/table/FilterDropdownMenuWrapper.jsx

@ -0,0 +1,16 @@
export default {
methods: {
handelClick (e) {
this.$emit('click', e)
},
},
render () {
const { $slots, handelClick } = this
return (
<div onClick={handelClick}>
{$slots.default}
</div>
)
},
}

68
components/table/SelectionBox.tsx

@ -0,0 +1,68 @@
import * as React from 'react';
import Checkbox from '../checkbox';
import Radio from '../radio';
import { SelectionBoxProps, SelectionBoxState } from './interface';
export default class SelectionBox extends React.Component<SelectionBoxProps, SelectionBoxState> {
unsubscribe: () => void;
constructor(props: SelectionBoxProps) {
super(props);
this.state = {
checked: this.getCheckState(props),
};
}
componentDidMount() {
this.subscribe();
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
subscribe() {
const { store } = this.props;
this.unsubscribe = store.subscribe(() => {
const checked = this.getCheckState(this.props);
this.setState({ checked });
});
}
getCheckState(props: SelectionBoxProps) {
const { store, defaultSelection, rowIndex } = props;
let checked = false;
if (store.getState().selectionDirty) {
checked = store.getState().selectedRowKeys.indexOf(rowIndex) >= 0;
} else {
checked = (store.getState().selectedRowKeys.indexOf(rowIndex) >= 0 ||
defaultSelection.indexOf(rowIndex) >= 0);
}
return checked;
}
render() {
const { type, rowIndex, ...rest } = this.props;
const { checked } = this.state;
if (type === 'radio') {
return (
<Radio
checked={checked}
value={rowIndex}
{...rest}
/>
);
} else {
return (
<Checkbox
checked={checked}
{...rest}
/>
);
}
}
}

183
components/table/SelectionCheckboxAll.tsx

@ -0,0 +1,183 @@
import * as React from 'react';
import Checkbox from '../checkbox';
import Dropdown from '../dropdown';
import Menu from '../menu';
import Icon from '../icon';
import classNames from 'classnames';
import { SelectionCheckboxAllProps, SelectionCheckboxAllState, SelectionItem } from './interface';
export default class SelectionCheckboxAll<T> extends
React.Component<SelectionCheckboxAllProps<T>, SelectionCheckboxAllState> {
unsubscribe: () => void;
defaultSelections: SelectionItem[];
constructor(props: SelectionCheckboxAllProps<T>) {
super(props);
this.defaultSelections = props.hideDefaultSelections ? [] : [{
key: 'all',
text: props.locale.selectAll,
onSelect: () => {},
}, {
key: 'invert',
text: props.locale.selectInvert,
onSelect: () => {},
}];
this.state = {
checked: this.getCheckState(props),
indeterminate: this.getIndeterminateState(props),
};
}
componentDidMount() {
this.subscribe();
}
componentWillReceiveProps(nextProps: SelectionCheckboxAllProps<T>) {
this.setCheckState(nextProps);
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
subscribe() {
const { store } = this.props;
this.unsubscribe = store.subscribe(() => {
this.setCheckState(this.props);
});
}
checkSelection(data: T[], type: string, byDefaultChecked: boolean) {
const { store, getCheckboxPropsByItem, getRecordKey } = this.props;
// type should be 'every' | 'some'
if (type === 'every' || type === 'some') {
return (
byDefaultChecked
? data[type]((item, i) => getCheckboxPropsByItem(item, i).defaultChecked)
: data[type]((item, i) =>
store.getState().selectedRowKeys.indexOf(getRecordKey(item, i)) >= 0)
);
}
return false;
}
setCheckState(props: SelectionCheckboxAllProps<T>) {
const checked = this.getCheckState(props);
const indeterminate = this.getIndeterminateState(props);
if (checked !== this.state.checked) {
this.setState({ checked });
}
if (indeterminate !== this.state.indeterminate) {
this.setState({ indeterminate });
}
}
getCheckState(props: SelectionCheckboxAllProps<T>) {
const { store, data } = props;
let checked;
if (!data.length) {
checked = false;
} else {
checked = store.getState().selectionDirty
? this.checkSelection(data, 'every', false)
: (
this.checkSelection(data, 'every', false) ||
this.checkSelection(data, 'every', true)
);
}
return checked;
}
getIndeterminateState(props: SelectionCheckboxAllProps<T>) {
const { store, data } = props;
let indeterminate;
if (!data.length) {
indeterminate = false;
} else {
indeterminate = store.getState().selectionDirty
? (
this.checkSelection(data, 'some', false) &&
!this.checkSelection(data, 'every', false)
)
: ((this.checkSelection(data, 'some', false) &&
!this.checkSelection(data, 'every', false)) ||
(this.checkSelection(data, 'some', true) &&
!this.checkSelection(data, 'every', true))
);
}
return indeterminate;
}
handleSelectAllChagne = (e: React.ChangeEvent<HTMLInputElement>) => {
let checked = e.target.checked;
this.props.onSelect(checked ? 'all' : 'removeAll', 0, null);
}
renderMenus(selections: SelectionItem[]) {
return selections.map((selection, index) => {
return (
<Menu.Item
key={selection.key || index}
>
<div
onClick={() => {this.props.onSelect(selection.key, index, selection.onSelect); }}
>
{selection.text}
</div>
</Menu.Item>
);
});
}
render() {
const { disabled, prefixCls, selections, getPopupContainer } = this.props;
const { checked, indeterminate } = this.state;
let selectionPrefixCls = `${prefixCls}-selection`;
let customSelections: React.ReactNode = null;
if (selections) {
let newSelections = Array.isArray(selections) ? this.defaultSelections.concat(selections)
: this.defaultSelections;
const menu = (
<Menu
className={`${selectionPrefixCls}-menu`}
selectedKeys={[]}
>
{this.renderMenus(newSelections)}
</Menu>
);
customSelections = newSelections.length > 0 ? (
<Dropdown
overlay={menu}
getPopupContainer={getPopupContainer}
>
<div className={`${selectionPrefixCls}-down`}>
<Icon type="down" />
</div>
</Dropdown>
) : null;
}
return (
<div className={selectionPrefixCls}>
<Checkbox
className={classNames({ [`${selectionPrefixCls}-select-all-custom`]: customSelections })}
checked={checked}
indeterminate={indeterminate}
disabled={disabled}
onChange={this.handleSelectAllChagne}
/>
{customSelections}
</div>
);
}
}

1002
components/table/Table.tsx

File diff suppressed because it is too large Load Diff

59
components/table/createBodyRow.jsx

@ -0,0 +1,59 @@
import PropTypes from '../_util/vue-types'
import { Store } from './createStore'
const BodyRowProps = {
store: Store,
rowKey: PropTypes.string,
prefixCls: PropTypes.string,
}
export default function createTableRow (Component = 'tr') {
const BodyRow = {
name: 'BodyRow',
props: BodyRowProps,
data () {
const { selectedRowKeys } = this.store.getState()
return {
selected: selectedRowKeys.indexOf(this.rowKey) >= 0,
}
},
mounted () {
this.subscribe()
},
beforeDestroy () {
if (this.unsubscribe) {
this.unsubscribe()
}
},
methods: {
subscribe () {
const { store, rowKey } = this
this.unsubscribe = store.subscribe(() => {
const { selectedRowKeys } = this.store.getState()
const selected = selectedRowKeys.indexOf(rowKey) >= 0
if (selected !== this.selected) {
this.selected = selected
}
})
},
},
render () {
const className = {
[`${this.props.prefixCls}-row-selected`]: this.selected,
}
return (
<Component class={className}>
{this.$slots.default}
</Component>
)
},
}
return BodyRow
}

11
components/table/createStore.jsx

@ -0,0 +1,11 @@
import PropTypes from '../_util/vue-types'
export const Store = PropTypes.shape({
setState: PropTypes.func,
getState: PropTypes.func,
subscribe: PropTypes.func,
}).loose
import create from '../_util/store/create'
const createStore = create
export default createStore

228
components/table/filterDropdown.tsx

@ -0,0 +1,228 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Menu, { SubMenu, Item as MenuItem } from 'rc-menu';
import closest from 'dom-closest';
import classNames from 'classnames';
import Dropdown from '../dropdown';
import Icon from '../icon';
import Checkbox from '../checkbox';
import Radio from '../radio';
import FilterDropdownMenuWrapper from './FilterDropdownMenuWrapper';
import { FilterMenuProps, FilterMenuState, ColumnProps, ColumnFilterItem } from './interface';
export default class FilterMenu<T> extends React.Component<FilterMenuProps<T>, FilterMenuState> {
static defaultProps = {
handleFilter() {},
column: {},
};
neverShown: boolean;
constructor(props: FilterMenuProps<T>) {
super(props);
const visible = ('filterDropdownVisible' in props.column) ?
props.column.filterDropdownVisible : false;
this.state = {
selectedKeys: props.selectedKeys,
keyPathOfSelectedItem: {}, // 记录所有有选中子菜单的祖先菜单
visible,
};
}
componentDidMount() {
const { column } = this.props;
this.setNeverShown(column);
}
componentWillReceiveProps(nextProps: FilterMenuProps<T>) {
const { column } = nextProps;
this.setNeverShown(column);
const newState = {} as {
selectedKeys: string[];
visible: boolean;
};
if ('selectedKeys' in nextProps) {
newState.selectedKeys = nextProps.selectedKeys;
}
if ('filterDropdownVisible' in column) {
newState.visible = column.filterDropdownVisible as boolean;
}
if (Object.keys(newState).length > 0) {
this.setState(newState);
}
}
setNeverShown = (column: ColumnProps<T>) => {
const rootNode = ReactDOM.findDOMNode(this);
const filterBelongToScrollBody = !!closest(rootNode, `.ant-table-scroll`);
if (filterBelongToScrollBody) {
// When fixed column have filters, there will be two dropdown menus
// Filter dropdown menu inside scroll body should never be shown
// To fix https://github.com/ant-design/ant-design/issues/5010 and
// https://github.com/ant-design/ant-design/issues/7909
this.neverShown = !!column.fixed;
}
}
setSelectedKeys = ({ selectedKeys }: { selectedKeys: string[] }) => {
this.setState({ selectedKeys });
}
setVisible(visible: boolean) {
const { column } = this.props;
if (!('filterDropdownVisible' in column)) {
this.setState({ visible });
}
if (column.onFilterDropdownVisibleChange) {
column.onFilterDropdownVisibleChange(visible);
}
}
handleClearFilters = () => {
this.setState({
selectedKeys: [],
}, this.handleConfirm);
}
handleConfirm = () => {
this.setVisible(false);
this.confirmFilter();
}
onVisibleChange = (visible: boolean) => {
this.setVisible(visible);
if (!visible) {
this.confirmFilter();
}
}
confirmFilter() {
if (this.state.selectedKeys !== this.props.selectedKeys) {
this.props.confirmFilter(this.props.column, this.state.selectedKeys);
}
}
renderMenuItem(item: ColumnFilterItem) {
const { column } = this.props;
const multiple = ('filterMultiple' in column) ? column.filterMultiple : true;
const input = multiple ? (
<Checkbox checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
) : (
<Radio checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
);
return (
<MenuItem key={item.value}>
{input}
<span>{item.text}</span>
</MenuItem>
);
}
hasSubMenu() {
const { column: { filters = [] } } = this.props;
return filters.some(item => !!(item.children && item.children.length > 0));
}
renderMenus(items: ColumnFilterItem[]): React.ReactElement<any>[] {
return items.map(item => {
if (item.children && item.children.length > 0) {
const { keyPathOfSelectedItem } = this.state;
const containSelected = Object.keys(keyPathOfSelectedItem).some(
key => keyPathOfSelectedItem[key].indexOf(item.value) >= 0,
);
const subMenuCls = containSelected ? `${this.props.dropdownPrefixCls}-submenu-contain-selected` : '';
return (
<SubMenu title={item.text} className={subMenuCls} key={item.value.toString()}>
{this.renderMenus(item.children)}
</SubMenu>
);
}
return this.renderMenuItem(item);
});
}
handleMenuItemClick = (info: { keyPath: string, key: string }) => {
if (info.keyPath.length <= 1) {
return;
}
const keyPathOfSelectedItem = this.state.keyPathOfSelectedItem;
if (this.state.selectedKeys.indexOf(info.key) >= 0) {
// deselect SubMenu child
delete keyPathOfSelectedItem[info.key];
} else {
// select SubMenu child
keyPathOfSelectedItem[info.key] = info.keyPath;
}
this.setState({ keyPathOfSelectedItem });
}
renderFilterIcon = () => {
const { column, locale, prefixCls } = this.props;
const filterIcon = column.filterIcon as any;
const dropdownSelectedClass = this.props.selectedKeys.length > 0 ? `${prefixCls}-selected` : '';
return filterIcon ? React.cloneElement(filterIcon as any, {
title: locale.filterTitle,
className: classNames(filterIcon.className, {
[`${prefixCls}-icon`]: true,
}),
}) : <Icon title={locale.filterTitle} type="filter" className={dropdownSelectedClass} />;
}
render() {
const { column, locale, prefixCls, dropdownPrefixCls, getPopupContainer } = this.props;
// default multiple selection in filter dropdown
const multiple = ('filterMultiple' in column) ? column.filterMultiple : true;
const dropdownMenuClass = classNames({
[`${dropdownPrefixCls}-menu-without-submenu`]: !this.hasSubMenu(),
});
const menus = column.filterDropdown ? (
<FilterDropdownMenuWrapper>
{column.filterDropdown}
</FilterDropdownMenuWrapper>
) : (
<FilterDropdownMenuWrapper className={`${prefixCls}-dropdown`}>
<Menu
multiple={multiple}
onClick={this.handleMenuItemClick}
prefixCls={`${dropdownPrefixCls}-menu`}
className={dropdownMenuClass}
onSelect={this.setSelectedKeys}
onDeselect={this.setSelectedKeys}
selectedKeys={this.state.selectedKeys}
>
{this.renderMenus(column.filters!)}
</Menu>
<div className={`${prefixCls}-dropdown-btns`}>
<a
className={`${prefixCls}-dropdown-link confirm`}
onClick={this.handleConfirm}
>
{locale.filterConfirm}
</a>
<a
className={`${prefixCls}-dropdown-link clear`}
onClick={this.handleClearFilters}
>
{locale.filterReset}
</a>
</div>
</FilterDropdownMenuWrapper>
);
return (
<Dropdown
trigger={['click']}
overlay={menus}
visible={this.neverShown ? false : this.state.visible}
onVisibleChange={this.onVisibleChange}
getPopupContainer={getPopupContainer}
forceRender
>
{this.renderFilterIcon()}
</Dropdown>
);
}
}

210
components/table/index.en-US.md

@ -0,0 +1,210 @@
---
category: Components
cols: 1
type: Data Display
title: Table
---
A table displays rows of data.
## When To Use
- To display a collection of structured data.
- To sort, search, paginate, filter data.
## How To Use
Specify `dataSource` of Table as an array of data.
```jsx
const dataSource = [{
key: '1',
name: 'Mike',
age: 32,
address: '10 Downing Street'
}, {
key: '2',
name: 'John',
age: 42,
address: '10 Downing Street'
}];
const columns = [{
title: 'Name',
dataIndex: 'name',
key: 'name',
}, {
title: 'Age',
dataIndex: 'age',
key: 'age',
}, {
title: 'Address',
dataIndex: 'address',
key: 'address',
}];
<Table dataSource={dataSource} columns={columns} />
```
## API
### Table
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| bordered | Whether to show all table borders | boolean | `false` |
| columns | Columns of table | [ColumnProps](https://git.io/vMMXC)\[] | - |
| components | Override default table elements | object | - |
| dataSource | Data record array to be displayed | any\[] | - |
| defaultExpandAllRows | Expand all rows initially | boolean | `false` |
| defaultExpandedRowKeys | Initial expanded row keys | string\[] | - |
| expandedRowKeys | Current expanded row keys | string\[] | - |
| expandedRowRender | Expanded container render for each row | Function(record):ReactNode | - |
| expandRowByClick | Whether to expand row by clicking anywhere in the whole row | boolean | `false` |
| footer | Table footer renderer | Function(currentPageData) | |
| indentSize | Indent size in pixels of tree data | number | 15 |
| loading | Loading status of table | boolean\|[object](https://ant.design/components/spin-cn/#API) ([more](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | `false` |
| locale | i18n text including filter, sort, empty text, etc | object | filterConfirm: 'Ok' <br> filterReset: 'Reset' <br> emptyText: 'No Data' <br> [Default](https://github.com/ant-design/ant-design/issues/575#issuecomment-159169511) |
| pagination | Pagination [config](/components/pagination/), hide it by setting it to `false` | object | |
| rowClassName | Row's className | Function(record, index):string | - |
| rowKey | Row's unique key, could be a string or function that returns a string | string\|Function(record):string | `key` |
| rowSelection | Row selection [config](#rowSelection) | object | null |
| scroll | Whether table can be scrolled in x/y direction, `x` or `y` can be a number that indicates the width and height of table body | object | - |
| showHeader | Whether to show table header | boolean | `true` |
| size | Size of table | `default` \| `middle` \| `small` | `default` |
| title | Table title renderer | Function(currentPageData) | |
| onChange | Callback executed when pagination, filters or sorter is changed | Function(pagination, filters, sorter) | |
| onExpand | Callback executed when the row expand icon is clicked | Function(expanded, record) | |
| onExpandedRowsChange | Callback executed when the expanded rows change | Function(expandedRows) | |
| onHeaderRow | Set props on per header row | Function(column, index) | - |
| onRow | Set props on per row | Function(record, index) | - |
#### onRow usage
Same as `onRow` `onHeaderRow` `onCell` `onHeaderCell`
```jsx
<Table
onRow={(record) => {
return {
onClick: () => {}, // click row
onMouseEnter: () => {}, // mouse enter row
onXxxx...
};
)}
onHeaderRow={(column) => {
return {
onClick: () => {}, // click header row
};
)}
/>
```
### Column
One of the Table `columns` prop for describing the table's columns, Column has the same API.
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| className | className of this column | string | - |
| colSpan | Span of this column's title | number | |
| dataIndex | Display field of the data record, could be set like `a.b.c` | string | - |
| defaultSortOrder | Default order of sorted values: `'ascend'` `'descend'` `null` | string | - |
| filterDropdown | Customized filter overlay | ReactNode | - |
| filterDropdownVisible | Whether `filterDropdown` is visible | boolean | - |
| filtered | Whether the `dataSource` is filtered | boolean | `false` |
| filteredValue | Controlled filtered value, filter icon will highlight | string\[] | - |
| filterIcon | Customized filter icon | ReactNode | `false` |
| filterMultiple | Whether multiple filters can be selected | boolean | `true` |
| filters | Filter menu config | object\[] | - |
| fixed | Set column to be fixed: `true`(same as left) `'left'` `'right'` | boolean\|string | `false` |
| key | Unique key of this column, you can ignore this prop if you've set a unique `dataIndex` | string | - |
| render | Renderer of the table cell. The return value should be a ReactNode, or an object for [colSpan/rowSpan config](#components-table-demo-colspan-rowspan) | Function(text, record, index) {} | - |
| sorter | Sort function for local sort, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction. If you need sort buttons only, set to `true` | Function\|boolean | - |
| sortOrder | Order of sorted values: `'ascend'` `'descend'` `false` | boolean\|string | - |
| title | Title of this column | string\|ReactNode | - |
| width | Width of this column | string\|number | - |
| onCell | Set props on per cell | Function(record) | - |
| onFilter | Callback executed when the confirm filter button is clicked | Function | - |
| onFilterDropdownVisibleChange | Callback executed when `filterDropdownVisible` is changed | function(visible) {} | - |
| onHeaderCell | Set props on per header cell | Function(column) | - |
### ColumnGroup
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| title | Title of the column group | string\|ReactNode | - |
### rowSelection
Properties for row selection.
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| fixed | Fixed selection column on the left | boolean | - |
| getCheckboxProps | Get Checkbox or Radio props | Function(record) | - |
| hideDefaultSelections | Remove the default `Select All` and `Select Invert` selections | boolean | `false` |
| selectedRowKeys | Controlled selected row keys | string\[] | \[] |
| selections | Custom selection [config](#rowSelection), only displays default selections when set to `true` | object\[]\|boolean | - |
| type | `checkbox` or `radio` | `checkbox` \| `radio` | `checkbox` |
| onChange | Callback executed when selected rows change | Function(selectedRowKeys, selectedRows) | - |
| onSelect | Callback executed when select/deselect one row | Function(record, selected, selectedRows) | - |
| onSelectAll | Callback executed when select/deselect all rows | Function(selected, selectedRows, changeRows) | - |
| onSelectInvert | Callback executed when row selection is inverted | Function(selectedRows) | - |
### selection
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| key | Unique key of this selection | string | - |
| text | Display text of this selection | string\|React.ReactNode | - |
| onSelect | Callback executed when this selection is clicked | Function(changeableRowKeys) | - |
## Using in TypeScript
```jsx
import { Table } from 'antd';
import { ColumnProps } from 'antd/lib/table';
interface IUser {
key: number,
name: string;
}
const columns: ColumnProps<IUser>[] = [{
key: 'name',
title: 'Name',
dataIndex: 'name',
}];
const data: IUser[] = [{
key: 0,
name: 'Jack',
}];
class UserTable extends Table<IUser> {}
<UserTable columns={columns} dataSource={data} />
// Use JSX style API
class NameColumn extends Table.Column<IUser> {}
<UserTable dataSource={data}>
<NameColumn key="name" title="Name" dataIndex="name" />
</UserTable>
```
## Note
According to [React documentation](https://facebook.github.io/react/docs/lists-and-keys.html#keys), every child in array should be assigned a unique key. The values inside `dataSource` and `columns` should follow this in Table, and `dataSource[i].key` would be treated as key value default for `dataSource`.
If `dataSource[i].key` is not provided, then you should specify the primary key of dataSource value via `rowKey`. If not, warnings like above will show in browser console.
![](https://os.alipayobjects.com/rmsportal/luLdLvhPOiRpyss.png)
```jsx
// primary key is uid
return <Table rowKey="uid" />;
// or
return <Table rowKey={record => record.uid} />;
```

5
components/table/index.jsx

@ -0,0 +1,5 @@
import Table from './Table'
export * from './interface'
export default Table

210
components/table/index.zh-CN.md

@ -0,0 +1,210 @@
---
category: Components
cols: 1
type: Data Display
title: Table
subtitle: 表格
---
展示行列数据。
## 何时使用
- 当有大量结构化的数据需要展现时;
- 当需要对数据进行排序、搜索、分页、自定义操作等复杂行为时。
## 如何使用
指定表格的数据源 `dataSource` 为一个数组。
```jsx
const dataSource = [{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号'
}, {
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号'
}];
const columns = [{
title: '姓名',
dataIndex: 'name',
key: 'name',
}, {
title: '年龄',
dataIndex: 'age',
key: 'age',
}, {
title: '住址',
dataIndex: 'address',
key: 'address',
}];
<Table dataSource={dataSource} columns={columns} />
```
## API
### Table
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| bordered | 是否展示外边框和列边框 | boolean | false |
| columns | 表格列的配置描述,具体项见下表 | [ColumnProps](https://git.io/vMMXC)\[] | - |
| components | 覆盖默认的 table 元素 | object | - |
| dataSource | 数据数组 | any\[] | |
| defaultExpandAllRows | 初始时,是否展开所有行 | boolean | false |
| defaultExpandedRowKeys | 默认展开的行 | string\[] | - |
| expandedRowKeys | 展开的行,控制属性 | string\[] | - |
| expandedRowRender | 额外的展开行 | Function(record):ReactNode | - |
| expandRowByClick | 通过点击行来展开子行 | boolean | `false` |
| footer | 表格尾部 | Function(currentPageData) | |
| indentSize | 展示树形数据时,每层缩进的宽度,以 px 为单位 | number | 15 |
| loading | 页面是否加载中 | boolean\|[object](https://ant.design/components/spin-cn/#API) ([更多](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | false |
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: '确定' <br> filterReset: '重置' <br> emptyText: '暂无数据' <br> [默认值](https://github.com/ant-design/ant-design/issues/575#issuecomment-159169511) |
| pagination | 分页器,配置项参考 [pagination](/components/pagination/),设为 false 时不展示和进行分页 | object | |
| rowClassName | 表格行的类名 | Function(record, index):string | - |
| rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string\|Function(record):string | 'key' |
| rowSelection | 列表项是否可选择,[配置项](#rowSelection) | object | null |
| scroll | 横向或纵向支持滚动,也可用于指定滚动区域的宽高度:`{{ x: true, y: 300 }}` | object | - |
| showHeader | 是否显示表头 | boolean | true |
| size | 正常或迷你类型,`default` or `small` | string | default |
| title | 表格标题 | Function(currentPageData) | |
| onChange | 分页、排序、筛选变化时触发 | Function(pagination, filters, sorter) | |
| onExpand | 点击展开图标时触发 | Function(expanded, record) | |
| onExpandedRowsChange | 展开的行变化时触发 | Function(expandedRows) | |
| onHeaderRow | 设置头部行属性 | Function(column, index) | - |
| onRow | 设置行属性 | Function(record, index) | - |
#### onRow 用法
适用于 `onRow` `onHeaderRow` `onCell` `onHeaderCell`
```jsx
<Table
onRow={(record) => {
return {
onClick: () => {}, // 点击行
onMouseEnter: () => {}, // 鼠标移入行
onXxxx...
};
)}
onHeaderRow={(column) => {
return {
onClick: () => {}, // 点击表头行
};
)}
/>
```
### Column
列描述数据对象,是 columns 中的一项,Column 使用相同的 API。
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| className | 列的 className | string | - |
| colSpan | 表头列合并,设置为 0 时,不渲染 | number | |
| dataIndex | 列数据在数据项中对应的 key,支持 `a.b.c` 的嵌套写法 | string | - |
| filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | ReactNode | - |
| filterDropdownVisible | 用于控制自定义筛选菜单是否可见 | boolean | - |
| filtered | 标识数据是否经过过滤,筛选图标会高亮 | boolean | false |
| filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string\[] | - |
| filterIcon | 自定义 fiter 图标。 | ReactNode | false |
| filterMultiple | 是否多选 | boolean | true |
| filters | 表头的筛选菜单项 | object\[] | - |
| fixed | 列是否固定,可选 `true`(等效于 left) `'left'` `'right'` | boolean\|string | false |
| key | React 需要的 key,如果已经设置了唯一的 `dataIndex`,可以忽略这个属性 | string | - |
| render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引,@return里面可以设置表格[行/列合并](#components-table-demo-colspan-rowspan) | Function(text, record, index) {} | - |
| sorter | 排序函数,本地排序使用一个函数(参考 [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 的 compareFunction),需要服务端排序可设为 true | Function\|boolean | - |
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `false` | boolean\|string | - |
| title | 列头显示文字 | string\|ReactNode | - |
| width | 列宽度 | string\|number | - |
| onCell | 设置单元格属性 | Function(record) | - |
| onFilter | 本地模式下,确定筛选的运行函数 | Function | - |
| onFilterDropdownVisibleChange | 自定义筛选菜单可见变化时调用 | function(visible) {} | - |
| onHeaderCell | 设置头部单元格属性 | Function(column) | - |
### ColumnGroup
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| title | 列头显示文字 | string\|ReactNode | - |
### rowSelection
选择功能的配置。
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| fixed | 把选择框列固定在左边 | boolean | - |
| getCheckboxProps | 选择框的默认属性配置 | Function(record) | - |
| hideDefaultSelections | 去掉『全选』『反选』两个默认选项 | boolean | false |
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 | string\[] | \[] |
| selections | 自定义选择项 [配置项](#selection), 设为 `true` 时使用默认选择项 | object\[]\|boolean | true |
| type | 多选/单选,`checkbox` or `radio` | string | `checkbox` |
| onChange | 选中项发生变化的时的回调 | Function(selectedRowKeys, selectedRows) | - |
| onSelect | 用户手动选择/取消选择某列的回调 | Function(record, selected, selectedRows) | - |
| onSelectAll | 用户手动选择/取消选择所有列的回调 | Function(selected, selectedRows, changeRows) | - |
| onSelectInvert | 用户手动选择反选的回调 | Function(selectedRows) | - |
### selection
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| key | React 需要的 key,建议设置 | string | - |
| text | 选择项显示的文字 | string\|React.ReactNode | - |
| onSelect | 选择项点击回调 | Function(changeableRowKeys) | - |
## 在 TypeScript 中使用
```jsx
import { Table } from 'antd';
import { ColumnProps } from 'antd/lib/table';
interface IUser {
key: number;
name: string;
}
const columns: ColumnProps<IUser>[] = [{
key: 'name',
title: 'Name',
dataIndex: 'name',
}];
const data: IUser[] = [{
key: 0,
name: 'Jack',
}];
class UserTable extends Table<IUser> {}
<UserTable columns={columns} dataSource={data} />
// 使用 JSX 风格的 API
class NameColumn extends Table.Column<IUser> {}
<UserTable dataSource={data}>
<NameColumn key="name" title="Name" dataIndex="name" />
</UserTable>
```
## 注意
按照 [React 的规范](https://facebook.github.io/react/docs/lists-and-keys.html#keys),所有的组件数组必须绑定 key。在 Table 中,`dataSource` 和 `columns` 里的数据值都需要指定 `key` 值。对于 `dataSource` 默认将每列数据的 `key` 属性作为唯一的标识。
如果你的数据没有这个属性,务必使用 `rowKey` 来指定数据列的主键。若没有指定,控制台会出现以下的提示,表格组件也会出现各类奇怪的错误。
![](https://os.alipayobjects.com/rmsportal/luLdLvhPOiRpyss.png)
```jsx
// 比如你的数据主键是 uid
return <Table rowKey="uid" />;
// 或
return <Table rowKey={record => record.uid} />;
```

186
components/table/interface.js

@ -0,0 +1,186 @@
import PropTypes from '../_util/vue-types'
import { PaginationProps as getPaginationProps } from '../pagination'
import { SpinProps as getSpinProps } from '../spin'
import { Store } from './createStore'
const PaginationProps = getPaginationProps()
const SpinProps = getSpinProps()
// export type CompareFn<T> = ((a: T, b: T) => number);
export const ColumnFilterItem = PropTypes.shape({
text: PropTypes.string,
value: PropTypes.string,
children: PropTypes.array,
}).loose
export const ColumnProps = {
title: PropTypes.any,
// key?: React.Key;
dataIndex: PropTypes.string,
render: PropTypes.func,
filters: PropTypes.arrayOf(ColumnFilterItem),
// onFilter: (value: any, record: T) => PropTypes.bool,
filterMultiple: PropTypes.bool,
filterDropdown: PropTypes.any,
filterDropdownVisible: PropTypes.bool,
// onFilterDropdownVisibleChange?: (visible: boolean) => void;
sorter: PropTypes.oneOfType([PropTypes.boolean, PropTypes.func]),
defaultSortOrder: PropTypes.oneOf(['ascend', 'descend']),
colSpan: PropTypes.number,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
// className: string,
fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['left', 'right'])]),
filterIcon: PropTypes.any,
filteredValue: PropTypes.array,
sortOrder: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['ascend', 'descend'])]),
// children?: ColumnProps<T>[];
// onCellClick?: (record: T, event: any) => void;
// onCell?: (record: T) => any;
// onHeaderCell?: (props: ColumnProps<T>) => any;
}
// export interface TableComponents {
// table?: any;
// header?: {
// wrapper?: any;
// row?: any;
// cell?: any;
// };
// body?: {
// wrapper?: any;
// row?: any;
// cell?: any;
// };
// }
export const TableLocale = PropTypes.shape({
filterTitle: PropTypes.string,
filterConfirm: PropTypes.any,
filterReset: PropTypes.any,
emptyText: PropTypes.any,
selectAll: PropTypes.any,
selectInvert: PropTypes.any,
}).loose
export const RowSelectionType = PropTypes.oneOf(['checkbox', 'radio'])
// export type SelectionSelectFn<T> = (record: T, selected: boolean, selectedRows: Object[]) => any;
export const TableRowSelection = {
type: RowSelectionType,
selectedRowKeys: PropTypes.array,
// onChange?: (selectedRowKeys: string[] | number[], selectedRows: Object[]) => any;
getCheckboxProps: PropTypes.func,
// onSelect?: SelectionSelectFn<T>;
// onSelectAll?: (selected: boolean, selectedRows: Object[], changeRows: Object[]) => any;
// onSelectInvert?: (selectedRows: Object[]) => any;
selections: PropTypes.oneOfType([PropTypes.array, PropTypes.bool]),
hideDefaultSelections: PropTypes.bool,
fixed: PropTypes.bool,
}
export const TableProps = {
prefixCls: PropTypes.string,
dropdownPrefixCls: PropTypes.string,
rowSelection: PropTypes.shape(TableRowSelection).loose,
pagination: PropTypes.oneOfType([PropTypes.shape(PaginationProps).loose, PropTypes.bool]),
size: PropTypes.oneOf(['default', 'middle', 'small']),
dataSource: PropTypes.array,
components: PropTypes.object,
columns: PropTypes.array,
rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
rowClassName: PropTypes.func,
expandedRowRender: PropTypes.any,
defaultExpandAllRows: PropTypes.bool,
defaultExpandedRowKeys: PropTypes.array,
expandedRowKeys: PropTypes.array,
expandIconAsCell: PropTypes.bool,
expandIconColumnIndex: PropTypes.number,
expandRowByClick: PropTypes.bool,
// onExpandedRowsChange?: (expandedRowKeys: string[] | number[]) => void;
// onExpand?: (expanded: boolean, record: T) => void;
// onChange?: (pagination: PaginationProps | boolean, filters: string[], sorter: Object) => any;
loading: PropTypes.oneOfType([PropTypes.shape(SpinProps).loose, PropTypes.bool]),
locale: PropTypes.object,
indentSize: PropTypes.number,
// onRowClick?: (record: T, index: number, event: Event) => any;
// onRow?: (record: T, index: number) => any;
useFixedHeader: PropTypes.bool,
bordered: PropTypes.bool,
showHeader: PropTypes.bool,
footer: PropTypes.any,
title: PropTypes.any,
scroll: PropTypes.object,
childrenColumnName: PropTypes.string,
bodyStyle: PropTypes.any,
// className?: PropTypes.string,
// style?: React.CSSProperties;
// children?: React.ReactNode;
}
// export interface TableStateFilters {
// [key: string]: string[];
// }
// export interface TableState<T> {
// pagination: PaginationProps;
// filters: TableStateFilters;
// sortColumn: ColumnProps<T> | null;
// sortOrder: PropTypes.string,
// }
// export type SelectionItemSelectFn = (key: string[]) => any;
// export interface SelectionItem {
// key: PropTypes.string,
// text: PropTypes.any,
// onSelect: SelectionItemSelectFn;
// }
export const SelectionCheckboxAllProps = {
store: Store,
locale: PropTypes.any,
disabled: PropTypes.bool,
getCheckboxPropsByItem: PropTypes.func,
getRecordKey: PropTypes.func,
data: PropTypes.array,
prefixCls: PropTypes.string,
// onSelect: (key: string, index: number, selectFunc: any) => void;
hideDefaultSelections: PropTypes.bool,
selections: PropTypes.oneOfType([PropTypes.array, PropTypes.bool]),
getPopupContainer: PropTypes.func,
}
// export interface SelectionCheckboxAllState {
// checked: PropTypes.bool,
// indeterminate: PropTypes.bool,
// }
export const SelectionBoxProps = {
store: Store,
type: RowSelectionType,
defaultSelection: PropTypes.arrayOf(PropTypes.string),
rowIndex: PropTypes.string,
name: PropTypes.string,
disabled: PropTypes.bool,
// onChange: React.ChangeEventHandler<HTMLInputElement>;
}
// export interface SelectionBoxState {
// checked?: PropTypes.bool,
// }
export const FilterMenuProps = {
locale: TableLocale,
selectedKeys: PropTypes.arrayOf(PropTypes.string),
column: PropTypes.shape(ColumnProps),
confirmFilter: PropTypes.func,
prefixCls: PropTypes.string,
dropdownPrefixCls: PropTypes.string,
getPopupContainer: PropTypes.func,
}
// export interface FilterMenuState {
// selectedKeys: string[];
// keyPathOfSelectedItem: { [key: string]: string };
// visible?: PropTypes.bool,
// }

9
components/table/style/index.js

@ -0,0 +1,9 @@
import '../../style/index.less'
import './index.less'
// style dependencies
import '../../radio/style'
import '../../checkbox/style'
import '../../dropdown/style'
import '../../spin/style'
import '../../pagination/style'

568
components/table/style/index.less

@ -0,0 +1,568 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@table-prefix-cls: ~"@{ant-prefix}-table";
@table-header-icon-color: @text-color-secondary;
.@{table-prefix-cls}-wrapper {
.clearfix;
}
.@{table-prefix-cls} {
.reset-component;
position: relative;
border-radius: @border-radius-base @border-radius-base 0 0;
&-body {
transition: opacity .3s;
}
table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
text-align: left;
border-radius: @border-radius-base @border-radius-base 0 0;
}
&-thead > tr > th {
background: @table-header-bg;
transition: background .3s ease;
text-align: left;
color: @heading-color;
font-weight: 500;
border-bottom: @border-width-base @border-style-base @border-color-split;
&[colspan] {
text-align: center;
border-bottom: 0;
}
.@{iconfont-css-prefix}-filter,
.@{table-prefix-cls}-filter-icon {
position: relative;
margin-left: 8px;
font-size: @font-size-base;
cursor: pointer;
color: @table-header-icon-color;
transition: all .3s;
width: 14px;
font-weight: normal;
vertical-align: text-bottom;
&:hover {
color: @text-color;
}
}
.@{table-prefix-cls}-column-sorter + .@{iconfont-css-prefix}-filter {
margin-left: 4px;
}
.@{table-prefix-cls}-filter-selected.@{iconfont-css-prefix}-filter {
color: @primary-color;
}
// https://github.com/ant-design/ant-design/issues/8979
&.@{table-prefix-cls}-column-has-filters {
overflow: hidden;
}
}
&-tbody > tr > td {
border-bottom: @border-width-base @border-style-base @border-color-split;
transition: all .3s;
}
&-thead > tr,
&-tbody > tr {
transition: all .3s;
&.@{table-prefix-cls}-row-hover,
&:hover {
& > td {
background: @table-row-hover-bg;
}
}
}
&-thead > tr:hover {
background: none;
}
&-footer {
padding: @table-padding-vertical @table-padding-horizontal;
background: @table-header-bg;
border-radius: 0 0 @border-radius-base @border-radius-base;
position: relative;
border-top: @border-width-base @border-style-base @border-color-split;
&:before {
content: '';
height: 1px;
background: @table-header-bg;
position: absolute;
top: -1px;
width: 100%;
left: 0;
}
}
&.@{table-prefix-cls}-bordered &-footer {
border: @border-width-base @border-style-base @border-color-split;
}
&-title {
padding: @table-padding-vertical 0;
position: relative;
top: 1px;
border-radius: @border-radius-base @border-radius-base 0 0;
}
&.@{table-prefix-cls}-bordered &-title {
border: @border-width-base @border-style-base @border-color-split;
padding-left: @table-padding-horizontal;
padding-right: @table-padding-horizontal;
}
&-title + &-content {
position: relative;
border-radius: @border-radius-base @border-radius-base 0 0;
overflow: hidden;
.@{table-prefix-cls}-bordered & {
&,
table {
border-radius: 0;
}
}
}
// https://github.com/ant-design/ant-design/issues/4373
&-without-column-header &-title + &-content,
&-without-column-header table {
border-radius: 0;
}
&-tbody > tr.@{table-prefix-cls}-row-selected td {
background: @table-selected-row-bg;
}
&-thead > tr > th.@{table-prefix-cls}-column-sort {
background: @table-header-sort-bg;
}
&-thead > tr > th,
&-tbody > tr > td {
padding: @table-padding-vertical @table-padding-horizontal;
word-break: break-all;
}
&-thead > tr > th.@{table-prefix-cls}-selection-column-custom {
padding-left: 16px;
padding-right: 0;
}
&-thead > tr > th.@{table-prefix-cls}-selection-column,
&-tbody > tr > td.@{table-prefix-cls}-selection-column {
text-align: center;
min-width: 62px;
width: 62px;
.@{ant-prefix}-radio-wrapper {
margin-right: 0;
}
}
&-expand-icon-th,
&-row-expand-icon-cell {
text-align: center;
min-width: 50px;
width: 50px;
}
&-header {
background: @table-header-bg;
overflow: hidden;
}
&-header table {
border-radius: @border-radius-base @border-radius-base 0 0;
}
&-loading {
position: relative;
.@{table-prefix-cls}-body {
background: @component-background;
opacity: 0.5;
}
.@{table-prefix-cls}-spin-holder {
height: 20px;
line-height: 20px;
left: 50%;
top: 50%;
margin-left: -30px;
position: absolute;
}
.@{table-prefix-cls}-with-pagination {
margin-top: -20px;
}
.@{table-prefix-cls}-without-pagination {
margin-top: 10px;
}
}
&-column-sorter {
position: relative;
margin-left: 8px;
display: inline-block;
width: 14px;
height: 14px;
vertical-align: middle;
text-align: center;
font-weight: normal;
color: @table-header-icon-color;
&-up,
&-down {
line-height: 6px;
display: block;
width: 14px;
height: 6px;
cursor: pointer;
position: relative;
&:hover .@{iconfont-css-prefix} {
color: @primary-4;
}
&.on {
.@{iconfont-css-prefix}-caret-up,
.@{iconfont-css-prefix}-caret-down {
color: @primary-color;
}
}
&:after {
position: absolute;
content: '';
height: 30px;
width: 14px;
left: 0;
}
}
&-up:after {
bottom: -2px;
}
&-down:after {
top: 2px;
}
.@{iconfont-css-prefix}-caret-up,
.@{iconfont-css-prefix}-caret-down {
.iconfont-size-under-12px(8px);
line-height: 4px;
height: 4px;
transition: all .3s;
}
}
&-bordered {
.@{table-prefix-cls}-header > table,
.@{table-prefix-cls}-body > table,
.@{table-prefix-cls}-fixed-left table,
.@{table-prefix-cls}-fixed-right table {
border: @border-width-base @border-style-base @border-color-split;
border-right: 0;
border-bottom: 0;
}
&.@{table-prefix-cls}-empty {
.@{table-prefix-cls}-placeholder {
border-left: @border-width-base @border-style-base @border-color-split;
border-right: @border-width-base @border-style-base @border-color-split;
}
}
&.@{table-prefix-cls}-fixed-header {
.@{table-prefix-cls}-header > table {
border-bottom: 0;
}
.@{table-prefix-cls}-body > table {
border-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.@{table-prefix-cls}-body-inner > table {
border-top: 0;
}
.@{table-prefix-cls}-placeholder {
border: 0;
}
}
.@{table-prefix-cls}-thead > tr > th {
border-bottom: @border-width-base @border-style-base @border-color-split;
}
.@{table-prefix-cls}-thead > tr > th,
.@{table-prefix-cls}-tbody > tr > td {
border-right: @border-width-base @border-style-base @border-color-split;
}
}
&-placeholder {
position: relative;
padding: @table-padding-vertical @table-padding-horizontal;
background: @component-background;
border-bottom: @border-width-base @border-style-base @border-color-split;
text-align: center;
font-size: @font-size-base;
color: @text-color-secondary;
z-index: 1;
.@{iconfont-css-prefix} {
margin-right: 4px;
}
}
&-pagination.@{ant-prefix}-pagination {
margin: 16px 0;
float: right;
}
&-filter-dropdown {
min-width: 96px;
margin-left: -8px;
background: @component-background;
border-radius: @border-radius-base;
box-shadow: @box-shadow-base;
.@{ant-prefix}-dropdown-menu {
border: 0;
box-shadow: none;
border-radius: @border-radius-base @border-radius-base 0 0;
// https://github.com/ant-design/ant-design/issues/4916
&-without-submenu {
max-height: 400px;
overflow-x: hidden;
}
&-item > label + span {
padding-right: 0;
}
&-sub {
border-radius: @border-radius-base;
box-shadow: @box-shadow-base;
}
.@{ant-prefix}-dropdown-submenu-contain-selected {
.@{ant-prefix}-dropdown-menu-submenu-title:after {
color: @primary-color;
font-weight: bold;
text-shadow: 0 0 2px @primary-2;
}
}
}
.@{ant-prefix}-dropdown-menu-item {
overflow: hidden;
}
> .@{ant-prefix}-dropdown-menu > .@{ant-prefix}-dropdown-menu-item:last-child,
> .@{ant-prefix}-dropdown-menu > .@{ant-prefix}-dropdown-menu-submenu:last-child .@{ant-prefix}-dropdown-menu-submenu-title {
border-radius: 0;
}
&-btns {
overflow: hidden;
padding: 7px 8px;
border-top: @border-width-base @border-style-base @border-color-split;
}
&-link {
color: @link-color;
&:hover {
color: @link-hover-color;
}
&:active {
color: @link-active-color;
}
&.confirm {
float: left;
}
&.clear {
float: right;
}
}
}
&-selection {
&-select-all-custom {
margin-right: 4px !important;
}
.@{iconfont-css-prefix}-down {
color: @table-header-icon-color;
transition: all .3s;
}
&-menu {
min-width: 96px;
margin-top: 5px;
margin-left: -30px;
background: @component-background;
border-radius: @border-radius-base;
box-shadow: @box-shadow-base;
.@{ant-prefix}-action-down {
color: @table-header-icon-color;
}
}
&-down {
cursor: pointer;
padding: 0;
display: inline-block;
line-height: 1;
&:hover .@{iconfont-css-prefix}-down {
color: #666;
}
}
}
&-row {
&-expand-icon {
cursor: pointer;
display: inline-block;
width: 17px;
height: 17px;
text-align: center;
line-height: 14px;
border: @border-width-base @border-style-base @border-color-split;
user-select: none;
background: @component-background;
}
&-expanded:after {
content: '-';
}
&-collapsed:after {
content: '+';
}
&-spaced {
visibility: hidden;
&:after {
content: '.';
}
}
&[class*="@{table-prefix-cls}-row-level-0"] .@{table-prefix-cls}-selection-column > span {
display: inline-block;
}
}
tr&-expanded-row {
&,
&:hover {
background: #fbfbfb;
}
}
.@{table-prefix-cls}-row-indent + .@{table-prefix-cls}-row-expand-icon {
margin-right: 8px;
}
&-scroll {
overflow: auto;
overflow-x: hidden;
table {
width: auto;
min-width: 100%;
}
}
&-body-inner {
height: 100%;
}
&-fixed-header > &-content > &-scroll > &-body {
position: relative;
background: @component-background;
}
&-fixed-header &-body-inner {
overflow: scroll;
}
&-fixed-header &-scroll &-header {
overflow: scroll;
padding-bottom: 20px;
margin-bottom: -20px;
}
&-fixed-left,
&-fixed-right {
position: absolute;
top: 0;
overflow: hidden;
transition: box-shadow .3s ease;
border-radius: 0;
table {
width: auto;
background: @component-background;
}
}
&-fixed-header &-fixed-left &-body-outer &-fixed,
&-fixed-header &-fixed-right &-body-outer &-fixed {
border-radius: 0;
}
&-fixed-left {
left: 0;
box-shadow: 6px 0 6px -4px @shadow-color;
.@{table-prefix-cls}-header {
overflow-y: hidden;
}
// hide scrollbar in left fixed columns
.@{table-prefix-cls}-body-inner {
margin-right: -20px;
padding-right: 20px;
}
.@{table-prefix-cls}-fixed-header & .@{table-prefix-cls}-body-inner {
padding-right: 0;
}
&,
table {
border-radius: @border-radius-base 0 0 0;
}
}
&-fixed-right {
right: 0;
box-shadow: -6px 0 6px -4px @shadow-color;
&,
table {
border-radius: 0 @border-radius-base 0 0;
}
// hide expand row content in right-fixed Table
// https://github.com/ant-design/ant-design/issues/1898
.@{table-prefix-cls}-expanded-row {
color: transparent;
pointer-events: none;
}
}
&&-scroll-position-left &-fixed-left {
box-shadow: none;
}
&&-scroll-position-right &-fixed-right {
box-shadow: none;
}
}
@import './size';

109
components/table/style/size.less

@ -0,0 +1,109 @@
.@{table-prefix-cls}-middle {
> .@{table-prefix-cls}-title,
> .@{table-prefix-cls}-footer {
padding: @table-padding-vertical*3/4 @table-padding-horizontal/2;
}
> .@{table-prefix-cls}-content {
> .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-body > table,
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-body > table,
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table,
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table {
> .@{table-prefix-cls}-thead > tr > th,
> .@{table-prefix-cls}-tbody > tr > td {
padding: @table-padding-vertical*3/4 @table-padding-horizontal/2;
}
}
}
}
.@{table-prefix-cls}-small {
border: @border-width-base @border-style-base @border-color-split;
border-radius: @border-radius-base;
> .@{table-prefix-cls}-title,
> .@{table-prefix-cls}-footer {
padding: @table-padding-vertical/2 @table-padding-horizontal/2;
}
> .@{table-prefix-cls}-title {
border-bottom: @border-width-base @border-style-base @border-color-split;
top: 0;
}
> .@{table-prefix-cls}-content {
> .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-body > table,
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-body > table,
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table,
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table {
border: 0;
padding: 0 @table-padding-horizontal/2;
> .@{table-prefix-cls}-thead > tr > th,
> .@{table-prefix-cls}-tbody > tr > td {
padding: @table-padding-vertical/2 @table-padding-horizontal/2;
}
> .@{table-prefix-cls}-thead > tr > th {
background: @component-background;
border-bottom: @border-width-base @border-style-base @border-color-split;
}
}
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-body > table,
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-header > table,
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table,
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table {
padding: 0;
}
.@{table-prefix-cls}-header {
background: @component-background;
}
.@{table-prefix-cls}-placeholder,
.@{table-prefix-cls}-row:last-child td {
border-bottom: 0;
}
}
&.@{table-prefix-cls}-bordered {
border-right: 0;
.@{table-prefix-cls}-title {
border: 0;
border-bottom: @border-width-base @border-style-base @border-color-split;
border-right: @border-width-base @border-style-base @border-color-split;
}
.@{table-prefix-cls}-content {
border-right: @border-width-base @border-style-base @border-color-split;
}
.@{table-prefix-cls}-footer {
border: 0;
border-top: @border-width-base @border-style-base @border-color-split;
border-right: @border-width-base @border-style-base @border-color-split;
&:before {
display: none;
}
}
.@{table-prefix-cls}-placeholder {
border-left: 0;
border-bottom: 0;
}
.@{table-prefix-cls}-thead > tr > th:last-child,
.@{table-prefix-cls}-tbody > tr > td:last-child {
border-right: none;
}
}
}

66
components/table/util.js

@ -0,0 +1,66 @@
export function flatArray (data = [], childrenName = 'children') {
const result = []
const loop = (array) => {
array.forEach(item => {
if (item[childrenName]) {
const newItem = { ...item }
delete newItem[childrenName]
result.push(newItem)
if (item[childrenName].length > 0) {
loop(item[childrenName])
}
} else {
result.push(item)
}
})
}
loop(data)
return result
}
export function treeMap (tree, mapper, childrenName = 'children') {
return tree.map((node, index) => {
const extra = {}
if (node[childrenName]) {
extra[childrenName] = treeMap(node[childrenName], mapper, childrenName)
}
return {
...mapper(node, index),
...extra,
}
})
}
export function flatFilter (tree, callback) {
return tree.reduce((acc, node) => {
if (callback(node)) {
acc.push(node)
}
if (node.children) {
const children = flatFilter(node.children, callback)
acc.push(...children)
}
return acc
}, [])
}
// export function normalizeColumns (elements) {
// const columns = []
// React.Children.forEach(elements, (element) => {
// if (!React.isValidElement(element)) {
// return
// }
// const column = {
// ...element.props,
// }
// if (element.key) {
// column.key = element.key
// }
// if (element.type && element.type.__ANT_TABLE_COLUMN_GROUP) {
// column.children = normalizeColumns(column.children)
// }
// columns.push(column)
// })
// return columns
// }
Loading…
Cancel
Save