diff --git a/components/vc-table/assets/animation.less b/components/vc-table/assets/animation.less new file mode 100644 index 000000000..a6d4e9281 --- /dev/null +++ b/components/vc-table/assets/animation.less @@ -0,0 +1,59 @@ + +.move-enter, .move-appear { + opacity: 0; + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-duration: 2.5s; + animation-fill-mode: both; + animation-play-state: paused; +} + +.move-leave { + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-duration: .5s; + animation-fill-mode: both; + animation-play-state: paused; +} + +.move-enter.move-enter-active, .move-appear.move-enter-active { + animation-name: moveLeftIn; + animation-play-state: running; +} + +.move-leave.move-leave-active { + animation-name: moveRightOut; + animation-play-state: running; +} + +@keyframes moveLeftIn { + 0% { + transform-origin: 0 0; + transform: translateX(30px); + opacity: 0; + background: #fff6de; + } + 20% { + transform-origin: 0 0; + transform: translateX(0); + opacity: 1; + } + 80%{ + background: #fff6de; + } + 100%{ + background: transparent; + opacity: 1; + } +} + +@keyframes moveRightOut { + 0% { + transform-origin: 0 0; + transform: translateX(0); + opacity: 1; + } + 100% { + transform-origin: 0 0; + transform: translateX(-30px); + opacity: 0; + } +} diff --git a/components/vc-table/assets/bordered.less b/components/vc-table/assets/bordered.less new file mode 100644 index 000000000..f3ad2e84a --- /dev/null +++ b/components/vc-table/assets/bordered.less @@ -0,0 +1,11 @@ +@tablePrefixCls: rc-table; +@table-border-color: #e9e9e9; + +.@{tablePrefixCls}.bordered { + table { + border-collapse: collapse; + } + th, td { + border: 1px solid @table-border-color; + } +} diff --git a/components/vc-table/assets/index.less b/components/vc-table/assets/index.less new file mode 100644 index 000000000..173d5f1eb --- /dev/null +++ b/components/vc-table/assets/index.less @@ -0,0 +1,225 @@ +@tablePrefixCls: rc-table; +@text-color : #666; +@font-size-base : 12px; +@line-height: 1.5; +@table-border-color: #e9e9e9; +@table-head-background-color: #f7f7f7; +@vertical-padding: 16px; +@horizontal-padding: 8px; + +.@{tablePrefixCls} { + font-size: @font-size-base; + color: @text-color; + transition: opacity 0.3s ease; + position: relative; + line-height: @line-height; + overflow: hidden; + + .@{tablePrefixCls}-scroll { + overflow: auto; + table { + width: auto; + min-width: 100%; + } + } + + .@{tablePrefixCls}-header { + overflow: hidden; + background: @table-head-background-color; + } + + &-fixed-header &-body { + background: #fff; + position: relative; + } + + &-fixed-header &-body-inner { + height: 100%; + overflow: scroll; + } + + &-fixed-header &-scroll &-header { + overflow-x: scroll; + padding-bottom: 20px; + margin-bottom: -20px; + overflow-y: scroll; + box-sizing: border-box; + } + + .@{tablePrefixCls}-title { + padding: @vertical-padding @horizontal-padding; + border-top: 1px solid @table-border-color; + } + + .@{tablePrefixCls}-content { + position: relative; + } + + .@{tablePrefixCls}-footer { + padding: @vertical-padding @horizontal-padding; + border-bottom: 1px solid @table-border-color; + } + + .@{tablePrefixCls}-placeholder { + padding: 16px 8px; + background: #fff; + border-bottom: 1px solid @table-border-color; + text-align: center; + position: relative; + &-fixed-columns { + position: absolute; + bottom: 0; + width: 100%; + background: transparent; + pointer-events: none; + } + } + + table { + width: 100%; + border-collapse: separate; + text-align: left; + } + + th { + background: @table-head-background-color; + font-weight: bold; + transition: background .3s ease; + } + + td { + border-bottom: 1px solid @table-border-color; + &:empty:after { + content: '.'; // empty cell placeholder + visibility: hidden; + } + } + + tr { + transition: all .3s ease; + &:hover { + background: #eaf8fe; + } + &.@{tablePrefixCls}-row-hover { + background: #eaf8fe; + } + } + + th, td { + padding: @vertical-padding @horizontal-padding; + white-space: nowrap; + } +} + +.@{tablePrefixCls} { + &-expand-icon-col { + width: 34px; + } + &-row, &-expanded-row { + &-expand-icon { + cursor: pointer; + display: inline-block; + width: 16px; + height: 16px; + text-align: center; + line-height: 16px; + border: 1px solid @table-border-color; + user-select: none; + background: #fff; + } + &-spaced { + visibility: hidden; + } + &-spaced:after { + content: '.' + } + + &-expanded:after { + content: '-' + } + + &-collapsed:after { + content: '+' + } + } + tr&-expanded-row { + background: #f7f7f7; + &:hover { + background: #f7f7f7; + } + } + &-column-hidden { + display: none; + } + &-prev-columns-page, + &-next-columns-page { + cursor: pointer; + color: #666; + z-index: 1; + &:hover { + color: #2db7f5; + } + &-disabled { + cursor: not-allowed; + color: #999; + &:hover { + color: #999; + } + } + } + &-prev-columns-page { + margin-right: 8px; + &:before { + content: '<'; + } + } + &-next-columns-page { + float: right; + &:before { + content: '>'; + } + } + + &-fixed-left, + &-fixed-right { + position: absolute; + top: 0; + overflow: hidden; + table { + width: auto; + background: #fff; + } + } + + &-fixed-left { + left: 0; + box-shadow: 4px 0 4px rgba(100, 100, 100, 0.1); + & .@{tablePrefixCls}-body-inner { + margin-right: -20px; + padding-right: 20px; + } + .@{tablePrefixCls}-fixed-header & .@{tablePrefixCls}-body-inner { + padding-right: 0; + } + } + + &-fixed-right { + right: 0; + box-shadow: -4px 0 4px rgba(100, 100, 100, 0.1); + + // hide expand row content in right fixed Table + // https://github.com/ant-design/ant-design/issues/1898 + .@{tablePrefixCls}-expanded-row { + color: transparent; + pointer-events: none; + } + } + + &&-scroll-position-left &-fixed-left { + box-shadow: none; + } + + &&-scroll-position-right &-fixed-right { + box-shadow: none; + } +} diff --git a/components/vc-table/demo/animation.js b/components/vc-table/demo/animation.js new file mode 100644 index 000000000..6f379dcdc --- /dev/null +++ b/components/vc-table/demo/animation.js @@ -0,0 +1,70 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import Animate from 'rc-animate'; +import 'rc-table/assets/index.less'; +import 'rc-table/assets/animation.less'; + +const AnimateBody = (props) => + ; + +class Demo extends React.Component { + constructor(props) { + super(props); + this.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: (text, record) => + this.onDelete(record.key, e)} href="#">Delete, + }, + ]; + this.state = { + data: [ + { a: '123', key: '1' }, + { a: 'cdd', b: 'edd', key: '2' }, + { a: '1333', c: 'eee', key: '3' }, + ], + }; + } + + onDelete(key, e) { + console.log('Delete', key); + e.preventDefault(); + const data = this.state.data.filter(item => item.key !== key); + this.setState({ data }); + } + + onAdd() { + const data = [...this.state.data]; + data.push({ + a: 'new data', + b: 'new data', + c: 'new data', + key: Date.now(), + }); + this.setState({ data }); + } + + render() { + return ( +
+

Table row with animation

+ + + + ); + } +} +ReactDOM.render( + , + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/childrenIndent.js b/components/vc-table/demo/childrenIndent.js new file mode 100644 index 000000000..4b226a887 --- /dev/null +++ b/components/vc-table/demo/childrenIndent.js @@ -0,0 +1,87 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [{ + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 400, +}, { + title: 'Age', + dataIndex: 'age', + key: 'age', + width: 100, +}, { + title: 'Address', + dataIndex: 'address', + key: 'address', + width: 200, +}, { + title: 'Operations', + dataIndex: 'operation', + key: 'x', + width: 150, +}]; + +const data = [{ + key: 1, + name: 'a', + age: 32, + address: 'I am a', + children: [{ + key: 11, + name: 'aa', + age: 33, + address: 'I am aa', + }, { + key: 12, + name: 'ab', + age: 33, + address: 'I am ab', + children: [{ + key: 121, + name: 'aba', + age: 33, + address: 'I am aba', + }], + }, { + key: 13, + name: 'ac', + age: 33, + address: 'I am ac', + children: [{ + key: 131, + name: 'aca', + age: 33, + address: 'I am aca', + children: [{ + key: 1311, + name: 'acaa', + age: 33, + address: 'I am acaa', + }, { + key: 1312, + name: 'acab', + age: 33, + address: 'I am acab', + }], + }], + }], +}, { + key: 2, + name: 'b', + age: 32, + address: 'I am b', +}]; + +function onExpand(expanded, record) { + console.log('onExpand', expanded, record); +} + +ReactDOM.render( +
, + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/className.js b/components/vc-table/demo/className.js new file mode 100644 index 000000000..77f631174 --- /dev/null +++ b/components/vc-table/demo/className.js @@ -0,0 +1,45 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', + className: 'a', + key: 'a', width: 100 }, + { id: '123', title: 'title2', dataIndex: 'b', + className: 'b', + key: 'b', width: 100 }, + { title: 'title3', dataIndex: 'c', + className: 'c', + key: 'c', width: 200 }, + { + title: 'Operations', dataIndex: '', + className: 'd', + 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' }, +]; + +ReactDOM.render( +
+

rowClassName and className

+
`row-${i}`} + expandedRowRender={record =>

extra: {record.a}

} + expandedRowClassName={(record, i) => `ex-row-${i}`} + data={data} + className="table" + /> + , + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/colspan-rowspan.js b/components/vc-table/demo/colspan-rowspan.js new file mode 100644 index 000000000..c0e9b9187 --- /dev/null +++ b/components/vc-table/demo/colspan-rowspan.js @@ -0,0 +1,107 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: '手机号', dataIndex: 'a', colSpan: 2, width: 100, key: 'a', render(o, row, index) { + const obj = { + children: o, + props: {}, + }; + // 设置第一行为链接 + if (index === 0) { + obj.children = {o}; + } + // 第5行合并两列 + if (index === 4) { + obj.props.colSpan = 2; + } + + if (index === 5) { + obj.props.colSpan = 6; + } + return obj; + } }, + { title: '电话', dataIndex: 'b', colSpan: 0, width: 100, key: 'b', render(o, row, index) { + const obj = { + children: o, + props: {}, + }; + // 列合并掉的表格设置colSpan=0,不会去渲染 + if (index === 4 || index === 5) { + obj.props.colSpan = 0; + } + return obj; + } }, + { title: 'Name', dataIndex: 'c', width: 100, key: 'c', render(o, row, index) { + const obj = { + children: o, + props: {}, + }; + + if (index === 5) { + obj.props.colSpan = 0; + } + return obj; + } }, + { title: 'Address', dataIndex: 'd', width: 200, key: 'd', render(o, row, index) { + const obj = { + children: o, + props: {}, + }; + if (index === 0) { + obj.props.rowSpan = 2; + } + if (index === 1 || index === 5) { + obj.props.rowSpan = 0; + } + + return obj; + } }, + { title: 'Gender', dataIndex: 'e', width: 200, key: 'e', render(o, row, index) { + const obj = { + children: o, + props: {}, + }; + if (index === 5) { + obj.props.colSpan = 0; + } + return obj; + } }, + { + title: 'Operations', dataIndex: '', key: 'f', + render(o, row, index) { + if (index === 5) { + return { + props: { + colSpan: 0, + }, + }; + } + return Operations; + }, + }, +]; + +const data = [ + { a: '13812340987', b: '0571-12345678', c: '张三', d: '文一西路', e: 'Male', key: '1' }, + { a: '13812340986', b: '0571-98787658', c: '张夫人', d: '文一西路', e: 'Female', key: '2' }, + { a: '13812988888', b: '0571-099877', c: '李四', d: '文二西路', e: 'Male', key: '3' }, + { a: '1381200008888', b: '0571-099877', c: '王五', d: '文二西路', e: 'Male', key: '4' }, + { a: '0571-88888110', c: '李警官', d: '武林门', e: 'Male', key: '5' }, + { a: '资料统计完毕于xxxx年xxx月xxx日', key: '6' }, +]; + +ReactDOM.render( +
+

colSpan & rowSpan

+
+ , + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/column-resize.js b/components/vc-table/demo/column-resize.js new file mode 100644 index 000000000..7a56e580d --- /dev/null +++ b/components/vc-table/demo/column-resize.js @@ -0,0 +1,84 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; +import { Resizable } from 'react-resizable'; +import 'react-resizable/css/styles.css'; + +const ResizeableTitle = (props) => { + const { onResize, width, ...restProps } = props; + + if (!width) { + return + ) + } + + let leafColumns + + if (fixed === 'left') { + leafColumns = table.columnManager.leftLeafColumns() + } else if (fixed === 'right') { + leafColumns = table.columnManager.rightLeafColumns() + } else { + leafColumns = table.columnManager.leafColumns() + } + cols = cols.concat( + leafColumns.map(c => { + return ( + + ) + }) + ) + return ( + + {cols} + + ) + }, + +} + diff --git a/components/vc-table/src/Column.jsx b/components/vc-table/src/Column.jsx new file mode 100644 index 000000000..8ad12648c --- /dev/null +++ b/components/vc-table/src/Column.jsx @@ -0,0 +1,23 @@ +import PropTypes from '../../_util/vue-types' + +export default { + name: 'Column', + props: { + colSpan: PropTypes.number, + title: PropTypes.any, + dataIndex: PropTypes.string, + width: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + ]), + fixed: PropTypes.oneOf([ + true, + 'left', + 'right', + ]), + render: PropTypes.func, + // onCellClick: PropTypes.func, + // onCell: PropTypes.func, + // onHeaderCell: PropTypes.func, + }, +} diff --git a/components/vc-table/src/ColumnGroup.jsx b/components/vc-table/src/ColumnGroup.jsx new file mode 100644 index 000000000..ad329cd15 --- /dev/null +++ b/components/vc-table/src/ColumnGroup.jsx @@ -0,0 +1,9 @@ +import PropTypes from '../../_util/vue-types' + +export default { + name: 'ColumnGroup', + props: { + title: PropTypes.any, + }, + isTableColumnGroup: true, +} diff --git a/components/vc-table/src/ColumnManager.js b/components/vc-table/src/ColumnManager.js new file mode 100644 index 000000000..a90756c14 --- /dev/null +++ b/components/vc-table/src/ColumnManager.js @@ -0,0 +1,150 @@ +export default class ColumnManager { + _cached = {} + + constructor (columns, elements) { + this.columns = columns || this.normalize(elements) + } + + isAnyColumnsFixed () { + return this._cache('isAnyColumnsFixed', () => { + return this.columns.some(column => !!column.fixed) + }) + } + + isAnyColumnsLeftFixed () { + return this._cache('isAnyColumnsLeftFixed', () => { + return this.columns.some( + column => column.fixed === 'left' || column.fixed === true + ) + }) + } + + isAnyColumnsRightFixed () { + return this._cache('isAnyColumnsRightFixed', () => { + return this.columns.some( + column => column.fixed === 'right' + ) + }) + } + + leftColumns () { + return this._cache('leftColumns', () => { + return this.groupedColumns().filter( + column => column.fixed === 'left' || column.fixed === true + ) + }) + } + + rightColumns () { + return this._cache('rightColumns', () => { + return this.groupedColumns().filter( + column => column.fixed === 'right' + ) + }) + } + + leafColumns () { + return this._cache('leafColumns', () => + this._leafColumns(this.columns) + ) + } + + leftLeafColumns () { + return this._cache('leftLeafColumns', () => + this._leafColumns(this.leftColumns()) + ) + } + + rightLeafColumns () { + return this._cache('rightLeafColumns', () => + this._leafColumns(this.rightColumns()) + ) + } + + // add appropriate rowspan and colspan to column + groupedColumns () { + return this._cache('groupedColumns', () => { + const _groupColumns = (columns, currentRow = 0, parentColumn = {}, rows = []) => { + // track how many rows we got + rows[currentRow] = rows[currentRow] || [] + const grouped = [] + const setRowSpan = column => { + const rowSpan = rows.length - currentRow + if (column && + !column.children && // parent columns are supposed to be one row + rowSpan > 1 && + (!column.rowSpan || column.rowSpan < rowSpan) + ) { + column.rowSpan = rowSpan + } + } + columns.forEach((column, index) => { + const newColumn = { ...column } + rows[currentRow].push(newColumn) + parentColumn.colSpan = parentColumn.colSpan || 0 + if (newColumn.children && newColumn.children.length > 0) { + newColumn.children = _groupColumns(newColumn.children, currentRow + 1, newColumn, rows) + parentColumn.colSpan = parentColumn.colSpan + newColumn.colSpan + } else { + parentColumn.colSpan++ + } + // update rowspan to all same row columns + for (let i = 0; i < rows[currentRow].length - 1; ++i) { + setRowSpan(rows[currentRow][i]) + } + // last column, update rowspan immediately + if (index + 1 === columns.length) { + setRowSpan(newColumn) + } + grouped.push(newColumn) + }) + return grouped + } + return _groupColumns(this.columns) + }) + } + + normalize (elements) { + const columns = [] + elements.forEach(element => { + if (!element.tag) { + return + } + debugger + const column = { ...element.props } + if (element.key) { + column.key = element.key + } + if (element.type.isTableColumnGroup) { + column.children = this.normalize(column.children) + } + columns.push(column) + }) + return columns + } + + reset (columns, elements) { + this.columns = columns || this.normalize(elements) + this._cached = {} + } + + _cache (name, fn) { + if (name in this._cached) { + return this._cached[name] + } + this._cached[name] = fn() + return this._cached[name] + } + + _leafColumns (columns) { + const leafColumns = [] + columns.forEach(column => { + if (!column.children) { + leafColumns.push(column) + } else { + leafColumns.push(...this._leafColumns(column.children)) + } + }) + return leafColumns + } +} diff --git a/components/vc-table/src/ExpandIcon.jsx b/components/vc-table/src/ExpandIcon.jsx new file mode 100644 index 000000000..490386d43 --- /dev/null +++ b/components/vc-table/src/ExpandIcon.jsx @@ -0,0 +1,34 @@ +import PropTypes from '../../_util/vue-types' +import BaseMixin from '../../_util/BaseMixin' +export default { + mixins: [BaseMixin], + name: 'ExpandIcon', + props: { + record: PropTypes.object, + prefixCls: PropTypes.string, + expandable: PropTypes.any, + expanded: PropTypes.bool, + needIndentSpaced: PropTypes.bool, + }, + methods: { + onExpand (e) { + this.__emit('expand', this.record, e) + }, + }, + + render () { + const { expandable, prefixCls, onExpand, needIndentSpaced, expanded } = this + if (expandable) { + const expandClassName = expanded ? 'expanded' : 'collapsed' + return ( + + ) + } else if (needIndentSpaced) { + return + } + return null + }, +} diff --git a/components/vc-table/src/ExpandableRow.js b/components/vc-table/src/ExpandableRow.js new file mode 100644 index 000000000..895cd886c --- /dev/null +++ b/components/vc-table/src/ExpandableRow.js @@ -0,0 +1,126 @@ +import PropTypes from '../../_util/vue-types' +import ExpandIcon from './ExpandIcon' +import BaseMixin from '../../_util/BaseMixin' + +const ExpandableRow = { + mixins: [BaseMixin], + name: 'ExpandableRow', + props: { + prefixCls: PropTypes.string.isRequired, + rowKey: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]).isRequired, + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + record: PropTypes.object.isRequired, + indentSize: PropTypes.number, + needIndentSpaced: PropTypes.bool.isRequired, + expandRowByClick: PropTypes.bool, + expanded: PropTypes.bool.isRequired, + expandIconAsCell: PropTypes.bool, + expandIconColumnIndex: PropTypes.number, + childrenColumnName: PropTypes.string, + expandedRowRender: PropTypes.func, + // onExpandedChange: PropTypes.func.isRequired, + // onRowClick: PropTypes.func, + // children: PropTypes.func.isRequired, + }, + + beforeDestroy () { + this.handleDestroy() + }, + + hasExpandIcon (columnIndex) { + const { expandRowByClick } = this + return !this.expandIconAsCell && + !expandRowByClick && + columnIndex === this.expandIconColumnIndex + }, + + handleExpandChange (record, event) { + const { expanded, rowKey } = this + this.__emit('expandedChange', !expanded, record, event, rowKey) + }, + + handleDestroy () { + const { rowKey, record } = this + this.__emit('expandedChange', false, record, null, rowKey, true) + }, + + handleRowClick (record, index, event) { + const { expandRowByClick } = this + if (expandRowByClick) { + this.handleExpandChange(record, event) + } + this.__emit('rowClick', record, index, event) + }, + + renderExpandIcon () { + const { prefixCls, expanded, record, needIndentSpaced } = this + + return ( + + ) + }, + + renderExpandIconCell (cells) { + if (!this.expandIconAsCell) { + return + } + const { prefixCls } = this + + cells.push( + + ) + }, + + render () { + const { + childrenColumnName, + expandedRowRender, + indentSize, + record, + fixed, + $scopedSlots, + } = this + + this.expandIconAsCell = fixed !== 'right' ? this.expandIconAsCell : false + this.expandIconColumnIndex = fixed !== 'right' ? this.expandIconColumnIndex : -1 + const childrenData = record[childrenColumnName] + this.expandable = !!(childrenData || expandedRowRender) + + const expandableRowProps = { + props: { + indentSize, + hasExpandIcon: this.hasExpandIcon, + renderExpandIcon: this.renderExpandIcon, + renderExpandIconCell: this.renderExpandIconCell, + }, + on: { + rowClick: this.handleRowClick, + }, + + } + + return $scopedSlots.default && $scopedSlots.default(expandableRowProps) + }, +} + +export default connect(({ expandedRowKeys }, { rowKey }) => ({ + expanded: !!~expandedRowKeys.indexOf(rowKey), +}))(ExpandableRow) diff --git a/components/vc-table/src/ExpandableTable.js b/components/vc-table/src/ExpandableTable.js new file mode 100644 index 000000000..025a59872 --- /dev/null +++ b/components/vc-table/src/ExpandableTable.js @@ -0,0 +1,223 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'mini-store' +import TableRow from './TableRow' +import { remove } from './utils' + +class ExpandableTable extends React.Component { + static propTypes = { + expandIconAsCell: PropTypes.bool, + expandedRowKeys: PropTypes.array, + expandedRowClassName: PropTypes.func, + defaultExpandAllRows: PropTypes.bool, + defaultExpandedRowKeys: PropTypes.array, + expandIconColumnIndex: PropTypes.number, + expandedRowRender: PropTypes.func, + childrenColumnName: PropTypes.string, + indentSize: PropTypes.number, + onExpand: PropTypes.func, + onExpandedRowsChange: PropTypes.func, + columnManager: PropTypes.object.isRequired, + store: PropTypes.object.isRequired, + prefixCls: PropTypes.string.isRequired, + data: PropTypes.array, + children: PropTypes.func.isRequired, + } + + static defaultProps = { + expandIconAsCell: false, + expandedRowClassName: () => '', + expandIconColumnIndex: 0, + defaultExpandAllRows: false, + defaultExpandedRowKeys: [], + childrenColumnName: 'children', + indentSize: 15, + onExpand () {}, + onExpandedRowsChange () {}, + } + + constructor (props) { + super(props) + + const { + data, + childrenColumnName, + defaultExpandAllRows, + expandedRowKeys, + defaultExpandedRowKeys, + getRowKey, + } = props + + let finnalExpandedRowKeys = [] + let rows = [...data] + + if (defaultExpandAllRows) { + for (let i = 0; i < rows.length; i++) { + const row = rows[i] + finnalExpandedRowKeys.push(getRowKey(row, i)) + rows = rows.concat(row[childrenColumnName] || []) + } + } else { + finnalExpandedRowKeys = expandedRowKeys || defaultExpandedRowKeys + } + + this.columnManager = props.columnManager + this.store = props.store + + this.store.setState({ + expandedRowsHeight: {}, + expandedRowKeys: finnalExpandedRowKeys, + }) + } + + componentWillReceiveProps (nextProps) { + if ('expandedRowKeys' in nextProps) { + this.store.setState({ + expandedRowKeys: nextProps.expandedRowKeys, + }) + } + } + + handleExpandChange = (expanded, record, event, rowKey, destroy = false) => { + if (event) { + event.preventDefault() + event.stopPropagation() + } + + const { onExpandedRowsChange, onExpand } = this.props + let { expandedRowKeys } = this.store.getState() + + if (expanded) { + // row was expaned + expandedRowKeys = [...expandedRowKeys, rowKey] + } else { + // row was collapse + const expandedRowIndex = expandedRowKeys.indexOf(rowKey) + if (expandedRowIndex !== -1) { + expandedRowKeys = remove(expandedRowKeys, rowKey) + } + } + + if (!this.props.expandedRowKeys) { + this.store.setState({ expandedRowKeys }) + } + + onExpandedRowsChange(expandedRowKeys) + if (!destroy) { + onExpand(expanded, record) + } + } + + renderExpandIndentCell = (rows, fixed) => { + const { prefixCls, expandIconAsCell } = this.props + if (!expandIconAsCell || fixed === 'right' || !rows.length) { + return + } + + const iconColumn = { + key: 'rc-table-expand-icon-cell', + className: `${prefixCls}-expand-icon-th`, + title: '', + rowSpan: rows.length, + } + + rows[0].unshift({ ...iconColumn, column: iconColumn }) + } + + renderExpandedRow (record, index, render, className, ancestorKeys, indent, fixed) { + const { prefixCls, expandIconAsCell, indentSize } = this.props + let colCount + if (fixed === 'left') { + colCount = this.columnManager.leftLeafColumns().length + } else if (fixed === 'right') { + colCount = this.columnManager.rightLeafColumns().length + } else { + colCount = this.columnManager.leafColumns().length + } + const columns = [{ + key: 'extra-row', + render: () => ({ + props: { + colSpan: colCount, + }, + children: fixed !== 'right' ? render(record, index, indent) : ' ', + }), + }] + if (expandIconAsCell && fixed !== 'right') { + columns.unshift({ + key: 'expand-icon-placeholder', + render: () => null, + }) + } + const parentKey = ancestorKeys[ancestorKeys.length - 1] + const rowKey = `${parentKey}-extra-row` + const components = { + body: { + row: 'tr', + cell: 'td', + }, + } + + return ( + + ) + } + + renderRows = (renderRows, rows, record, index, indent, fixed, parentKey, ancestorKeys) => { + const { expandedRowClassName, expandedRowRender, childrenColumnName } = this.props + const childrenData = record[childrenColumnName] + const nextAncestorKeys = [...ancestorKeys, parentKey] + const nextIndent = indent + 1 + + if (expandedRowRender) { + rows.push( + this.renderExpandedRow( + record, + index, + expandedRowRender, + expandedRowClassName(record, index, indent), + nextAncestorKeys, + nextIndent, + fixed, + ), + ) + } + + if (childrenData) { + rows.push( + ...renderRows( + childrenData, + nextIndent, + nextAncestorKeys, + ) + ) + } + } + + render () { + const { data, childrenColumnName, children } = this.props + const needIndentSpaced = data.some(record => record[childrenColumnName]) + + return children({ + props: this.props, + needIndentSpaced, + renderRows: this.renderRows, + handleExpandChange: this.handleExpandChange, + renderExpandIndentCell: this.renderExpandIndentCell, + }) + } +} + +export default connect()(ExpandableTable) diff --git a/components/vc-table/src/HeadTable.jsx b/components/vc-table/src/HeadTable.jsx new file mode 100644 index 000000000..e2095b364 --- /dev/null +++ b/components/vc-table/src/HeadTable.jsx @@ -0,0 +1,56 @@ +import PropTypes from '../../_util/vue-types' +import { measureScrollbar } from './utils' +import BaseTable from './BaseTable' + +export default { + name: 'HeadTable', + props: { + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + columns: PropTypes.array.isRequired, + tableClassName: PropTypes.string.isRequired, + handleBodyScrollLeft: PropTypes.func.isRequired, + expander: PropTypes.object.isRequired, + }, + render () { + const { columns, fixed, tableClassName, handleBodyScrollLeft, expander, table } = this + const { prefixCls, scroll, showHeader } = table + let { useFixedHeader } = table + const headStyle = {} + + if (scroll.y) { + useFixedHeader = true + // Add negative margin bottom for scroll bar overflow bug + const scrollbarWidth = measureScrollbar('horizontal') + if (scrollbarWidth > 0 && !fixed) { + headStyle.marginBottom = `-${scrollbarWidth}px` + headStyle.paddingBottom = '0px' + } + } + + if (!useFixedHeader || !showHeader) { + return null + } + return ( +
+ +
+ ) + }, + +} diff --git a/components/vc-table/src/Table.js b/components/vc-table/src/Table.js new file mode 100644 index 000000000..566e522de --- /dev/null +++ b/components/vc-table/src/Table.js @@ -0,0 +1,475 @@ +import React from 'react' +import PropTypes from 'prop-types' +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' +import BodyTable from './BodyTable' +import ExpandableTable from './ExpandableTable' + +export default class Table extends React.Component { + static propTypes = { + data: PropTypes.array, + useFixedHeader: PropTypes.bool, + columns: PropTypes.array, + prefixCls: PropTypes.string, + bodyStyle: PropTypes.object, + 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, + onRowMouseEnter: PropTypes.func, + onRowMouseLeave: PropTypes.func, + showHeader: PropTypes.bool, + title: PropTypes.func, + id: PropTypes.string, + footer: PropTypes.func, + emptyText: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + scroll: PropTypes.object, + 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 = { + data: [], + useFixedHeader: false, + rowKey: 'key', + rowClassName: () => '', + onRow () {}, + onHeaderRow () {}, + prefixCls: 'rc-table', + bodyStyle: {}, + style: {}, + showHeader: true, + scroll: {}, + rowRef: () => null, + emptyText: () => 'No Data', + } + + constructor (props) { + super(props); + + [ + 'onRowClick', + 'onRowDoubleClick', + 'onRowContextMenu', + 'onRowMouseEnter', + 'onRowMouseLeave', + ].forEach(name => { + warningOnce( + props[name] === undefined, + `${name} is deprecated, please use onRow instead.`, + ) + }) + + warningOnce( + props.getBodyWrapper === undefined, + 'getBodyWrapper is deprecated, please use custom components instead.', + ) + + this.columnManager = new ColumnManager(props.columns, props.children) + + this.store = create({ + currentHoverKey: null, + fixedColumnsHeadRowsHeight: [], + fixedColumnsBodyRowsHeight: [], + }) + + this.setScrollPosition('left') + + this.debouncedWindowResize = debounce(this.handleWindowResize, 150) + } + + getChildContext () { + return { + table: { + 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), + }, + } + } + + componentDidMount () { + if (this.columnManager.isAnyColumnsFixed()) { + this.handleWindowResize() + this.resizeEvent = addEventListener( + window, 'resize', this.debouncedWindowResize + ) + } + } + + componentWillReceiveProps (nextProps) { + if (nextProps.columns && nextProps.columns !== this.props.columns) { + this.columnManager.reset(nextProps.columns) + } else if (nextProps.children !== this.props.children) { + this.columnManager.reset(null, nextProps.children) + } + } + + componentDidUpdate (prevProps) { + if (this.columnManager.isAnyColumnsFixed()) { + this.handleWindowResize() + if (!this.resizeEvent) { + this.resizeEvent = addEventListener( + window, 'resize', this.debouncedWindowResize + ) + } + } + // when table changes to empty, reset scrollLeft + if (prevProps.data.length > 0 && this.props.data.length === 0 && this.hasScrollX()) { + this.resetScrollX() + } + } + + componentWillUnmount () { + if (this.resizeEvent) { + this.resizeEvent.remove() + } + if (this.debouncedWindowResize) { + this.debouncedWindowResize.cancel() + } + } + + getRowKey = (record, index) => { + const rowKey = this.props.rowKey + const key = (typeof rowKey === 'function') + ? rowKey(record, index) : record[rowKey] + warningOnce( + key !== undefined, + 'Each record in table should have a unique `key` prop,' + + 'or set `rowKey` to an unique primary key.' + ) + return key === undefined ? index : key + } + + setScrollPosition (position) { + this.scrollPosition = position + if (this.tableNode) { + const { prefixCls } = this.props + if (position === 'both') { + classes(this.tableNode) + .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`)) + .add(`${prefixCls}-scroll-position-left`) + .add(`${prefixCls}-scroll-position-right`) + } else { + classes(this.tableNode) + .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`)) + .add(`${prefixCls}-scroll-position-${position}`) + } + } + } + + setScrollPositionClassName () { + const node = this.bodyTable + const scrollToLeft = node.scrollLeft === 0 + const scrollToRight = node.scrollLeft + 1 >= + node.children[0].getBoundingClientRect().width - + node.getBoundingClientRect().width + if (scrollToLeft && scrollToRight) { + this.setScrollPosition('both') + } else if (scrollToLeft) { + this.setScrollPosition('left') + } else if (scrollToRight) { + this.setScrollPosition('right') + } else if (this.scrollPosition !== 'middle') { + this.setScrollPosition('middle') + } + } + + handleWindowResize = () => { + this.syncFixedTableRowHeight() + this.setScrollPositionClassName() + } + + syncFixedTableRowHeight = () => { + const tableRect = this.tableNode.getBoundingClientRect() + // If tableNode's height less than 0, suppose it is hidden and don't recalculate rowHeight. + // see: https://github.com/ant-design/ant-design/issues/4836 + if (tableRect.height !== undefined && tableRect.height <= 0) { + return + } + const { prefixCls } = this.props + const headRows = this.headTable + ? this.headTable.querySelectorAll('thead') + : this.bodyTable.querySelectorAll('thead') + const bodyRows = this.bodyTable.querySelectorAll(`.${prefixCls}-row`) || [] + const fixedColumnsHeadRowsHeight = [].map.call( + headRows, row => row.getBoundingClientRect().height || 'auto' + ) + const fixedColumnsBodyRowsHeight = [].map.call( + bodyRows, row => row.getBoundingClientRect().height || 'auto' + ) + const state = this.store.getState() + if (shallowequal(state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) && + shallowequal(state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)) { + return + } + + this.store.setState({ + fixedColumnsHeadRowsHeight, + fixedColumnsBodyRowsHeight, + }) + } + + resetScrollX () { + if (this.headTable) { + this.headTable.scrollLeft = 0 + } + if (this.bodyTable) { + this.bodyTable.scrollLeft = 0 + } + } + + hasScrollX () { + const { scroll = {}} = this.props + return 'x' in scroll + } + + handleBodyScrollLeft = (e) => { + // Fix https://github.com/ant-design/ant-design/issues/7635 + if (e.currentTarget !== e.target) { + return + } + const target = e.target + const { scroll = {}} = this.props + const { headTable, bodyTable } = this + if (target.scrollLeft !== this.lastScrollLeft && scroll.x) { + if (target === bodyTable && headTable) { + headTable.scrollLeft = target.scrollLeft + } else if (target === headTable && bodyTable) { + bodyTable.scrollLeft = target.scrollLeft + } + this.setScrollPositionClassName() + } + // Remember last scrollLeft for scroll direction detecting. + this.lastScrollLeft = target.scrollLeft + } + + handleBodyScrollTop = (e) => { + const target = e.target + const { scroll = {}} = this.props + const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this + if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== headTable) { + const scrollTop = target.scrollTop + if (fixedColumnsBodyLeft && target !== fixedColumnsBodyLeft) { + fixedColumnsBodyLeft.scrollTop = scrollTop + } + if (fixedColumnsBodyRight && target !== fixedColumnsBodyRight) { + fixedColumnsBodyRight.scrollTop = scrollTop + } + if (bodyTable && target !== bodyTable) { + bodyTable.scrollTop = scrollTop + } + } + // Remember last scrollTop for scroll direction detecting. + this.lastScrollTop = target.scrollTop + } + + handleBodyScroll = (e) => { + this.handleBodyScrollLeft(e) + this.handleBodyScrollTop(e) + } + + saveRef = (name) => (node) => { + this[name] = node + } + + renderMainTable () { + const { scroll, prefixCls } = this.props + const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed() + const scrollable = isAnyColumnsFixed || scroll.x || scroll.y + + const table = [ + this.renderTable({ + columns: this.columnManager.groupedColumns(), + isAnyColumnsFixed, + }), + this.renderEmptyText(), + this.renderFooter(), + ] + + return scrollable ? ( +
{table}
+ ) : table + } + + renderLeftFixedTable () { + const { prefixCls } = this.props + + return ( +
+ {this.renderTable({ + columns: this.columnManager.leftColumns(), + fixed: 'left', + })} +
+ ) + } + + renderRightFixedTable () { + const { prefixCls } = this.props + + return ( +
+ {this.renderTable({ + columns: this.columnManager.rightColumns(), + fixed: 'right', + })} +
+ ) + } + + renderTable (options) { + const { columns, fixed, isAnyColumnsFixed } = options + const { prefixCls, scroll = {}} = this.props + const tableClassName = (scroll.x || fixed) ? `${prefixCls}-fixed` : '' + + const headTable = ( + + ) + + const bodyTable = ( + + ) + + return [headTable, bodyTable] + } + + renderTitle () { + const { title, prefixCls } = this.props + return title ? ( +
+ {title(this.props.data)} +
+ ) : null + } + + renderFooter () { + const { footer, prefixCls } = this.props + return footer ? ( +
+ {footer(this.props.data)} +
+ ) : null + } + + renderEmptyText () { + const { emptyText, prefixCls, data } = this.props + if (data.length) { + return null + } + const emptyClassName = `${prefixCls}-placeholder` + return ( +
+ {(typeof emptyText === 'function') ? emptyText() : emptyText} +
+ ) + } + + render () { + const props = this.props + const prefixCls = props.prefixCls + + let className = props.prefixCls + if (props.className) { + className += ` ${props.className}` + } + if (props.useFixedHeader || (props.scroll && props.scroll.y)) { + className += ` ${prefixCls}-fixed-header` + } + if (this.scrollPosition === 'both') { + className += ` ${prefixCls}-scroll-position-left ${prefixCls}-scroll-position-right` + } else { + className += ` ${prefixCls}-scroll-position-${this.scrollPosition}` + } + const hasLeftFixed = this.columnManager.isAnyColumnsLeftFixed() + const hasRightFixed = this.columnManager.isAnyColumnsRightFixed() + + return ( + + + {(expander) => { + this.expander = expander + return ( +
+ {this.renderTitle()} +
+ {this.renderMainTable()} + {hasLeftFixed && this.renderLeftFixedTable()} + {hasRightFixed && this.renderRightFixedTable()} +
+
+ ) + }} +
+
+ ) + } +} diff --git a/components/vc-table/src/TableCell.jsx b/components/vc-table/src/TableCell.jsx new file mode 100644 index 000000000..41bccae5e --- /dev/null +++ b/components/vc-table/src/TableCell.jsx @@ -0,0 +1,104 @@ +import PropTypes from '../../_util/vue-types' +import get from 'lodash/get' + +export default { + name: 'TableCell', + props: { + record: PropTypes.object, + prefixCls: PropTypes.string, + index: PropTypes.number, + indent: PropTypes.number, + indentSize: PropTypes.number, + column: PropTypes.object, + expandIcon: PropTypes.node, + component: PropTypes.any, + }, + methods: { + isInvalidRenderCellText (text) { + debugger + return text && + Object.prototype.toString.call(text) === '[object Object]' + }, + + handleClick (e) { + const { record, column: { onCellClick }} = this + if (onCellClick) { + onCellClick(record, e) + } + }, + }, + + render () { + const { + record, + indentSize, + prefixCls, + indent, + index, + expandIcon, + column, + component: BodyCell, + } = this + const { dataIndex, render } = column + + // We should return undefined if no dataIndex is specified, but in order to + // be compatible with object-path's behavior, we return the record object instead. + let text + if (typeof dataIndex === 'number') { + text = get(record, dataIndex) + } else if (!dataIndex || dataIndex.length === 0) { + text = record + } else { + text = get(record, dataIndex) + } + let tdProps = {} + let colSpan + let rowSpan + + if (render) { + text = render(text, record, index) + if (this.isInvalidRenderCellText(text)) { + tdProps = text.props || tdProps + colSpan = tdProps.colSpan + rowSpan = tdProps.rowSpan + text = text.children + } + } + + if (column.onCell) { + tdProps = { ...tdProps, ...column.onCell(record) } + } + + // Fix https://github.com/ant-design/ant-design/issues/1202 + if (this.isInvalidRenderCellText(text)) { + text = null + } + + const indentText = expandIcon ? ( + + ) : null + + if (rowSpan === 0 || colSpan === 0) { + return null + } + + if (column.align) { + tdProps.style = { textAlign: column.align } + } + console.log('tdProps', tdProps) + + return ( + + {indentText} + {expandIcon} + {text} + + ) + }, +} diff --git a/components/vc-table/src/TableHeader.jsx b/components/vc-table/src/TableHeader.jsx new file mode 100644 index 000000000..b0a1be40f --- /dev/null +++ b/components/vc-table/src/TableHeader.jsx @@ -0,0 +1,85 @@ +import PropTypes from '../../_util/vue-types' +import TableHeaderRow from './TableHeaderRow' + +function getHeaderRows (columns, currentRow = 0, rows) { + rows = rows || [] + rows[currentRow] = rows[currentRow] || [] + + columns.forEach(column => { + if (column.rowSpan && rows.length < column.rowSpan) { + while (rows.length < column.rowSpan) { + rows.push([]) + } + } + const cell = { + key: column.key, + className: column.className || '', + children: column.title, + column, + } + if (column.children) { + getHeaderRows(column.children, currentRow + 1, rows) + } + if ('colSpan' in column) { + cell.colSpan = column.colSpan + } + if ('rowSpan' in column) { + cell.rowSpan = column.rowSpan + } + if (cell.colSpan !== 0) { + rows[currentRow].push(cell) + } + }) + return rows.filter(row => row.length > 0) +} + +export default { + name: 'TableHeader', + props: { + fixed: PropTypes.string, + columns: PropTypes.array.isRequired, + expander: PropTypes.object.isRequired, + + }, + methods: { + onHeaderRow () { + this.table.__emit('headerRow', ...arguments) + }, + }, + + render () { + const { components, prefixCls, showHeader } = this.table + const { expander, columns, fixed, onHeaderRow } = this + + if (!showHeader) { + return null + } + + const rows = getHeaderRows(columns) + + expander.renderExpandIndentCell(rows, fixed) + + const HeaderWrapper = components.header.wrapper + + return ( + + { + rows.map((row, index) => ( + + )) + } + + ) + }, + +} + diff --git a/components/vc-table/src/TableHeaderRow.js b/components/vc-table/src/TableHeaderRow.js new file mode 100644 index 000000000..d801ba46d --- /dev/null +++ b/components/vc-table/src/TableHeaderRow.js @@ -0,0 +1,53 @@ +import React from 'react' +import { connect } from 'mini-store' + +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) => { + const { column, ...cellProps } = cell + const customProps = column.onHeaderCell ? column.onHeaderCell(column) : {} + if (column.align) { + cellProps.style = { textAlign: column.align } + } + return ( + + ) + })} + + ) +} + +function getRowHeight (state, props) { + const { fixedColumnsHeadRowsHeight } = state + const { columns, rows, fixed } = props + const headerHeight = fixedColumnsHeadRowsHeight[0] + + if (!fixed) { + return null + } + + if (headerHeight && columns) { + if (headerHeight === 'auto') { + return 'auto' + } + return headerHeight / rows.length + } + return null +} + +export default connect((state, props) => { + return { + height: getRowHeight(state, props), + } +})(TableHeaderRow) diff --git a/components/vc-table/src/TableRow.js b/components/vc-table/src/TableRow.js new file mode 100644 index 000000000..3348bb559 --- /dev/null +++ b/components/vc-table/src/TableRow.js @@ -0,0 +1,291 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import PropTypes from 'prop-types' +import { connect } from 'mini-store' +import TableCell from './TableCell' +import { warningOnce } from './utils' + +class TableRow extends React.Component { + static propTypes = { + onRow: PropTypes.func, + onRowClick: PropTypes.func, + onRowDoubleClick: PropTypes.func, + onRowContextMenu: PropTypes.func, + onRowMouseEnter: PropTypes.func, + onRowMouseLeave: PropTypes.func, + record: PropTypes.object, + prefixCls: PropTypes.string, + onHover: PropTypes.func, + columns: PropTypes.array, + height: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), + index: PropTypes.number, + rowKey: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]).isRequired, + className: PropTypes.string, + indent: PropTypes.number, + indentSize: PropTypes.number, + hasExpandIcon: PropTypes.func.isRequired, + hovered: PropTypes.bool.isRequired, + visible: PropTypes.bool.isRequired, + store: PropTypes.object.isRequired, + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + renderExpandIcon: PropTypes.func, + renderExpandIconCell: PropTypes.func, + components: PropTypes.any, + expandedRow: PropTypes.bool, + isAnyColumnsFixed: PropTypes.bool, + ancestorKeys: PropTypes.array.isRequired, + } + + static defaultProps = { + onRow () {}, + expandIconColumnIndex: 0, + expandRowByClick: false, + onHover () {}, + hasExpandIcon () {}, + renderExpandIcon () {}, + renderExpandIconCell () {}, + } + + constructor (props) { + super(props) + + this.shouldRender = props.visible + } + + componentDidMount () { + if (this.shouldRender) { + this.saveRowRef() + } + } + + componentWillReceiveProps (nextProps) { + if (this.props.visible || (!this.props.visible && nextProps.visible)) { + this.shouldRender = true + } + } + + shouldComponentUpdate (nextProps) { + return !!(this.props.visible || nextProps.visible) + } + + componentDidUpdate () { + if (this.shouldRender && !this.rowRef) { + this.saveRowRef() + } + } + + onRowClick = (event) => { + const { record, index, onRowClick } = this.props + if (onRowClick) { + onRowClick(record, index, event) + } + } + + onRowDoubleClick = (event) => { + const { record, index, onRowDoubleClick } = this.props + if (onRowDoubleClick) { + onRowDoubleClick(record, index, event) + } + } + + onContextMenu = (event) => { + const { record, index, onRowContextMenu } = this.props + if (onRowContextMenu) { + onRowContextMenu(record, index, event) + } + } + + onMouseEnter = (event) => { + const { record, index, onRowMouseEnter, onHover, rowKey } = this.props + onHover(true, rowKey) + if (onRowMouseEnter) { + onRowMouseEnter(record, index, event) + } + } + + onMouseLeave = (event) => { + const { record, index, onRowMouseLeave, onHover, rowKey } = this.props + onHover(false, rowKey) + if (onRowMouseLeave) { + onRowMouseLeave(record, index, event) + } + } + + setExpanedRowHeight () { + const { store, rowKey } = this.props + let { expandedRowsHeight } = store.getState() + const height = this.rowRef.getBoundingClientRect().height + expandedRowsHeight = { + ...expandedRowsHeight, + [rowKey]: height, + } + store.setState({ expandedRowsHeight }) + } + + setRowHeight () { + const { store, index } = this.props + const fixedColumnsBodyRowsHeight = store.getState().fixedColumnsBodyRowsHeight.slice() + const height = this.rowRef.getBoundingClientRect().height + fixedColumnsBodyRowsHeight[index] = height + store.setState({ fixedColumnsBodyRowsHeight }) + } + + getStyle () { + const { height, visible } = this.props + + if (height && height !== this.style.height) { + this.style = { ...this.style, height } + } + + if (!visible && !this.style.display) { + this.style = { ...this.style, display: 'none' } + } + + return this.style + } + + saveRowRef () { + this.rowRef = ReactDOM.findDOMNode(this) + + const { isAnyColumnsFixed, fixed, expandedRow, ancestorKeys } = this.props + + if (!isAnyColumnsFixed) { + return + } + + if (!fixed && expandedRow) { + this.setExpanedRowHeight() + } + + if (!fixed && ancestorKeys.length >= 0) { + this.setRowHeight() + } + } + + render () { + if (!this.shouldRender) { + return null + } + + const { + prefixCls, + 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) { + className += ` ${prefixCls}-hover` + } + + const cells = [] + + renderExpandIconCell(cells) + + for (let i = 0; i < columns.length; i++) { + const column = columns[i] + + warningOnce( + column.onCellClick === undefined, + 'column[onCellClick] is deprecated, please use column[onCell] instead.', + ) + + cells.push( + + ) + } + + 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} + + ) + } +} + +function getRowHeight (state, props) { + const { expandedRowsHeight, fixedColumnsBodyRowsHeight } = state + const { fixed, index, rowKey } = props + + if (!fixed) { + return null + } + + if (expandedRowsHeight[rowKey]) { + return expandedRowsHeight[rowKey] + } + + if (fixedColumnsBodyRowsHeight[index]) { + return fixedColumnsBodyRowsHeight[index] + } + + return null +} + +export default connect((state, props) => { + const { currentHoverKey, expandedRowKeys } = state + const { rowKey, ancestorKeys } = props + const visible = ancestorKeys.length === 0 || ancestorKeys.every(k => ~expandedRowKeys.indexOf(k)) + + return ({ + visible, + hovered: currentHoverKey === rowKey, + height: getRowHeight(state, props), + }) +})(TableRow) diff --git a/components/vc-table/src/utils.js b/components/vc-table/src/utils.js new file mode 100644 index 000000000..5d3ef7c37 --- /dev/null +++ b/components/vc-table/src/utils.js @@ -0,0 +1,84 @@ +import warning from 'warning' + +let scrollbarSize + +// Measure scrollbar width for padding body during modal show/hide +const scrollbarMeasure = { + position: 'absolute', + top: '-9999px', + width: '50px', + height: '50px', + overflow: 'scroll', +} + +export function measureScrollbar (direction = 'vertical') { + if (typeof document === 'undefined' || typeof window === 'undefined') { + return 0 + } + if (scrollbarSize) { + return scrollbarSize + } + const scrollDiv = document.createElement('div') + for (const scrollProp in scrollbarMeasure) { + if (scrollbarMeasure.hasOwnProperty(scrollProp)) { + scrollDiv.style[scrollProp] = scrollbarMeasure[scrollProp] + } + } + document.body.appendChild(scrollDiv) + let size = 0 + if (direction === 'vertical') { + size = scrollDiv.offsetWidth - scrollDiv.clientWidth + } else if (direction === 'horizontal') { + size = scrollDiv.offsetHeight - scrollDiv.clientHeight + } + + document.body.removeChild(scrollDiv) + scrollbarSize = size + return scrollbarSize +} + +export function debounce (func, wait, immediate) { + let timeout + function debounceFunc () { + const context = this + const args = arguments + // https://fb.me/react-event-pooling + if (args[0] && args[0].persist) { + args[0].persist() + } + const later = () => { + timeout = null + if (!immediate) { + func.apply(context, args) + } + } + const callNow = immediate && !timeout + clearTimeout(timeout) + timeout = setTimeout(later, wait) + if (callNow) { + func.apply(context, args) + } + } + debounceFunc.cancel = function cancel () { + if (timeout) { + clearTimeout(timeout) + timeout = null + } + } + return debounceFunc +} + +const warned = {} +export function warningOnce (condition, format, args) { + if (!warned[format]) { + warning(condition, format, args) + warned[format] = !condition + } +} + +export function remove (array, item) { + const index = array.indexOf(item) + const front = array.slice(0, index) + const last = array.slice(index + 1, array.length) + return front.concat(last) +}
; + } + + return ( + + + + ); +}; + +ResizeableTitle.propTypes = { + onResize: PropTypes.func.isRequired, + width: PropTypes.number, +}; + +class Demo extends React.Component { + state = { + 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; + }, + }, + ], + } + + components = { + header: { + cell: ResizeableTitle, + }, + } + + data = [ + { a: '123', key: '1' }, + { a: 'cdd', b: 'edd', key: '2' }, + { a: '1333', c: 'eee', d: 2, key: '3' }, + ] + + handleResize = index => (e, { size }) => { + this.setState(({ columns }) => { + const nextColumns = [...columns]; + nextColumns[index] = { + ...nextColumns[index], + width: size.width, + }; + return { columns: nextColumns }; + }); + } + + render() { + const columns = this.state.columns.map((col, index) => ({ + ...col, + onHeaderCell: (column) => ({ + width: column.width, + onResize: this.handleResize(index), + }), + })); + + return ( +
+

Integrate with react-resizable

+ + + ); + } +} + +ReactDOM.render(, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/dropdown.js b/components/vc-table/demo/dropdown.js new file mode 100644 index 000000000..38c2f5e2c --- /dev/null +++ b/components/vc-table/demo/dropdown.js @@ -0,0 +1,111 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import Menu, { Item, Divider } from 'rc-menu'; +import DropDown from 'rc-dropdown'; +import 'rc-table/assets/index.less'; +import 'rc-dropdown/assets/index.css'; +import 'rc-menu/assets/index.css'; + +const data = []; +for (let i = 0; i < 10; i++) { + data.push({ + key: i, + a: `a${i}`, + b: `b${i}`, + c: `c${i}`, + }); +} + +class Demo extends React.Component { + state = { + visible: false, + } + + filters = [] + + handleVisibleChange = (visible) => { + this.setState({ visible }); + } + + handleSelect = (selected) => { + this.filters.push(selected); + } + + handleDeselect = (key) => { + const index = this.filters.indexOf(key); + if (index !== -1) { + this.filters.splice(index, 1); + } + } + + confirmFilter = () => { + console.log(this.filters.join(',')); + this.setState({ + visible: false, + }); + } + + render() { + const menu = ( + + one + two + three + + + + + + ); + + const columns = [ + { + title: ( +
+ title1 + + filter + +
+ ), key: 'a', dataIndex: 'a', width: 100, + }, + { title: 'title2', key: 'b', dataIndex: 'b', width: 100 }, + { title: 'title3', key: 'c', dataIndex: 'c', width: 200 }, + ]; + + return ( +
record.key} + /> + ); + } +} + +ReactDOM.render( +
+

use dropdown

+ +
, + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/expandedRowRender.js b/components/vc-table/demo/expandedRowRender.js new file mode 100644 index 000000000..6e04bcc12 --- /dev/null +++ b/components/vc-table/demo/expandedRowRender.js @@ -0,0 +1,114 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const tableData = [ + { key: 0, a: '123' }, + { key: 1, a: 'cdd', b: 'edd' }, + { key: 2, a: '1333', c: 'eee', d: 2 }, +]; + +class Demo extends React.Component { + state = { + data: tableData, + expandedRowKeys: [], + expandIconAsCell: true, + expandRowByClick: false, + } + + onExpand = (expanded, record) => { + console.log('onExpand', expanded, record); + } + + onExpandedRowsChange = (rows) => { + this.setState({ + expandedRowKeys: rows, + }); + } + + onExpandIconAsCellChange = (e) => { + this.setState({ + expandIconAsCell: e.target.checked, + }); + } + + onExpandRowByClickChange = (e) => { + this.setState({ + expandRowByClick: e.target.checked, + }); + } + + columns = [ + { title: 'title 1', dataIndex: 'a', key: 'a', width: 100 }, + { title: 'title 2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title 3', dataIndex: 'c', key: 'c', width: 200 }, + { title: 'Operation', dataIndex: '', key: 'x', render: this.renderAction }, + ] + + toggleButton() { + if (this.state.expandedRowKeys.length) { + const closeAll = () => this.setState({ expandedRowKeys: [] }); + return ; + } + const openAll = () => this.setState({ expandedRowKeys: [0, 1, 2] }); + return ; + } + + remove(index) { + const data = this.state.data; + data.splice(index, 1); + this.setState({ data }); + } + + expandedRowRender(record) { + // console.log(record); + return

extra: {record.a}

; + } + + renderAction(o, row, index) { + return this.remove(index)}>Delete; + } + + render() { + const { expandIconAsCell, expandRowByClick, expandedRowKeys, data } = this.state; + return ( +
+ {this.toggleButton()} + + + expandIconAsCell + + + expandRowByClick +
+ + ); + } +} + +ReactDOM.render( +
+

expandedRowRender

+ +
, + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/fixedColumns-auto-height.js b/components/vc-table/demo/fixedColumns-auto-height.js new file mode 100644 index 000000000..5a2973333 --- /dev/null +++ b/components/vc-table/demo/fixedColumns-auto-height.js @@ -0,0 +1,46 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100, fixed: 'left' }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100, fixed: 'left' }, + { title: 'title3', dataIndex: 'c', key: 'c' }, + { title: 'title4', dataIndex: 'b', key: 'd' }, + { title: 'title5', dataIndex: 'b', key: 'e' }, + { title: 'title6', dataIndex: 'b', key: 'f', + render: () =>
我很高
}, + { title: 'title7', dataIndex: 'b', key: 'g' }, + { title: 'title8', dataIndex: 'b', key: 'h' }, + { title: 'title9', dataIndex: 'b', key: 'i' }, + { title: 'title10', dataIndex: 'b', key: 'j' }, + { title: 'title11', dataIndex: 'b', key: 'k' }, + { title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' }, +]; + +const data = [ + { a: '123', b: 'xxxxxxxx', d: 3, key: '1', title: 'hello' }, + { a: 'cdd', b: 'edd12221', d: 3, key: '2', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '3', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '4', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '5', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '6', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '7', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '8', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '9', title: 'hello' }, +]; + +ReactDOM.render( +
+

Fixed columns

+
record.title} + expandIconAsCell + scroll={{ x: 1200 }} + data={data} + /> + +, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/fixedColumns.js b/components/vc-table/demo/fixedColumns.js new file mode 100644 index 000000000..ed05451dc --- /dev/null +++ b/components/vc-table/demo/fixedColumns.js @@ -0,0 +1,45 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100, fixed: 'left' }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100, fixed: 'left' }, + { title: 'title3', dataIndex: 'c', key: 'c' }, + { title: 'title4', dataIndex: 'b', key: 'd' }, + { title: 'title5', dataIndex: 'b', key: 'e' }, + { title: 'title6', dataIndex: 'b', key: 'f' }, + { title:
title7


Hello world!
, dataIndex: 'b', key: 'g' }, + { title: 'title8', dataIndex: 'b', key: 'h' }, + { title: 'title9', dataIndex: 'b', key: 'i' }, + { title: 'title10', dataIndex: 'b', key: 'j' }, + { title: 'title11', dataIndex: 'b', key: 'k' }, + { title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' }, +]; + +const data = [ + { a: '123', b: 'xxxxxxxx', d: 3, key: '1' }, + { a: 'cdd', b: 'edd12221', d: 3, key: '2' }, + { a: '133', c: 'edd12221', d: 2, key: '3' }, + { a: '133', c: 'edd12221', d: 2, key: '4' }, + { a: '133', c: 'edd12221', d: 2, key: '5' }, + { a: '133', c: 'edd12221', d: 2, key: '6' }, + { a: '133', c: 'edd12221', d: 2, key: '7' }, + { a: '133', c: 'edd12221', d: 2, key: '8' }, + { a: '133', c: 'edd12221', d: 2, key: '9' }, +]; + +ReactDOM.render( +
+

Fixed columns

+
record.title} + expandIconAsCell + scroll={{ x: 1200 }} + data={data} + /> + +, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/fixedColumnsAndHeader.js b/components/vc-table/demo/fixedColumnsAndHeader.js new file mode 100644 index 000000000..e62776a59 --- /dev/null +++ b/components/vc-table/demo/fixedColumnsAndHeader.js @@ -0,0 +1,39 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100, fixed: 'left' }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100, fixed: 'left' }, + { title: 'title3', dataIndex: 'c', key: 'c', width: 150 }, + { title: 'title4', dataIndex: 'c', key: 'd', width: 150 }, + { title: 'title5', dataIndex: 'c', key: 'e', width: 150 }, + { title: 'title6', dataIndex: 'c', key: 'f', width: 150 }, + { title: 'title7', dataIndex: 'c', key: 'g', width: 150 }, + { title: 'title8', dataIndex: 'c', key: 'h', width: 150 }, + { title: 'title9', dataIndex: 'b', key: 'i', width: 150 }, + { title: 'title10', dataIndex: 'b', key: 'j', width: 150 }, + { title: 'title11', dataIndex: 'b', key: 'k', width: 150 }, + { title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' }, +]; + +const data = [ + { a: 'aaa', b: 'bbb', c: '内容内容内容内容内容', d: 3, key: '1' }, + { a: 'aaa', b: 'bbb', c: '内容内容内容内容内容', d: 3, key: '2' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '3' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '4' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '5' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '6' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '7' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '8' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '9' }, +]; + +ReactDOM.render( +
+

Fixed columns and header

+
+ +, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/fixedColumnsAndHeaderSyncRowHeight.js b/components/vc-table/demo/fixedColumnsAndHeaderSyncRowHeight.js new file mode 100644 index 000000000..932bdfa0f --- /dev/null +++ b/components/vc-table/demo/fixedColumnsAndHeaderSyncRowHeight.js @@ -0,0 +1,39 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100, fixed: 'left' }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100, fixed: 'left' }, + { title: 'titletitle3', dataIndex: 'c', key: 'c' }, + { title: 'title4', dataIndex: 'c', key: 'd', width: 150 }, + { title: 'title5', dataIndex: 'c', key: 'e', width: 150 }, + { title: 'title6', dataIndex: 'c', key: 'f', width: 150 }, + { title: 'title7', dataIndex: 'c', key: 'g', width: 150 }, + { title: 'title8', dataIndex: 'c', key: 'h', width: 150 }, + { title: 'title9', dataIndex: 'b', key: 'i', width: 150 }, + { title: 'title10', dataIndex: 'b', key: 'j', width: 150 }, + { title: 'title11', dataIndex: 'b', key: 'k', width: 150 }, + { title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' }, +]; + +const data = [ + { a: 'aaa', b: 'bbb', c: '内容内容内容内容内容', d: 3, key: '1' }, + { a: 'aaa', b: 'bbb', c: '内容内容内容内容内容', d: 3, key: '2' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '3' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '4' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '5' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '6' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '7' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '8' }, + { a: 'aaa', c: '内容内容内容内容内容', d: 2, key: '9' }, +]; + +ReactDOM.render( +
+

Fixed columns and header, resize window for test

+
+ +, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/fixedColumnsWhenResize.js b/components/vc-table/demo/fixedColumnsWhenResize.js new file mode 100644 index 000000000..a59b2c2c0 --- /dev/null +++ b/components/vc-table/demo/fixedColumnsWhenResize.js @@ -0,0 +1,45 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100, fixed: 'left' }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100, fixed: 'left' }, + { title: 'title3', dataIndex: 'c', key: 'c' }, + { title: 'title4', dataIndex: 'b', key: 'd' }, + { title: 'title5', dataIndex: 'b', key: 'e' }, + { title: 'title6', dataIndex: 'b', key: 'f' }, + { title:
title7


Hello world!
, dataIndex: 'b', key: 'g' }, + { title: 'title8', dataIndex: 'b', key: 'h' }, + { title: 'title9', dataIndex: 'b', key: 'i' }, + { title: 'title10', dataIndex: 'b', key: 'j' }, + { title: 'title11', dataIndex: 'b', key: 'k' }, + { title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' }, +]; + +const data = [ + { a: '123', b: 'xxxxxxxx', d: 3, key: '1' }, + { a: 'cdd', b: 'edd12221', d: 3, key: '2' }, + { a: '133', c: 'edd12221', d: 2, key: '3' }, + { a: '133', c: 'edd12221', d: 2, key: '4' }, + { a: '133', c: 'edd12221', d: 2, key: '5' }, + { a: '133', c: 'edd12221', d: 2, key: '6' }, + { a: '133', c: 'edd12221', d: 2, key: '7' }, + { a: '133', c: 'edd12221', d: 2, key: '8' }, + { a: '133', c: 'edd12221', d: 2, key: '9' }, +]; + +ReactDOM.render( +
+

See fixed columns when you resize window

+
record.title} + expandIconAsCell + scroll={{ x: 800 }} + data={data} + /> + +, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/grouping-columns.js b/components/vc-table/demo/grouping-columns.js new file mode 100644 index 000000000..699a5ce0f --- /dev/null +++ b/components/vc-table/demo/grouping-columns.js @@ -0,0 +1,100 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; +import 'rc-table/assets/bordered.less'; + +const columns = [ + { + title: '姓名', + dataIndex: 'name', + key: 'name', + }, + { + title: '其它', + children: [ + { + title: '年龄', + dataIndex: 'age', + key: 'age', + }, + { + title: '住址', + children: [ + { + title: '街道', + dataIndex: 'street', + key: 'street', + }, + { + title: '小区', + children: [ + { + title: '单元', + dataIndex: 'building', + key: 'building', + }, + { + title: '门牌', + dataIndex: 'number', + key: 'number', + }, + ], + }, + ], + }, + ], + }, + { + title: '公司', + children: [ + { + title: '地址', + dataIndex: 'companyAddress', + key: 'companyAddress', + }, + { + title: '名称', + dataIndex: 'companyName', + key: 'companyName', + }, + ], + }, + { + title: '性别', + dataIndex: 'gender', + key: 'gender', + }, +]; + + +const data = [{ + key: '1', + name: '胡彦斌', + age: 32, + street: '拱墅区和睦街道', + building: 1, + number: 2033, + companyAddress: '西湖区湖底公园', + companyName: '湖底有限公司', + gender: '男', +}, { + key: '2', + name: '胡彦祖', + age: 42, + street: '拱墅区和睦街道', + building: 3, + number: 2035, + companyAddress: '西湖区湖底公园', + companyName: '湖底有限公司', + gender: '男', +}]; + +ReactDOM.render( +
+

grouping columns

+
+ , + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/hide-header.js b/components/vc-table/demo/hide-header.js new file mode 100644 index 000000000..ca1db1c6a --- /dev/null +++ b/components/vc-table/demo/hide-header.js @@ -0,0 +1,34 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +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' }, +]; + +ReactDOM.render( +
+

hide table head

+
+ , + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/jsx.js b/components/vc-table/demo/jsx.js new file mode 100644 index 000000000..d39118b4a --- /dev/null +++ b/components/vc-table/demo/jsx.js @@ -0,0 +1,49 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const { ColumnGroup, Column } = Table; + +const data = [ + { a: '123', key: '1' }, + { a: 'cdd', b: 'edd', key: '2' }, + { a: '1333', c: 'eee', d: 2, key: '3' }, +]; + +ReactDOM.render( +
+

JSX table

+
+ + + + + + Operations} + /> +
+
, + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/key.js b/components/vc-table/demo/key.js new file mode 100644 index 000000000..d468ba201 --- /dev/null +++ b/components/vc-table/demo/key.js @@ -0,0 +1,70 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const CheckBox = ({ id }) => ( + +); + +class Demo extends React.Component { + static propTypes = { + data: PropTypes.array.isRequired, + } + + constructor(props) { + super(props); + + this.state = { + data: props.data, + }; + } + + remove(index) { + const rows = this.state.data; + rows.splice(index, 1); + this.setState({ + data: rows, + }); + } + + handleClick = (index) => () => { + this.remove(index); + } + + checkbox(a) { + return ; + } + + renderAction = (o, row, index) => { + return Delete; + } + + render() { + const state = this.state; + const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100, render: this.checkbox }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title3', dataIndex: 'c', key: 'c', width: 200 }, + { title: 'Operations', dataIndex: '', key: 'x', render: this.renderAction }, + ]; + return ( + record.a} /> + ); + } +} + +const data = [{ a: '123' }, { a: 'cdd', b: 'edd' }, { a: '1333', c: 'eee', d: 2 }]; + +ReactDOM.render( +
+

specify key

+ +
, + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/nested.js b/components/vc-table/demo/nested.js new file mode 100644 index 000000000..860ebb0b5 --- /dev/null +++ b/components/vc-table/demo/nested.js @@ -0,0 +1,45 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'First Name', dataIndex: 'names.first', key: 'a', width: 100 }, + { title: 'Last Name', dataIndex: 'names.last', key: 'b', width: 100 }, + { title: 'Age', dataIndex: 'age', key: 'c', width: 100 }, +]; + +const data = [{ + age: '23', + names: { + first: 'John', + last: 'Doe', + }, + key: '1', +}, { + age: '36', + names: { + first: 'Terry', + last: 'Garner', + }, + key: '2', +}, { + age: '52', + names: { + first: 'Thomas', + last: 'Goodwin', + }, + key: '3', +}]; + +ReactDOM.render( +
+

Nested data table

+
+ , + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/no-data.js b/components/vc-table/demo/no-data.js new file mode 100644 index 000000000..670384753 --- /dev/null +++ b/components/vc-table/demo/no-data.js @@ -0,0 +1,26 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +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 = []; + +ReactDOM.render( +
+

simple table

+
+ , + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/react-dnd.js b/components/vc-table/demo/react-dnd.js new file mode 100644 index 000000000..886b6f0b0 --- /dev/null +++ b/components/vc-table/demo/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/components/vc-table/demo/rowAndCellClick.js b/components/vc-table/demo/rowAndCellClick.js new file mode 100644 index 000000000..e8dd239bc --- /dev/null +++ b/components/vc-table/demo/rowAndCellClick.js @@ -0,0 +1,105 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const onRowClick = (record, index, event) => { + console.log(`Click nth(${index}) row of parent, record.name: ${record.name}`); + // See https://facebook.github.io/react/docs/events.html for original click event details. + if (event.shiftKey) { + console.log('Shift + mouse click triggered.'); + } +}; + +const onRowDoubleClick = (record, index) => { + console.log(`Double click nth(${index}) row of parent, record.name: ${record.name}`); +}; + +const columns = [{ + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 400, +}, { + title: 'Age', + dataIndex: 'age', + key: 'age', + width: 100, + render: (text) => ( + {text} (Trigger Cell Click) + ), + onCell: (record) => ({ + onClick(e) { + console.log('Click cell', record, e.target); + }, + }), +}, { + title: 'Address', + dataIndex: 'address', + key: 'address', + width: 200, +}]; + +const data = [{ + key: 1, + name: 'a', + age: 32, + address: 'I am a', + children: [{ + key: 11, + name: 'aa', + age: 33, + address: 'I am aa', + }, { + key: 12, + name: 'ab', + age: 33, + address: 'I am ab', + children: [{ + key: 121, + name: 'aba', + age: 33, + address: 'I am aba', + }], + }, { + key: 13, + name: 'ac', + age: 33, + address: 'I am ac', + children: [{ + key: 131, + name: 'aca', + age: 33, + address: 'I am aca', + children: [{ + key: 1311, + name: 'acaa', + age: 33, + address: 'I am acaa', + }, { + key: 1312, + name: 'acab', + age: 33, + address: 'I am acab', + }], + }], + }], +}, { + key: 2, + name: 'b', + age: 32, + address: 'I am b', +}]; + +ReactDOM.render( +
({ + onClick: onRowClick.bind(null, record, index), + onDoubleClick: onRowDoubleClick.bind(null, record, index), + })} + />, + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/scrollX.js b/components/vc-table/demo/scrollX.js new file mode 100644 index 000000000..4659bcea2 --- /dev/null +++ b/components/vc-table/demo/scrollX.js @@ -0,0 +1,34 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100 }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title3', dataIndex: 'c', key: 'c', width: 100 }, + { title: 'title4', dataIndex: 'b', key: 'd', width: 100 }, + { title: 'title5', dataIndex: 'b', key: 'e', width: 100 }, + { title: 'title6', dataIndex: 'b', key: 'f', width: 100 }, + { title: 'title7', dataIndex: 'b', key: 'g', width: 100 }, + { title: 'title8', dataIndex: 'b', key: 'h', width: 100 }, + { title: 'title9', dataIndex: 'b', key: 'i', width: 100 }, + { title: 'title10', dataIndex: 'b', key: 'j', width: 100 }, + { title: 'title11', dataIndex: 'b', key: 'k', width: 100 }, + { title: 'title12', dataIndex: 'b', key: 'l', width: 100 }, +]; + +const data = [ + { a: '123', b: 'xxxxxxxx xxxxxxxx', d: 3, key: '1' }, + { a: 'cdd', b: 'edd12221 edd12221', d: 3, key: '2' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '3' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '4' }, +]; + +ReactDOM.render( +
+

Scroll X

+
+ +, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/scrollXY.js b/components/vc-table/demo/scrollXY.js new file mode 100644 index 000000000..6da56a772 --- /dev/null +++ b/components/vc-table/demo/scrollXY.js @@ -0,0 +1,45 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100 }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title3', dataIndex: 'c', key: 'c', width: 100 }, + { title: 'title4', dataIndex: 'b', key: 'd', width: 100 }, + { title: 'title5', dataIndex: 'b', key: 'e', width: 100 }, + { title: 'title6', dataIndex: 'b', key: 'f', width: 100 }, + { title: 'title7', dataIndex: 'b', key: 'g', width: 100 }, + { title: 'title8', dataIndex: 'b', key: 'h', width: 100 }, + { title: 'title9', dataIndex: 'b', key: 'i', width: 100 }, + { title: 'title10', dataIndex: 'b', key: 'j', width: 100 }, + { title: 'title11', dataIndex: 'b', key: 'k', width: 100 }, + { title: 'title12', dataIndex: 'b', key: 'l', width: 100 }, +]; + +const data = [ + { a: '123', b: 'xxxxxxxx xxxxxxxx', d: 3, key: '1' }, + { a: 'cdd', b: 'edd12221 edd12221', d: 3, key: '2' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '3' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '4' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '5' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '6' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '7' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '8' }, + { a: '133', c: 'edd12221 edd12221', d: 2, key: '9' }, +]; + +ReactDOM.render( +
+

