diff --git a/examples/react-dnd.html b/examples/react-dnd.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/react-dnd.js b/examples/react-dnd.js
new file mode 100644
index 000000000..886b6f0b0
--- /dev/null
+++ b/examples/react-dnd.js
@@ -0,0 +1,187 @@
+/* eslint-disable no-unused-expressions,new-cap */
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { injectGlobal } from 'styled-components';
+import update from 'immutability-helper';
+import { DragDropContext, DragSource, DropTarget } from 'react-dnd';
+import HTML5Backend from 'react-dnd-html5-backend';
+import Table from 'rc-table';
+import 'rc-table/assets/index.less';
+
+injectGlobal`
+ tr.drop-over-downward td {
+ border-bottom: 2px dashed red;
+ }
+
+ tr.drop-over-upward td {
+ border-top: 2px dashed red;
+ }
+`;
+
+function dragDirection(
+ dragIndex,
+ hoverIndex,
+ initialClientOffset,
+ clientOffset,
+ sourceClientOffset,
+) {
+ const hoverMiddleY = (initialClientOffset.y - sourceClientOffset.y) / 2;
+ const hoverClientY = clientOffset.y - sourceClientOffset.y;
+ if (dragIndex < hoverIndex && hoverClientY > hoverMiddleY) {
+ return 'downward';
+ }
+ if (dragIndex > hoverIndex && hoverClientY < hoverMiddleY) {
+ return 'upward';
+ }
+}
+
+let BodyRow = (props) => {
+ const {
+ isOver,
+ connectDragSource,
+ connectDropTarget,
+ moveRow,
+ dragRow,
+ clientOffset,
+ sourceClientOffset,
+ initialClientOffset,
+ ...restProps,
+ } = props;
+ const style = { cursor: 'move' };
+
+ let className = restProps.className;
+ if (isOver && initialClientOffset) {
+ const direction = dragDirection(
+ dragRow.index,
+ restProps.index,
+ initialClientOffset,
+ clientOffset,
+ sourceClientOffset
+ );
+ if (direction === 'downward') {
+ className += ' drop-over-downward';
+ }
+ if (direction === 'upward') {
+ className += ' drop-over-upward';
+ }
+ }
+
+ return connectDragSource(
+ connectDropTarget(
+
+ )
+ );
+};
+
+const rowSource = {
+ beginDrag(props) {
+ return {
+ index: props.index,
+ };
+ },
+};
+
+const rowTarget = {
+ drop(props, monitor) {
+ const dragIndex = monitor.getItem().index;
+ const hoverIndex = props.index;
+
+ // Don't replace items with themselves
+ if (dragIndex === hoverIndex) {
+ return;
+ }
+
+ // Time to actually perform the action
+ props.moveRow(dragIndex, hoverIndex);
+
+ // Note: we're mutating the monitor item here!
+ // Generally it's better to avoid mutations,
+ // but it's good here for the sake of performance
+ // to avoid expensive index searches.
+ monitor.getItem().index = hoverIndex;
+ },
+};
+
+BodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({
+ connectDropTarget: connect.dropTarget(),
+ isOver: monitor.isOver(),
+ sourceClientOffset: monitor.getSourceClientOffset(),
+}))(
+ DragSource('row', rowSource, (connect, monitor) => ({
+ connectDragSource: connect.dragSource(),
+ dragRow: monitor.getItem(),
+ clientOffset: monitor.getClientOffset(),
+ initialClientOffset: monitor.getInitialClientOffset(),
+ }))(BodyRow)
+);
+
+const columns = [
+ { title: 'title1', dataIndex: 'a', key: 'a', width: 100 },
+ { id: '123', title: 'title2', dataIndex: 'b', key: 'b', width: 100 },
+ { title: 'title3', dataIndex: 'c', key: 'c', width: 200 },
+ {
+ title: 'Operations',
+ dataIndex: '',
+ key: 'd',
+ render() {
+ return Operations;
+ },
+ },
+];
+
+class Demo extends React.Component {
+ state = {
+ data: [
+ { a: '123', key: '1' },
+ { a: 'cdd', b: 'edd', key: '2' },
+ { a: '1333', c: 'eee', d: 2, key: '3' },
+ ],
+ }
+
+ components = {
+ body: {
+ row: BodyRow,
+ },
+ }
+
+ moveRow = (dragIndex, hoverIndex) => {
+ const { data } = this.state;
+ const dragRow = data[dragIndex];
+
+ this.setState(
+ update(this.state, {
+ data: {
+ $splice: [[dragIndex, 1], [hoverIndex, 0, dragRow]],
+ },
+ }),
+ );
+ }
+
+ render() {
+ return (
+ ({
+ index,
+ moveRow: this.moveRow,
+ })}
+ />
+ );
+ }
+}
+
+Demo = DragDropContext(HTML5Backend)(Demo);
+
+ReactDOM.render(
+
+
Integrate with react-dnd
+
+ ,
+ document.getElementById('__react-content')
+);
diff --git a/examples/styled-components.html b/examples/styled-components.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/styled-components.js b/examples/styled-components.js
new file mode 100644
index 000000000..fa453e009
--- /dev/null
+++ b/examples/styled-components.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import styled from 'styled-components';
+import Table from 'rc-table';
+import 'rc-table/assets/index.less';
+
+const columns = [
+ { title: 'title1', dataIndex: 'a', key: 'a', width: 100 },
+ { id: '123', title: 'title2', dataIndex: 'b', key: 'b', width: 100 },
+ { title: 'title3', dataIndex: 'c', key: 'c', width: 200 },
+ {
+ title: 'Operations',
+ dataIndex: '',
+ key: 'd',
+ render() {
+ return Operations;
+ },
+ },
+];
+
+const data = [
+ { a: '123', key: '1' },
+ { a: 'cdd', b: 'edd', key: '2' },
+ { a: '1333', c: 'eee', d: 2, key: '3' },
+];
+
+const BodyRow = styled.tr`
+ &:hover {
+ background: palevioletred !important;
+ }
+`;
+
+const components = {
+ body: {
+ row: BodyRow,
+ },
+};
+
+ReactDOM.render(
+
+
Integrate with styled-components
+
+
,
+ document.getElementById('__react-content')
+);
diff --git a/package.json b/package.json
index e75884906..07a2df6b1 100644
--- a/package.json
+++ b/package.json
@@ -71,6 +71,7 @@
"babel-runtime": "6.x",
"component-classes": "^1.2.6",
"lodash.get": "^4.4.2",
+ "lodash.merge": "^4.6.0",
"mini-store": "^1.0.2",
"prop-types": "^15.5.8",
"rc-util": "^4.0.4",
@@ -82,6 +83,7 @@
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.1",
"enzyme-to-json": "^3.1.2",
+ "immutability-helper": "^2.4.0",
"jest": "^21.2.1",
"pre-commit": "1.x",
"rc-animate": "^2.3.0",
@@ -89,8 +91,12 @@
"rc-menu": "^5.0.11",
"rc-tools": "7.x",
"react": "^16.0.0",
+ "react-dnd": "^2.5.4",
+ "react-dnd-html5-backend": "^2.5.4",
"react-dom": "^16.0.0",
- "react-test-renderer": "^16.0.0"
+ "react-test-renderer": "^16.0.0",
+ "react-virtualized": "^9.12.0",
+ "styled-components": "^2.2.1"
},
"pre-commit": [
"lint"
diff --git a/src/BaseTable.js b/src/BaseTable.js
index 91d3cc867..f4b7c59da 100644
--- a/src/BaseTable.js
+++ b/src/BaseTable.js
@@ -33,7 +33,7 @@ class BaseTable extends React.Component {
renderRows = (renderData, indent, ancestorKeys = []) => {
const { table } = this.context;
- const { columnManager } = table;
+ const { columnManager, components } = table;
const {
prefixCls,
childrenColumnName,
@@ -44,6 +44,7 @@ class BaseTable extends React.Component {
onRowContextMenu,
onRowMouseEnter,
onRowMouseLeave,
+ onRow,
} = table.props;
const { getRowKey, fixed, expander } = this.props;
@@ -95,6 +96,7 @@ class BaseTable extends React.Component {
prefixCls={rowPrefixCls}
childrenColumnName={childrenColumnName}
columns={leafColumns}
+ onRow={onRow}
onRowDoubleClick={onRowDoubleClick}
onRowContextMenu={onRowContextMenu}
onRowMouseEnter={onRowMouseEnter}
@@ -103,6 +105,7 @@ class BaseTable extends React.Component {
rowKey={key}
ancestorKeys={ancestorKeys}
ref={rowRef(record, i, indent)}
+ components={components}
{...expandableRow}
/>
)}
@@ -129,7 +132,9 @@ class BaseTable extends React.Component {
}
render() {
- const { prefixCls, scroll, data, getBodyWrapper } = this.context.table.props;
+ const { table } = this.context;
+ const { components } = table;
+ const { prefixCls, scroll, data, getBodyWrapper } = table.props;
const { expander, tableClassName, hasHead, hasBody, fixed, columns } = this.props;
const tableStyle = {};
@@ -142,16 +147,19 @@ class BaseTable extends React.Component {
}
}
+ const Table = hasBody ? components.table : 'table';
+ const BodyWrapper = components.body.wrapper;
+
return (
-
+
{hasHead && }
{hasBody && getBodyWrapper(
-
+
{this.renderRows(data, 0)}
-
+
)}
-
+
);
}
}
diff --git a/src/ExpandableTable.js b/src/ExpandableTable.js
index 797b49817..9656def33 100644
--- a/src/ExpandableTable.js
+++ b/src/ExpandableTable.js
@@ -111,12 +111,14 @@ class ExpandableTable extends React.Component {
return;
}
- rows[0].unshift({
- key: 'rc-table-expandIconAsCell',
+ const iconColumn = {
+ key: 'rc-table-expand-icon-cell',
className: `${prefixCls}-expand-icon-th`,
title: '',
rowSpan: rows.length,
- });
+ };
+
+ rows[0].unshift({ ...iconColumn, column: iconColumn });
}
renderExpandedRow(content, className, ancestorKeys, fixed) {
@@ -146,6 +148,12 @@ class ExpandableTable extends React.Component {
}
const rowKey = `${ancestorKeys[0]}-extra-row`;
+ const components = {
+ body: {
+ row: 'tr',
+ cell: 'td',
+ },
+ };
return (
);
}
diff --git a/src/Table.js b/src/Table.js
index 36f5700a6..0a511a827 100644
--- a/src/Table.js
+++ b/src/Table.js
@@ -4,6 +4,7 @@ import { debounce, warningOnce } from './utils';
import shallowequal from 'shallowequal';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import { Provider, create } from 'mini-store';
+import merge from 'lodash.merge';
import ColumnManager from './ColumnManager';
import classes from 'component-classes';
import HeadTable from './HeadTable';
@@ -20,6 +21,8 @@ export default class Table extends React.Component {
style: PropTypes.object,
rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
+ onRow: PropTypes.func,
+ onHeaderRow: PropTypes.func,
onRowClick: PropTypes.func,
onRowDoubleClick: PropTypes.func,
onRowContextMenu: PropTypes.func,
@@ -33,11 +36,25 @@ export default class Table extends React.Component {
rowRef: PropTypes.func,
getBodyWrapper: PropTypes.func,
children: PropTypes.node,
+ components: PropTypes.shape({
+ table: PropTypes.any,
+ header: PropTypes.shape({
+ wrapper: PropTypes.any,
+ row: PropTypes.any,
+ cell: PropTypes.any,
+ }),
+ body: PropTypes.shape({
+ wrapper: PropTypes.any,
+ row: PropTypes.any,
+ cell: PropTypes.any,
+ }),
+ }),
...ExpandableTable.PropTypes,
}
static childContextTypes = {
table: PropTypes.any,
+ components: PropTypes.any,
}
static defaultProps = {
@@ -45,6 +62,8 @@ export default class Table extends React.Component {
useFixedHeader: false,
rowKey: 'key',
rowClassName: () => '',
+ onRow() {},
+ onHeaderRow() {},
onRowClick() {},
onRowDoubleClick() {},
onRowContextMenu() {},
@@ -82,6 +101,19 @@ export default class Table extends React.Component {
props: this.props,
columnManager: this.columnManager,
saveRef: this.saveRef,
+ components: merge({
+ table: 'table',
+ header: {
+ wrapper: 'thead',
+ row: 'tr',
+ cell: 'th',
+ },
+ body: {
+ wrapper: 'tbody',
+ row: 'tr',
+ cell: 'td',
+ },
+ }, this.props.components),
},
};
}
diff --git a/src/TableCell.js b/src/TableCell.js
index 797e1e5e5..2619da3da 100644
--- a/src/TableCell.js
+++ b/src/TableCell.js
@@ -11,6 +11,7 @@ export default class TableCell extends React.Component {
indentSize: PropTypes.number,
column: PropTypes.object,
expandIcon: PropTypes.node,
+ component: PropTypes.any,
}
isInvalidRenderCellText(text) {
@@ -26,8 +27,16 @@ export default class TableCell extends React.Component {
}
render() {
- const { record, indentSize, prefixCls, indent,
- index, expandIcon, column } = this.props;
+ const {
+ record,
+ indentSize,
+ prefixCls,
+ indent,
+ index,
+ expandIcon,
+ column,
+ component: BodyCell,
+ } = this.props;
const { dataIndex, render, className = '' } = column;
// We should return undefined if no dataIndex is specified, but in order to
@@ -54,6 +63,10 @@ export default class TableCell extends React.Component {
}
}
+ if (column.onCell) {
+ tdProps = { ...tdProps, ...column.onCell(record) };
+ }
+
// Fix https://github.com/ant-design/ant-design/issues/1202
if (this.isInvalidRenderCellText(text)) {
text = null;
@@ -70,15 +83,15 @@ export default class TableCell extends React.Component {
return null;
}
return (
-
{indentText}
{expandIcon}
{text}
- |
+
);
}
}
diff --git a/src/TableHeader.js b/src/TableHeader.js
index 51f124fcf..5cfcff8f2 100644
--- a/src/TableHeader.js
+++ b/src/TableHeader.js
@@ -16,6 +16,7 @@ function getHeaderRows(columns, currentRow = 0, rows) {
key: column.key,
className: column.className || '',
children: column.title,
+ column,
};
if (column.children) {
getHeaderRows(column.children, currentRow + 1, rows);
@@ -34,7 +35,8 @@ function getHeaderRows(columns, currentRow = 0, rows) {
}
export default function TableHeader(props, { table }) {
- const { prefixCls, showHeader } = table.props;
+ const { components } = table;
+ const { prefixCls, showHeader, onHeaderRow } = table.props;
const { expander, columns, fixed } = props;
if (!showHeader) {
@@ -45,20 +47,25 @@ export default function TableHeader(props, { table }) {
expander.renderExpandIndentCell(rows, fixed);
+ const HeaderWrapper = components.header.wrapper;
+
return (
-
+
{
rows.map((row, index) => (
))
}
-
+
);
}
@@ -66,6 +73,7 @@ TableHeader.propTypes = {
fixed: PropTypes.string,
columns: PropTypes.array.isRequired,
expander: PropTypes.object.isRequired,
+ onHeaderRow: PropTypes.func,
};
TableHeader.contextTypes = {
diff --git a/src/TableHeaderRow.js b/src/TableHeaderRow.js
index 6b2301684..bbf98c7cc 100644
--- a/src/TableHeaderRow.js
+++ b/src/TableHeaderRow.js
@@ -1,13 +1,21 @@
import React from 'react';
import { connect } from 'mini-store';
-function TableHeaderRow({ row, height }) {
- const style = { height };
+function TableHeaderRow({ row, index, height, components, onHeaderRow }) {
+ const HeaderRow = components.header.row;
+ const HeaderCell = components.header.cell;
+ const rowProps = onHeaderRow(row.map(cell => cell.column), index);
+ const customStyle = rowProps ? rowProps.style : {};
+ const style = { height, ...customStyle };
return (
-
- {row.map((cell, i) => | )}
-
+
+ {row.map((cell, i) => {
+ const { column, ...cellProps } = cell;
+ const customProps = column.onHeaderCell ? column.onHeaderCell(column) : {};
+ return ;
+ })}
+
);
}
diff --git a/src/TableRow.js b/src/TableRow.js
index bbb93bb42..a767a3f4f 100644
--- a/src/TableRow.js
+++ b/src/TableRow.js
@@ -1,10 +1,12 @@
import React from 'react';
+import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { connect } from 'mini-store';
import TableCell from './TableCell';
class TableRow extends React.Component {
static propTypes = {
+ onRow: PropTypes.func,
onRowClick: PropTypes.func,
onRowDoubleClick: PropTypes.func,
onRowContextMenu: PropTypes.func,
@@ -36,9 +38,11 @@ class TableRow extends React.Component {
]),
renderExpandIcon: PropTypes.func,
renderExpandIconCell: PropTypes.func,
+ components: PropTypes.any,
}
static defaultProps = {
+ onRow() {},
onRowClick() {},
onRowDoubleClick() {},
onRowContextMenu() {},
@@ -56,9 +60,12 @@ class TableRow extends React.Component {
super(props);
this.shouldRender = props.visible;
+ }
- // avoid creating new object which may fail the sCU.
- this.style = {};
+ componentDidMount() {
+ if (this.shouldRender) {
+ this.saveRowRef();
+ }
}
componentWillReceiveProps(nextProps) {
@@ -67,6 +74,12 @@ class TableRow extends React.Component {
}
}
+ componentDidUpdate() {
+ if (this.shouldRender && !this.rowRef) {
+ this.saveRowRef();
+ }
+ }
+
onRowClick = (event) => {
const { record, index, onRowClick } = this.props;
onRowClick(record, index, event);
@@ -116,12 +129,11 @@ class TableRow extends React.Component {
return this.style;
}
- saveRowRef = (node) => {
- this.rowRef = node;
- if (node) {
- if (!this.props.fixed) {
- this.setHeight();
- }
+ saveRowRef() {
+ this.rowRef = ReactDOM.findDOMNode(this);
+
+ if (!this.props.fixed) {
+ this.setHeight();
}
}
@@ -135,14 +147,21 @@ class TableRow extends React.Component {
columns,
record,
index,
+ onRow,
indent,
indentSize,
hovered,
+ height,
+ visible,
+ components,
hasExpandIcon,
renderExpandIcon,
renderExpandIconCell,
} = this.props;
+ const BodyRow = components.body.row;
+ const BodyCell = components.body.cell;
+
let { className } = this.props;
if (hovered) {
@@ -164,6 +183,7 @@ class TableRow extends React.Component {
column={columns[i]}
key={columns[i].key || columns[i].dataIndex}
expandIcon={hasExpandIcon(i) && renderExpandIcon()}
+ component={BodyCell}
/>
);
}
@@ -171,19 +191,29 @@ class TableRow extends React.Component {
const rowClassName =
`${prefixCls} ${className} ${prefixCls}-level-${indent}`.trim();
+ const rowProps = onRow(record, index);
+ const customStyle = rowProps ? rowProps.style : {};
+ let style = { height };
+
+ if (!visible) {
+ style.display = 'none';
+ }
+
+ style = { ...style, ...customStyle };
+
return (
-
{cells}
-
+
);
}
}
diff --git a/tests/Table.spec.js b/tests/Table.spec.js
index 22aab3717..16fde87f5 100644
--- a/tests/Table.spec.js
+++ b/tests/Table.spec.js
@@ -440,4 +440,100 @@ describe('Table', () => {
}));
expect(wrapper).toMatchSnapshot();
});
+
+ it('renders onRow correctly', () => {
+ const onRow = (record, index) => ({
+ id: `row-${record.key}`,
+ index,
+ });
+ const wrapper = render(createTable({ onRow }));
+
+ expect(wrapper.find('tbody tr')).toMatchSnapshot();
+ });
+
+ it('renders column.onCell correctly', () => {
+ const onCell = (record) => ({
+ id: `cell-${record.name}`,
+ });
+ const columns = [
+ { title: 'Name', dataIndex: 'name', key: 'name', onCell },
+ ];
+ const wrapper = render(createTable({ columns }));
+
+ expect(wrapper.find('tbody td')).toMatchSnapshot();
+ });
+
+ it('renders onHeaderRow correctly', () => {
+ const onHeaderRow = jest.fn((columns, index) => ({
+ id: `header-row-${index}`,
+ }));
+ const wrapper = render(createTable({ onHeaderRow }));
+
+ expect(wrapper.find('thead tr')).toMatchSnapshot();
+ expect(onHeaderRow).toBeCalledWith(
+ [{ title: 'Name', dataIndex: 'name', key: 'name' }],
+ 0,
+ );
+ });
+
+ it('renders column.onHeaderCell', () => {
+ const onHeaderCell = (column) => ({
+ id: `header-cell-${column.key}`,
+ });
+ const columns = [
+ { title: 'Name', dataIndex: 'name', key: 'name', onHeaderCell },
+ ];
+ const wrapper = render(createTable({ columns }));
+
+ expect(wrapper.find('thead th')).toMatchSnapshot();
+ });
+
+ describe('custom components', () => {
+ const MyTable = (props) => ;
+ const HeaderWrapper = (props) => ;
+ const HeaderRow = (props) =>
;
+ const HeaderCell = (props) => | ;
+ const BodyWrapper = (props) => ;
+ const BodyRow = (props) =>
;
+ const BodyCell = (props) => | ;
+
+ const components = {
+ table: MyTable,
+ header: {
+ wrapper: HeaderWrapper,
+ row: HeaderRow,
+ cell: HeaderCell,
+ },
+ body: {
+ wrapper: BodyWrapper,
+ row: BodyRow,
+ cell: BodyCell,
+ },
+ };
+
+ it('renders correctly', () => {
+ const wrapper = render(createTable({ components }));
+
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('renders fixed column and header correctly', () => {
+ const columns = [
+ { title: 'Name', dataIndex: 'name', key: 'name', fixed: 'left' },
+ { title: 'Age', dataIndex: 'age', key: 'age' },
+ { title: 'Gender', dataIndex: 'gender', key: 'gender', fixed: 'right' },
+ ];
+ const sampleData = [
+ { key: 0, name: 'Lucy', age: 27, gender: 'F' },
+ ];
+ const wrapper = render(createTable({
+ columns,
+ data: sampleData,
+ components,
+ scroll: { y: 100 },
+ }));
+
+ expect(wrapper).toMatchSnapshot();
+ });
+ });
});
diff --git a/tests/__snapshots__/Table.spec.js.snap b/tests/__snapshots__/Table.spec.js.snap
index 98e9c233f..c9e320534 100644
--- a/tests/__snapshots__/Table.spec.js.snap
+++ b/tests/__snapshots__/Table.spec.js.snap
@@ -1,5 +1,312 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Table custom components renders correctly 1`] = `
+
+`;
+
+exports[`Table custom components renders fixed column and header correctly 1`] = `
+
+`;
+
exports[`Table dataIndex render text by path 1`] = `