Scroll X/Y

+
+ +, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/scrollY.js b/components/vc-table/demo/scrollY.js new file mode 100644 index 000000000..ae286c5ed --- /dev/null +++ b/components/vc-table/demo/scrollY.js @@ -0,0 +1,62 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const data = []; +for (let i = 0; i < 10; i++) { + data.push({ + key: i, + a: `a${i}`, + b: `b${i}`, + c: `c${i}`, + }); +} + +class Demo extends React.Component { + state = { + showBody: true, + } + + toggleBody = () => { + this.setState({ + showBody: !this.state.showBody, + }); + } + + render() { + const columns = [ + { title: 'title1', key: 'a', dataIndex: 'a', width: 100 }, + { id: '123', title: 'title2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title3', key: 'c', dataIndex: 'c', width: 200 }, + { + title: {this.state.showBody ? '隐藏' : '显示'}体, + key: 'x', + width: 200, + render() { + return Operations; + }, + }, + ]; + return ( +
record.key} + bodyStyle={{ + display: this.state.showBody ? '' : 'none', + }} + /> + ); + } +} + +ReactDOM.render( +
+

scroll body table

+ +
, + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/simple.js b/components/vc-table/demo/simple.js new file mode 100644 index 000000000..c07339aaf --- /dev/null +++ b/components/vc-table/demo/simple.js @@ -0,0 +1,30 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +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' }, +]; + +ReactDOM.render( +
+

simple table

+
+ , + document.getElementById('__react-content') +); diff --git a/components/vc-table/demo/styled-components.js b/components/vc-table/demo/styled-components.js new file mode 100644 index 000000000..fa453e009 --- /dev/null +++ b/components/vc-table/demo/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/components/vc-table/demo/subTable.js b/components/vc-table/demo/subTable.js new file mode 100644 index 000000000..459428e36 --- /dev/null +++ b/components/vc-table/demo/subTable.js @@ -0,0 +1,63 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const data = [ + { + a: 'a1', + }, + { + a: 'a2', + b: 'b2', + children: [ + { + a: 'a2-1', + b: 'b2-1', + }, + { + a: 'a2-2', + b: 'b2-2', + }, + ], + }, + { + a: 'a3', + c: 'c3', + d: 'd3', + }, +]; + +class Demo extends React.Component { + handleClick = (record, e) => { + e.preventDefault(); + console.log(record.a); + } + + render() { + const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100 }, + { title: 'title2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title3', dataIndex: 'c', key: 'c', width: 200 }, + { + title: 'Operations', dataIndex: '', key: 'x', render: (text, record) => { + return this.handleClick(record, e)}>click {record.a}; + }, + }, + ]; + return ( +
+

sub table

+
record.a} + /> + + ); + } +} + +ReactDOM.render(, document.getElementById('__react-content')); diff --git a/components/vc-table/demo/title-and-footer.js b/components/vc-table/demo/title-and-footer.js new file mode 100644 index 000000000..92fb0fc8c --- /dev/null +++ b/components/vc-table/demo/title-and-footer.js @@ -0,0 +1,35 @@ +/* eslint-disable no-console,func-names,react/no-multi-comp */ +import React from 'react'; +import ReactDOM from 'react-dom'; +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' }, +]; + +ReactDOM.render( +
+

title and footer

+
Title: {currentData.length} items
} + footer={currentData =>
Footer: {currentData.length} items
} + /> + , + document.getElementById('__react-content') +); diff --git a/components/vc-table/index.js b/components/vc-table/index.js new file mode 100644 index 000000000..c4a842cdc --- /dev/null +++ b/components/vc-table/index.js @@ -0,0 +1,9 @@ +import Table from './src/Table' +import Column from './src/Column' +import ColumnGroup from './src/ColumnGroup' + +Table.Column = Column +Table.ColumnGroup = ColumnGroup + +export default Table +export { Column, ColumnGroup } diff --git a/components/vc-table/src/BaseTable.jsx b/components/vc-table/src/BaseTable.jsx new file mode 100644 index 000000000..c2d0811eb --- /dev/null +++ b/components/vc-table/src/BaseTable.jsx @@ -0,0 +1,189 @@ + +import PropTypes from '../../_util/vue-types' +import ColGroup from './ColGroup' +import TableHeader from './TableHeader' +import TableRow from './TableRow' +import ExpandableRow from './ExpandableRow' +import { mergeProps } from '../../_util/props-util' + +const BaseTable = { + name: 'BaseTable', + props: { + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + columns: PropTypes.array.isRequired, + tableClassName: PropTypes.string.isRequired, + hasHead: PropTypes.bool.isRequired, + hasBody: PropTypes.bool.isRequired, + store: PropTypes.object.isRequired, + expander: PropTypes.object.isRequired, + getRowKey: PropTypes.func, + isAnyColumnsFixed: PropTypes.bool, + }, + inject: { + table: { default: {}}, + }, + methods: { + handleRowHover (isHover, key) { + this.props.store.setState({ + currentHoverKey: isHover ? key : null, + }) + }, + + renderRows (renderData, indent, ancestorKeys = []) { + const { + columnManager, components, + prefixCls, + childrenColumnName, + rowClassName, + // rowRef, + $listeners: { + rowClick: onRowClick, + rowDoubleclick: onRowDoubleClick, + rowContextmenu: onRowContextMenu, + rowMouseenter: onRowMouseEnter, + rowMouseleave: onRowMouseLeave, + row: onRow, + }, + } = this.table + const { getRowKey, fixed, expander, isAnyColumnsFixed } = this + + const rows = [] + + for (let i = 0; i < renderData.length; i++) { + const record = renderData[i] + const key = getRowKey(record, i) + const className = typeof rowClassName === 'string' + ? rowClassName + : rowClassName(record, i, indent) + + const onHoverProps = {} + if (columnManager.isAnyColumnsFixed()) { + onHoverProps.hover = this.handleRowHover + } + + let leafColumns + if (fixed === 'left') { + leafColumns = columnManager.leftLeafColumns() + } else if (fixed === 'right') { + leafColumns = columnManager.rightLeafColumns() + } else { + leafColumns = columnManager.leafColumns() + } + + const rowPrefixCls = `${prefixCls}-row` + const expandableRowProps = { + props: { + ...expander.props, + fixed, + index: i, + prefixCls: rowPrefixCls, + record, + rowKey: key, + needIndentSpaced: expander.needIndentSpaced, + }, + key, + on: { + rowClick: onRowClick, + expandedChange: expander.handleExpandChange, + }, + scopedSlots: { + default: (expandableRow) => { + const tableRowProps = mergeProps({ + props: { + fixed, + indent, + record, + index: i, + prefixCls: rowPrefixCls, + childrenColumnName: childrenColumnName, + columns: leafColumns, + rowKey: key, + ancestorKeys, + components, + isAnyColumnsFixed, + }, + on: { + row: onRow, + rowDoubleclick: onRowDoubleClick, + rowContextmenu: onRowContextMenu, + rowMouseenter: onRowMouseEnter, + rowMouseleave: onRowMouseLeave, + ...onHoverProps, + }, + class: className, + ref: `row_${i}_${indent}`, + }, expandableRow) + return ( + + ) + }, + }, + } + const row = ( + + ) + + rows.push(row) + + expander.renderRows( + this.renderRows, + rows, + record, + i, + indent, + fixed, + key, + ancestorKeys + ) + } + return rows + }, + }, + + render () { + const { components, prefixCls, scroll, data, getBodyWrapper } = this.table + const { expander, tableClassName, hasHead, hasBody, fixed, columns } = this + const tableStyle = {} + + if (!fixed && scroll.x) { + // not set width, then use content fixed width + if (scroll.x === true) { + tableStyle.tableLayout = 'fixed' + } else { + tableStyle.width = scroll.x + } + } + + const Table = hasBody ? components.table : 'table' + const BodyWrapper = components.body.wrapper + + let body + if (hasBody) { + body = ( + + {this.renderRows(data, 0)} + + ) + if (getBodyWrapper) { + body = getBodyWrapper(body) + } + } + + return ( +
+ + {hasHead && } + {body} +
+ ) + }, +} + +export default BaseTable diff --git a/components/vc-table/src/BodyTable.jsx b/components/vc-table/src/BodyTable.jsx new file mode 100644 index 000000000..6cb8d69be --- /dev/null +++ b/components/vc-table/src/BodyTable.jsx @@ -0,0 +1,117 @@ +import PropTypes from '../../_util/vue-types' +import { measureScrollbar } from './utils' +import BaseTable from './BaseTable' + +export default { + name: 'BodyTable', + props: { + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + columns: PropTypes.array.isRequired, + tableClassName: PropTypes.string.isRequired, + handleBodyScroll: PropTypes.func.isRequired, + getRowKey: PropTypes.func.isRequired, + expander: PropTypes.object.isRequired, + isAnyColumnsFixed: PropTypes.bool, + }, + inject: { + table: { default: {}}, + }, + render () { + const { prefixCls, scroll } = this.table + const { + columns, + fixed, + tableClassName, + getRowKey, + handleBodyScroll, + expander, + isAnyColumnsFixed, + } = this + let { useFixedHeader } = this.table + const bodyStyle = { ...this.table.bodyStyle } + const innerBodyStyle = {} + + if (scroll.x || fixed) { + bodyStyle.overflowX = bodyStyle.overflowX || 'auto' + // Fix weired webkit render bug + // https://github.com/ant-design/ant-design/issues/7783 + bodyStyle.WebkitTransform = 'translate3d (0, 0, 0)' + } + + if (scroll.y) { + // maxHeight will make fixed-Table scrolling not working + // so we only set maxHeight to body-Table here + if (fixed) { + innerBodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y + innerBodyStyle.overflowY = bodyStyle.overflowY || 'scroll' + } else { + bodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y + } + bodyStyle.overflowY = bodyStyle.overflowY || 'scroll' + useFixedHeader = true + + // Add negative margin bottom for scroll bar overflow bug + const scrollbarWidth = measureScrollbar() + if (scrollbarWidth > 0 && fixed) { + bodyStyle.marginBottom = `-${scrollbarWidth}px` + bodyStyle.paddingBottom = '0px' + } + } + + const baseTable = ( + + ) + + if (fixed && columns.length) { + let refName + if (columns[0].fixed === 'left' || columns[0].fixed === true) { + refName = 'fixedColumnsBodyLeft' + } else if (columns[0].fixed === 'right') { + refName = 'fixedColumnsBodyRight' + } + delete bodyStyle.overflowX + delete bodyStyle.overflowY + return ( +
+
+ {baseTable} +
+
+ ) + } + return ( +
+ {baseTable} +
+ ) + }, + +} + diff --git a/components/vc-table/src/ColGroup.jsx b/components/vc-table/src/ColGroup.jsx new file mode 100644 index 000000000..0df1afe27 --- /dev/null +++ b/components/vc-table/src/ColGroup.jsx @@ -0,0 +1,53 @@ +import PropTypes from '../../_util/vue-types' + +export default { + name: 'ColGroup', + props: { + fixed: PropTypes.string, + }, + inject: { + table: { default: {}}, + }, + render () { + const { fixed, table } = this + const { prefixCls, expandIconAsCell } = table + + let cols = [] + + if (expandIconAsCell && fixed !== 'right') { + cols.push( +
+ {this.renderExpandIcon()} +