commit 16b56350fb9c400dc9200d48f0c453603deb6222
Author: Boyuzhou <386607913@qq.com>
Date: Mon Dec 26 16:52:39 2016 +0800
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..25052a1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+
+dist
+coverage
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..b6fadc1
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,28 @@
+bower_components/
+*.cfg
+node_modules/
+nohup.out
+*.iml
+.idea/
+.ipr
+.iws
+*~
+~*
+*.diff
+*.log
+*.patch
+*.bak
+.DS_Store
+Thumbs.db
+.project
+.*proj
+.svn/
+*.swp
+out/
+.build
+node_modules
+_site
+sea-modules
+spm_modules
+.cache
+coverage
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e5a6d34
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,22 @@
+sudo: false
+
+language: node_js
+
+node_js:
+ - "6"
+
+service_name: travis-ci
+repo_token: add
+
+env:
+ global:
+ - NODE_ENV=travisci
+ - NPM_CONFIG_PROGRESS="false"
+
+before_install:
+ - npm install -g bee-tools
+
+script: npm test
+
+after_script:
+ - npm run coveralls
diff --git a/HISTORY.md b/HISTORY.md
new file mode 100644
index 0000000..e69de29
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4b4e44e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+# bee-table
+
+[![npm version](https://img.shields.io/npm/v/bee-table.svg)](https://www.npmjs.com/package/bee-table)
+[![Build Status](https://img.shields.io/travis/tinper-bee/bee-table/master.svg)](https://travis-ci.org/tinper-bee/bee-table)
+[![Coverage Status](https://coveralls.io/repos/github/tinper-bee/bee-table/badge.svg?branch=master)](https://coveralls.io/github/tinper-bee/bee-table?branch=master)
+[![devDependency Status](https://img.shields.io/david/dev/tinper-bee/bee-table.svg)](https://david-dm.org/tinper-bee/bee-table#info=devDependencies)
+[![NPM downloads](http://img.shields.io/npm/dm/bee-table.svg?style=flat)](https://npmjs.org/package/bee-table)
+[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tinper-bee/bee-table.svg)](http://isitmaintained.com/project/tinper-bee/bee-table "Average time to resolve an issue")
+[![Percentage of issues still open](http://isitmaintained.com/badge/open/tinper-bee/bee-table.svg)](http://isitmaintained.com/project/tinper-bee/bee-table "Percentage of issues still open")
+
+## Browser Support
+
+|![IE](https://raw.github.com/alrra/browser-logos/master/internet-explorer/internet-explorer_48x48.png) | ![Chrome](https://raw.github.com/alrra/browser-logos/master/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/firefox/firefox_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/opera/opera_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/safari/safari_48x48.png)|
+| --- | --- | --- | --- | --- |
+| IE 9+ ✔ | Chrome 31.0+ ✔ | Firefox 31.0+ ✔ | Opera 30.0+ ✔ | Safari 7.0+ ✔ |
+
+
+react bee-table component for tinper-bee
+
+some description...
+
+## 使用方法
+
+```js
+
+```
+
+
+
+## API
+
+|参数|说明|类型|默认值|
+|:--|:---:|:--:|---:|
+
+#### 开发调试
+
+```sh
+$ npm install -g bee-tools
+$ git clone https://github.com/tinper-bee/bee-table
+$ cd bee-table
+$ npm install
+$ npm run dev
+```
diff --git a/README_EN.md b/README_EN.md
new file mode 100644
index 0000000..08ea79e
--- /dev/null
+++ b/README_EN.md
@@ -0,0 +1,43 @@
+# bee-dropdown
+
+[![npm version](https://img.shields.io/npm/v/bee-dropdown.svg)](https://www.npmjs.com/package/bee-dropdown)
+[![Build Status](https://img.shields.io/travis/tinper-bee/bee-dropdown/master.svg)](https://travis-ci.org/tinper-bee/bee-dropdown)
+[![Coverage Status](https://coveralls.io/repos/github/tinper-bee/bee-dropdown/badge.svg?branch=master)](https://coveralls.io/github/tinper-bee/bee-dropdown?branch=master)
+[![devDependency Status](https://img.shields.io/david/dev/tinper-bee/bee-dropdown.svg)](https://david-dm.org/tinper-bee/bee-dropdown#info=devDependencies)
+[![NPM downloads](http://img.shields.io/npm/dm/<%= packageName%>.svg?style=flat)](https://npmjs.org/package/<%= packageName%>)
+[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tinper-bee/<%= packageName%>.svg)](http://isitmaintained.com/project/tinper-bee/<%= packageName%> "Average time to resolve an issue")
+[![Percentage of issues still open](http://isitmaintained.com/badge/open/tinper-bee/<%= packageName%>.svg)](http://isitmaintained.com/project/tinper-bee/<%= packageName%> "Percentage of issues still open")
+
+## Browser Support
+
+|![IE](https://raw.github.com/alrra/browser-logos/master/internet-explorer/internet-explorer_48x48.png) | ![Chrome](https://raw.github.com/alrra/browser-logos/master/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/firefox/firefox_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/opera/opera_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/safari/safari_48x48.png)|
+| --- | --- | --- | --- | --- |
+| IE 9+ ✔ | Chrome 31.0+ ✔ | Firefox 31.0+ ✔ | Opera 30.0+ ✔ | Safari 7.0+ ✔ |
+
+
+react bee-dropdown component for tinper-bee
+
+
+## Usage
+
+```js
+
+
+```
+
+
+
+## API
+
+|参数|说明|类型|默认值|
+|:--|:---:|:--:|---:|
+
+#### develop
+
+```sh
+$ npm install -g bee-tools
+$ git clone https://github.com/tinper-bee/bee-dropdown
+$ cd bee-dropdown
+$ npm install
+$ npm run dev
+```
diff --git a/demo/TableDemo.js b/demo/TableDemo.js
new file mode 100644
index 0000000..dd841f7
--- /dev/null
+++ b/demo/TableDemo.js
@@ -0,0 +1,5 @@
+import Table from '../src/index';
+import React, { Component } from 'react';
+import ReactDOM from 'react-dom';
+class Demo extends Component {render(){return(
)}}
+export default Demo;
\ No newline at end of file
diff --git a/demo/TableDemo.scss b/demo/TableDemo.scss
new file mode 100644
index 0000000..40f2366
--- /dev/null
+++ b/demo/TableDemo.scss
@@ -0,0 +1,6 @@
+@import "../node_modules/tinper-bee-core/scss/index.scss";
+@import "../src/Table.scss";
+@import "../node_modules/bee-panel/src/Panel.scss";
+@import "../node_modules/bee-layout/src/Layout.scss";
+@import "../node_modules/bee-button/src/Button.scss";
+@import "../node_modules/bee-transition/src/Transition.scss";
\ No newline at end of file
diff --git a/demo/atom-one-dark.css b/demo/atom-one-dark.css
new file mode 100644
index 0000000..1616aaf
--- /dev/null
+++ b/demo/atom-one-dark.css
@@ -0,0 +1,96 @@
+/*
+
+Atom One Dark by Daniel Gamage
+Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax
+
+base: #282c34
+mono-1: #abb2bf
+mono-2: #818896
+mono-3: #5c6370
+hue-1: #56b6c2
+hue-2: #61aeee
+hue-3: #c678dd
+hue-4: #98c379
+hue-5: #e06c75
+hue-5-2: #be5046
+hue-6: #d19a66
+hue-6-2: #e6c07b
+
+*/
+
+.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 0.5em;
+ color: #abb2bf;
+ background: #282c34;
+}
+
+.hljs-comment,
+.hljs-quote {
+ color: #5c6370;
+ font-style: italic;
+}
+
+.hljs-doctag,
+.hljs-keyword,
+.hljs-formula {
+ color: #c678dd;
+}
+
+.hljs-section,
+.hljs-name,
+.hljs-selector-tag,
+.hljs-deletion,
+.hljs-subst {
+ color: #e06c75;
+}
+
+.hljs-literal {
+ color: #56b6c2;
+}
+
+.hljs-string,
+.hljs-regexp,
+.hljs-addition,
+.hljs-attribute,
+.hljs-meta-string {
+ color: #98c379;
+}
+
+.hljs-built_in,
+.hljs-class .hljs-title {
+ color: #e6c07b;
+}
+
+.hljs-attr,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-type,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo,
+.hljs-number {
+ color: #d19a66;
+}
+
+.hljs-symbol,
+.hljs-bullet,
+.hljs-link,
+.hljs-meta,
+.hljs-selector-id,
+.hljs-title {
+ color: #61aeee;
+}
+
+.hljs-emphasis {
+ font-style: italic;
+}
+
+.hljs-strong {
+ font-weight: bold;
+}
+
+.hljs-link {
+ text-decoration: underline;
+}
diff --git a/demo/demolist/Demo1.js b/demo/demolist/Demo1.js
new file mode 100644
index 0000000..123fa06
--- /dev/null
+++ b/demo/demolist/Demo1.js
@@ -0,0 +1,15 @@
+/**
+*
+* @title 这是标题
+* @description 这是描述
+*
+*/
+class Demo1 extends Component {
+render () {
+return (
+
+欢迎使用老赵DEMO系统
+
+)
+}
+}
\ No newline at end of file
diff --git a/demo/index-demo-base.js b/demo/index-demo-base.js
new file mode 100644
index 0000000..f17ac8d
--- /dev/null
+++ b/demo/index-demo-base.js
@@ -0,0 +1,83 @@
+
+import { Con, Row, Col } from 'bee-layout';
+import { Panel } from 'bee-panel';
+import Button from 'bee-button';
+import React, { Component } from 'react';
+import ReactDOM from 'react-dom';
+import Table from '../src';
+
+
+const CARET = ;
+
+const CARETUP = ;
+
+
+{demolist}
+
+class Demo extends Component {
+ constructor(props){
+ super(props);
+ this.state = {
+ open: false
+ }
+ this.handleClick = this.handleClick.bind(this);
+ }
+ handleClick() {
+ this.setState({ open: !this.state.open })
+ }
+
+ render () {
+ const { title, example, code, desc } = this.props;
+ let caret = this.state.open ? CARETUP : CARET;
+ let text = this.state.open ? "隐藏代码" : "查看代码";
+
+ const footer = (
+
+ );
+ const header = (
+
+
+ { example }
+
+
+
+
+
+ );
+ return (
+
+ { title }
+ { desc }
+
+ { code }
+
+
+ )
+ }
+}
+
+class DemoGroup extends Component {
+ constructor(props){
+ super(props)
+ }
+ render () {
+ return (
+
+ {DemoArray.map((child,index) => {
+
+ return (
+
+ )
+
+ })}
+
+ )
+ }
+}
+
+ReactDOM.render(, document.getElementById('tinperBeeDemo'));
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 0000000..2c5a03a
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,5 @@
+# Table
+## 代码演示
+## API
+|参数|说明|类型|默认值|
+|:---|:-----|:----|:------|
\ No newline at end of file
diff --git a/docs/api_en.md b/docs/api_en.md
new file mode 100644
index 0000000..4375555
--- /dev/null
+++ b/docs/api_en.md
@@ -0,0 +1,5 @@
+## Table
+## Code display
+## API
+|Property|Description|Type|Default|
+|:---|:-----|:----|:------|
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..dfc6777
--- /dev/null
+++ b/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+ tinper-bee demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d4c5748
--- /dev/null
+++ b/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "bee-table",
+ "version": "0.0.1",
+ "description": "Table ui component for react",
+ "keywords": [
+ "react",
+ "react-component",
+ "bee-table",
+ "iuap-design",
+ "tinper-bee",
+ "Table"
+ ],
+ "engines": {
+ "node": ">=4.0.0"
+ },
+ "homepage": "https://github.com/tinper-bee/bee-table.git",
+ "author": "Yonyou FED",
+ "repository": "http://github.com/tinper-bee/bee-table",
+ "bugs": "https://github.com/tinper-bee/bee-table.git/issues",
+ "license": "MIT",
+
+ "main":"./build/index.js",
+ "config":{
+ "port": 3000
+ },
+ "scripts": {
+ "dev": "bee-tools run start",
+ "build": "bee-tools run build",
+ "lint": "bee-tools run lint",
+ "test": "bee-tools run test",
+ "chrome": "bee-tools run chrome",
+ "coveralls": "bee-tools run coverage",
+ "browsers": "bee-tools run browsers",
+ "pub": "bee-tools run pub"
+ },
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "tinper-bee-core": "latest"
+ },
+ "devDependencies": {
+ "chai": "^3.5.0",
+ "enzyme": "^2.4.1",
+ "react": "~0.14.0",
+ "react-addons-test-utils": "15.3.2",
+ "react-dom": "~0.14.0",
+ "console-polyfill": "~0.2.1",
+ "es5-shim": "~4.1.10",
+ "bee-panel": "latest",
+ "bee-layout": "latest",
+ "bee-button": "latest"
+ }
+}
diff --git a/src/Column.jsx b/src/Column.jsx
new file mode 100644
index 0000000..8a167ea
--- /dev/null
+++ b/src/Column.jsx
@@ -0,0 +1,21 @@
+import { Component, PropTypes } from 'react';
+
+export default class Column extends Component {
+ static propTypes = {
+ className: PropTypes.string,
+ colSpan: PropTypes.number,
+ title: PropTypes.node,
+ dataIndex: PropTypes.string,
+ width: PropTypes.oneOfType([
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+ fixed: PropTypes.oneOf([
+ true,
+ 'left',
+ 'right',
+ ]),
+ render: PropTypes.func,
+ onCellClick: PropTypes.func,
+ }
+}
diff --git a/src/ColumnGroup.jsx b/src/ColumnGroup.jsx
new file mode 100644
index 0000000..6ea54a0
--- /dev/null
+++ b/src/ColumnGroup.jsx
@@ -0,0 +1,7 @@
+import { Component, PropTypes } from 'react';
+
+export default class ColumnGroup extends Component {
+ static propTypes = {
+ title: PropTypes.node,
+ }
+}
diff --git a/src/ColumnManager.js b/src/ColumnManager.js
new file mode 100644
index 0000000..a143288
--- /dev/null
+++ b/src/ColumnManager.js
@@ -0,0 +1,155 @@
+import React from 'react';
+import Column from './Column';
+import ColumnGroup from './ColumnGroup';
+
+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 = [];
+ React.Children.forEach(elements, element => {
+ if (!this.isColumnElement(element)) return;
+ const column = { ...element.props };
+ if (element.key) {
+ column.key = element.key;
+ }
+ if (element.type === ColumnGroup) {
+ column.children = this.normalize(column.children);
+ }
+ columns.push(column);
+ });
+ return columns;
+ }
+
+ isColumnElement(element) {
+ return element && (element.type === Column || element.type === ColumnGroup);
+ }
+
+ 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/src/ExpandIcon.jsx b/src/ExpandIcon.jsx
new file mode 100644
index 0000000..358eff3
--- /dev/null
+++ b/src/ExpandIcon.jsx
@@ -0,0 +1,33 @@
+import React, { PropTypes } from 'react';
+import shallowequal from 'shallowequal';
+
+const ExpandIcon = React.createClass({
+ propTypes: {
+ record: PropTypes.object,
+ prefixCls: PropTypes.string,
+ expandable: PropTypes.any,
+ expanded: PropTypes.bool,
+ needIndentSpaced: PropTypes.bool,
+ onExpand: PropTypes.func,
+ },
+ shouldComponentUpdate(nextProps) {
+ return !shallowequal(nextProps, this.props);
+ },
+ render() {
+ const { expandable, prefixCls, onExpand, needIndentSpaced, expanded, record } = this.props;
+ if (expandable) {
+ const expandClassName = expanded ? 'expanded' : 'collapsed';
+ return (
+ onExpand(!expanded, record, e)}
+ />
+ );
+ } else if (needIndentSpaced) {
+ return ;
+ }
+ return null;
+ },
+});
+
+export default ExpandIcon;
diff --git a/src/Table.jsx b/src/Table.jsx
new file mode 100644
index 0000000..420de16
--- /dev/null
+++ b/src/Table.jsx
@@ -0,0 +1,705 @@
+import React, { PropTypes } from 'react';
+import TableRow from './TableRow';
+import TableHeader from './TableHeader';
+import { measureScrollbar, debounce, warningOnce } from './utils';
+import shallowequal from 'shallowequal';
+import addEventListener from 'rc-util/lib/Dom/addEventListener';
+import ColumnManager from './ColumnManager';
+import createStore from './createStore';
+
+const Table = React.createClass({
+ propTypes: {
+ data: PropTypes.array,
+ expandIconAsCell: PropTypes.bool,
+ defaultExpandAllRows: PropTypes.bool,
+ expandedRowKeys: PropTypes.array,
+ defaultExpandedRowKeys: 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.func,
+ expandedRowClassName: PropTypes.func,
+ childrenColumnName: PropTypes.string,
+ onExpand: PropTypes.func,
+ onExpandedRowsChange: PropTypes.func,
+ indentSize: PropTypes.number,
+ onRowClick: PropTypes.func,
+ onRowDoubleClick: PropTypes.func,
+ expandIconColumnIndex: PropTypes.number,
+ showHeader: PropTypes.bool,
+ title: PropTypes.func,
+ footer: PropTypes.func,
+ emptyText: PropTypes.func,
+ scroll: PropTypes.object,
+ rowRef: PropTypes.func,
+ getBodyWrapper: PropTypes.func,
+ children: PropTypes.node,
+ },
+
+ getDefaultProps() {
+ return {
+ data: [],
+ useFixedHeader: false,
+ expandIconAsCell: false,
+ defaultExpandAllRows: false,
+ defaultExpandedRowKeys: [],
+ rowKey: 'key',
+ rowClassName: () => '',
+ expandedRowClassName: () => '',
+ onExpand() {},
+ onExpandedRowsChange() {},
+ onRowClick() {},
+ onRowDoubleClick() {},
+ prefixCls: 'rc-table',
+ bodyStyle: {},
+ style: {},
+ childrenColumnName: 'children',
+ indentSize: 15,
+ expandIconColumnIndex: 0,
+ showHeader: true,
+ scroll: {},
+ rowRef: () => null,
+ getBodyWrapper: body => body,
+ emptyText: () => 'No Data',
+ };
+ },
+
+ getInitialState() {
+ const props = this.props;
+ let expandedRowKeys = [];
+ let rows = [...props.data];
+ this.columnManager = new ColumnManager(props.columns, props.children);
+ this.store = createStore({ currentHoverKey: null });
+
+ if (props.defaultExpandAllRows) {
+ for (let i = 0; i < rows.length; i++) {
+ const row = rows[i];
+ expandedRowKeys.push(this.getRowKey(row, i));
+ rows = rows.concat(row[props.childrenColumnName] || []);
+ }
+ } else {
+ expandedRowKeys = props.expandedRowKeys || props.defaultExpandedRowKeys;
+ }
+ return {
+ expandedRowKeys,
+ data: props.data,
+ currentHoverKey: null,
+ scrollPosition: 'left',
+ fixedColumnsHeadRowsHeight: [],
+ fixedColumnsBodyRowsHeight: [],
+ };
+ },
+
+ componentDidMount() {
+ this.resetScrollY();
+ if (this.columnManager.isAnyColumnsFixed()) {
+ this.syncFixedTableRowHeight();
+ this.resizeEvent = addEventListener(
+ window, 'resize', debounce(this.syncFixedTableRowHeight, 150)
+ );
+ }
+ },
+
+ componentWillReceiveProps(nextProps) {
+ if ('data' in nextProps) {
+ this.setState({
+ data: nextProps.data,
+ });
+ if (!nextProps.data || nextProps.data.length === 0) {
+ this.resetScrollY();
+ }
+ }
+ if ('expandedRowKeys' in nextProps) {
+ this.setState({
+ expandedRowKeys: nextProps.expandedRowKeys,
+ });
+ }
+ 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() {
+ this.syncFixedTableRowHeight();
+ },
+
+ componentWillUnmount() {
+ if (this.resizeEvent) {
+ this.resizeEvent.remove();
+ }
+ },
+
+ onExpandedRowsChange(expandedRowKeys) {
+ if (!this.props.expandedRowKeys) {
+ this.setState({ expandedRowKeys });
+ }
+ this.props.onExpandedRowsChange(expandedRowKeys);
+ },
+
+ onExpanded(expanded, record, e, index) {
+ if (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ const info = this.findExpandedRow(record);
+ if (typeof info !== 'undefined' && !expanded) {
+ this.onRowDestroy(record, index);
+ } else if (!info && expanded) {
+ const expandedRows = this.getExpandedRows().concat();
+ expandedRows.push(this.getRowKey(record, index));
+ this.onExpandedRowsChange(expandedRows);
+ }
+ this.props.onExpand(expanded, record);
+ },
+
+ onRowDestroy(record, rowIndex) {
+ const expandedRows = this.getExpandedRows().concat();
+ const rowKey = this.getRowKey(record, rowIndex);
+ let index = -1;
+ expandedRows.forEach((r, i) => {
+ if (r === rowKey) {
+ index = i;
+ }
+ });
+ if (index !== -1) {
+ expandedRows.splice(index, 1);
+ }
+ this.onExpandedRowsChange(expandedRows);
+ },
+
+ 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;
+ },
+
+ getExpandedRows() {
+ return this.props.expandedRowKeys || this.state.expandedRowKeys;
+ },
+
+ getHeader(columns, fixed) {
+ const { showHeader, expandIconAsCell, prefixCls } = this.props;
+ const rows = this.getHeaderRows(columns);
+
+ if (expandIconAsCell && fixed !== 'right') {
+ rows[0].unshift({
+ key: 'rc-table-expandIconAsCell',
+ className: `${prefixCls}-expand-icon-th`,
+ title: '',
+ rowSpan: rows.length,
+ });
+ }
+
+ const trStyle = fixed ? this.getHeaderRowStyle(columns, rows) : null;
+
+ return showHeader ? (
+
+ ) : null;
+ },
+
+ 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,
+ };
+ if (column.children) {
+ this.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);
+ },
+
+ getExpandedRow(key, content, visible, className, fixed) {
+ const { prefixCls, expandIconAsCell } = 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' ? content : ' ',
+ }),
+ }];
+ if (expandIconAsCell && fixed !== 'right') {
+ columns.unshift({
+ key: 'expand-icon-placeholder',
+ render: () => null,
+ });
+ }
+ return (
+
+ );
+ },
+
+ getRowsByData(data, visible, indent, columns, fixed) {
+ const props = this.props;
+ const childrenColumnName = props.childrenColumnName;
+ const expandedRowRender = props.expandedRowRender;
+ const expandRowByClick = props.expandRowByClick;
+ const { fixedColumnsBodyRowsHeight } = this.state;
+ let rst = [];
+ const rowClassName = props.rowClassName;
+ const rowRef = props.rowRef;
+ const expandedRowClassName = props.expandedRowClassName;
+ const needIndentSpaced = props.data.some(record => record[childrenColumnName]);
+ const onRowClick = props.onRowClick;
+ const onRowDoubleClick = props.onRowDoubleClick;
+
+ const expandIconAsCell = fixed !== 'right' ? props.expandIconAsCell : false;
+ const expandIconColumnIndex = fixed !== 'right' ? props.expandIconColumnIndex : -1;
+
+ for (let i = 0; i < data.length; i++) {
+ const record = data[i];
+ const key = this.getRowKey(record, i);
+ const childrenColumn = record[childrenColumnName];
+ const isRowExpanded = this.isRowExpanded(record, i);
+ let expandedRowContent;
+ if (expandedRowRender && isRowExpanded) {
+ expandedRowContent = expandedRowRender(record, i, indent);
+ }
+ const className = rowClassName(record, i, indent);
+
+ const onHoverProps = {};
+ if (this.columnManager.isAnyColumnsFixed()) {
+ onHoverProps.onHover = this.handleRowHover;
+ }
+
+ const height = (fixed && fixedColumnsBodyRowsHeight[i]) ?
+ fixedColumnsBodyRowsHeight[i] : null;
+
+
+ let leafColumns;
+ if (fixed === 'left') {
+ leafColumns = this.columnManager.leftLeafColumns();
+ } else if (fixed === 'right') {
+ leafColumns = this.columnManager.rightLeafColumns();
+ } else {
+ leafColumns = this.columnManager.leafColumns();
+ }
+
+ rst.push(
+
+ );
+
+ const subVisible = visible && isRowExpanded;
+
+ if (expandedRowContent && isRowExpanded) {
+ rst.push(this.getExpandedRow(
+ key, expandedRowContent, subVisible, expandedRowClassName(record, i, indent), fixed
+ ));
+ }
+ if (childrenColumn) {
+ rst = rst.concat(this.getRowsByData(
+ childrenColumn, subVisible, indent + 1, columns, fixed
+ ));
+ }
+ }
+ return rst;
+ },
+
+ getRows(columns, fixed) {
+ return this.getRowsByData(this.state.data, true, 0, columns, fixed);
+ },
+
+ getColGroup(columns, fixed) {
+ let cols = [];
+ if (this.props.expandIconAsCell && fixed !== 'right') {
+ cols.push(
+
+ );
+ }
+ let leafColumns;
+ if (fixed === 'left') {
+ leafColumns = this.columnManager.leftLeafColumns();
+ } else if (fixed === 'right') {
+ leafColumns = this.columnManager.rightLeafColumns();
+ } else {
+ leafColumns = this.columnManager.leafColumns();
+ }
+ cols = cols.concat(leafColumns.map(c => {
+ return ;
+ }));
+ return {cols};
+ },
+
+ getLeftFixedTable() {
+ return this.getTable({
+ columns: this.columnManager.leftColumns(),
+ fixed: 'left',
+ });
+ },
+
+ getRightFixedTable() {
+ return this.getTable({
+ columns: this.columnManager.rightColumns(),
+ fixed: 'right',
+ });
+ },
+
+ getTable(options = {}) {
+ const { columns, fixed } = options;
+ const { prefixCls, scroll = {}, getBodyWrapper } = this.props;
+ let { useFixedHeader } = this.props;
+ const bodyStyle = { ...this.props.bodyStyle };
+ const headStyle = {};
+
+ let tableClassName = '';
+ if (scroll.x || fixed) {
+ tableClassName = `${prefixCls}-fixed`;
+ bodyStyle.overflowX = bodyStyle.overflowX || 'auto';
+ }
+
+ if (scroll.y) {
+ // maxHeight will make fixed-Table scrolling not working
+ // so we only set maxHeight to body-Table here
+ if (fixed) {
+ bodyStyle.height = bodyStyle.height || scroll.y;
+ } 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 : headStyle).marginBottom = `-${scrollbarWidth}px`;
+ (fixed ? bodyStyle : headStyle).paddingBottom = '0px';
+ }
+ }
+
+ const renderTable = (hasHead = true, hasBody = true) => {
+ 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 tableBody = hasBody ? getBodyWrapper(
+
+ {this.getRows(columns, fixed)}
+
+ ) : null;
+ return (
+
+ {this.getColGroup(columns, fixed)}
+ {hasHead ? this.getHeader(columns, fixed) : null}
+ {tableBody}
+
+ );
+ };
+
+ let headTable;
+
+ if (useFixedHeader) {
+ headTable = (
+
+ {renderTable(true, false)}
+
+ );
+ }
+
+ let BodyTable = (
+
+ {renderTable(!useFixedHeader)}
+
+ );
+
+ 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;
+ BodyTable = (
+
+
+ {renderTable(!useFixedHeader)}
+
+
+ );
+ }
+
+ return {headTable}{BodyTable};
+ },
+
+ getTitle() {
+ const { title, prefixCls } = this.props;
+ return title ? (
+
+ {title(this.state.data)}
+
+ ) : null;
+ },
+
+ getFooter() {
+ const { footer, prefixCls } = this.props;
+ return footer ? (
+
+ {footer(this.state.data)}
+
+ ) : null;
+ },
+
+ getEmptyText() {
+ const { emptyText, prefixCls, data } = this.props;
+ return !data.length ? (
+
+ {emptyText()}
+
+ ) : null;
+ },
+
+ getHeaderRowStyle(columns, rows) {
+ const { fixedColumnsHeadRowsHeight } = this.state;
+ const headerHeight = fixedColumnsHeadRowsHeight[0];
+ if (headerHeight && columns) {
+ if (headerHeight === 'auto') {
+ return { height: 'auto' };
+ }
+ return { height: headerHeight / rows.length };
+ }
+ return null;
+ },
+
+ syncFixedTableRowHeight() {
+ const { prefixCls } = this.props;
+ const headRows = this.refs.headTable ?
+ this.refs.headTable.querySelectorAll('thead') :
+ this.refs.bodyTable.querySelectorAll('thead');
+ const bodyRows = this.refs.bodyTable.querySelectorAll(`.${prefixCls}-row`) || [];
+ const fixedColumnsHeadRowsHeight = [].map.call(
+ headRows, row => row.getBoundingClientRect().height || 'auto'
+ );
+ const fixedColumnsBodyRowsHeight = [].map.call(
+ bodyRows, row => row.getBoundingClientRect().height || 'auto'
+ );
+ if (shallowequal(this.state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) &&
+ shallowequal(this.state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)) {
+ return;
+ }
+ this.setState({
+ fixedColumnsHeadRowsHeight,
+ fixedColumnsBodyRowsHeight,
+ });
+ },
+
+ resetScrollY() {
+ if (this.refs.headTable) {
+ this.refs.headTable.scrollLeft = 0;
+ }
+ if (this.refs.bodyTable) {
+ this.refs.bodyTable.scrollLeft = 0;
+ }
+ },
+
+ findExpandedRow(record, index) {
+ const rows = this.getExpandedRows().filter(i => i === this.getRowKey(record, index));
+ return rows[0];
+ },
+
+ isRowExpanded(record, index) {
+ return typeof this.findExpandedRow(record, index) !== 'undefined';
+ },
+
+ detectScrollTarget(e) {
+ if (this.scrollTarget !== e.currentTarget) {
+ this.scrollTarget = e.currentTarget;
+ }
+ },
+
+ handleBodyScroll(e) {
+ // Prevent scrollTop setter trigger onScroll event
+ // http://stackoverflow.com/q/1386696
+ if (e.target !== this.scrollTarget) {
+ return;
+ }
+ const { scroll = {} } = this.props;
+ const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this.refs;
+ if (scroll.x && e.target.scrollLeft !== this.lastScrollLeft) {
+ if (e.target === bodyTable && headTable) {
+ headTable.scrollLeft = e.target.scrollLeft;
+ } else if (e.target === headTable && bodyTable) {
+ bodyTable.scrollLeft = e.target.scrollLeft;
+ }
+ if (e.target.scrollLeft === 0) {
+ this.setState({ scrollPosition: 'left' });
+ } else if (e.target.scrollLeft + 1 >=
+ e.target.children[0].getBoundingClientRect().width -
+ e.target.getBoundingClientRect().width) {
+ this.setState({ scrollPosition: 'right' });
+ } else if (this.state.scrollPosition !== 'middle') {
+ this.setState({ scrollPosition: 'middle' });
+ }
+ }
+ if (scroll.y) {
+ if (fixedColumnsBodyLeft && e.target !== fixedColumnsBodyLeft) {
+ fixedColumnsBodyLeft.scrollTop = e.target.scrollTop;
+ }
+ if (fixedColumnsBodyRight && e.target !== fixedColumnsBodyRight) {
+ fixedColumnsBodyRight.scrollTop = e.target.scrollTop;
+ }
+ if (bodyTable && e.target !== bodyTable) {
+ bodyTable.scrollTop = e.target.scrollTop;
+ }
+ }
+ // Remember last scrollLeft for scroll direction detecting.
+ this.lastScrollLeft = e.target.scrollLeft;
+ },
+
+ handleRowHover(isHover, key) {
+ this.store.setState({
+ currentHoverKey: isHover ? key : null,
+ });
+ },
+
+ 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`;
+ }
+ className += ` ${prefixCls}-scroll-position-${this.state.scrollPosition}`;
+
+ const isTableScroll = this.columnManager.isAnyColumnsFixed() ||
+ props.scroll.x ||
+ props.scroll.y;
+
+ return (
+
+ {this.getTitle()}
+
+ {this.columnManager.isAnyColumnsLeftFixed() &&
+
+ {this.getLeftFixedTable()}
+
}
+
+ {this.getTable({ columns: this.columnManager.groupedColumns() })}
+ {this.getEmptyText()}
+ {this.getFooter()}
+
+ {this.columnManager.isAnyColumnsRightFixed() &&
+
+ {this.getRightFixedTable()}
+
}
+
+
+ );
+ },
+});
+
+export default Table;
diff --git a/src/TableCell.jsx b/src/TableCell.jsx
new file mode 100644
index 0000000..37b0f4f
--- /dev/null
+++ b/src/TableCell.jsx
@@ -0,0 +1,74 @@
+import React, { PropTypes } from 'react';
+import objectPath from 'object-path';
+
+const TableCell = React.createClass({
+ propTypes: {
+ record: PropTypes.object,
+ prefixCls: PropTypes.string,
+ index: PropTypes.number,
+ indent: PropTypes.number,
+ indentSize: PropTypes.number,
+ column: PropTypes.object,
+ expandIcon: PropTypes.node,
+ },
+ isInvalidRenderCellText(text) {
+ return text && !React.isValidElement(text) &&
+ Object.prototype.toString.call(text) === '[object Object]';
+ },
+ handleClick(e) {
+ const { record, column: { onCellClick } } = this.props;
+ if (onCellClick) {
+ onCellClick(record, e);
+ }
+ },
+ render() {
+ const { record, indentSize, prefixCls, indent,
+ index, expandIcon, column } = this.props;
+ const { dataIndex, render, className = '' } = column;
+
+ let text = objectPath.get(record, dataIndex);
+ let tdProps;
+ let colSpan;
+ let rowSpan;
+
+ if (render) {
+ text = render(text, record, index);
+ if (this.isInvalidRenderCellText(text)) {
+ tdProps = text.props || {};
+ rowSpan = tdProps.rowSpan;
+ colSpan = tdProps.colSpan;
+ text = text.children;
+ }
+ }
+
+ // 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;
+ }
+ return (
+
+ {indentText}
+ {expandIcon}
+ {text}
+ |
+ );
+ },
+});
+
+export default TableCell;
diff --git a/src/TableHeader.jsx b/src/TableHeader.jsx
new file mode 100644
index 0000000..4a6ca8d
--- /dev/null
+++ b/src/TableHeader.jsx
@@ -0,0 +1,27 @@
+import React, { PropTypes } from 'react';
+import shallowequal from 'shallowequal';
+
+export default React.createClass({
+ propTypes: {
+ prefixCls: PropTypes.string,
+ rowStyle: PropTypes.object,
+ rows: PropTypes.array,
+ },
+ shouldComponentUpdate(nextProps) {
+ return !shallowequal(nextProps, this.props);
+ },
+ render() {
+ const { prefixCls, rowStyle, rows } = this.props;
+ return (
+
+ {
+ rows.map((row, index) => (
+
+ {row.map((cellProps, i) => | )}
+
+ ))
+ }
+
+ );
+ },
+});
diff --git a/src/TableRow.jsx b/src/TableRow.jsx
new file mode 100644
index 0000000..781d08b
--- /dev/null
+++ b/src/TableRow.jsx
@@ -0,0 +1,173 @@
+import React, { PropTypes } from 'react';
+import TableCell from './TableCell';
+import ExpandIcon from './ExpandIcon';
+
+const TableRow = React.createClass({
+ propTypes: {
+ onDestroy: PropTypes.func,
+ onRowClick: PropTypes.func,
+ onRowDoubleClick: PropTypes.func,
+ record: PropTypes.object,
+ prefixCls: PropTypes.string,
+ expandIconColumnIndex: PropTypes.number,
+ onHover: PropTypes.func,
+ columns: PropTypes.array,
+ height: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ visible: PropTypes.bool,
+ index: PropTypes.number,
+ hoverKey: PropTypes.any,
+ expanded: PropTypes.bool,
+ expandable: PropTypes.any,
+ onExpand: PropTypes.func,
+ needIndentSpaced: PropTypes.bool,
+ className: PropTypes.string,
+ indent: PropTypes.number,
+ indentSize: PropTypes.number,
+ expandIconAsCell: PropTypes.bool,
+ expandRowByClick: PropTypes.bool,
+ store: PropTypes.object.isRequired,
+ },
+
+ getDefaultProps() {
+ return {
+ onRowClick() {},
+ onRowDoubleClick() {},
+ onDestroy() {},
+ expandIconColumnIndex: 0,
+ expandRowByClick: false,
+ onHover() {},
+ };
+ },
+
+ getInitialState() {
+ return {
+ hovered: false,
+ };
+ },
+
+ componentDidMount() {
+ const { store, hoverKey } = this.props;
+ this.unsubscribe = store.subscribe(() => {
+ if (store.getState().currentHoverKey === hoverKey) {
+ this.setState({ hovered: true });
+ } else if (this.state.hovered === true) {
+ this.setState({ hovered: false });
+ }
+ });
+ },
+
+ componentWillUnmount() {
+ const { record, onDestroy, index } = this.props;
+ onDestroy(record, index);
+ if (this.unsubscribe) {
+ this.unsubscribe();
+ }
+ },
+
+ onRowClick(event) {
+ const {
+ record,
+ index,
+ onRowClick,
+ expandable,
+ expandRowByClick,
+ expanded,
+ onExpand,
+ } = this.props;
+ if (expandable && expandRowByClick) {
+ onExpand(!expanded, record, index);
+ }
+ onRowClick(record, index, event);
+ },
+
+ onRowDoubleClick(event) {
+ const { record, index, onRowDoubleClick } = this.props;
+ onRowDoubleClick(record, index, event);
+ },
+
+ onMouseEnter() {
+ const { onHover, hoverKey } = this.props;
+ onHover(true, hoverKey);
+ },
+
+ onMouseLeave() {
+ const { onHover, hoverKey } = this.props;
+ onHover(false, hoverKey);
+ },
+
+ render() {
+ const {
+ prefixCls, columns, record, height, visible, index,
+ expandIconColumnIndex, expandIconAsCell, expanded, expandRowByClick,
+ expandable, onExpand, needIndentSpaced, indent, indentSize,
+ } = this.props;
+
+ let { className } = this.props;
+
+ if (this.state.hovered) {
+ className += ` ${prefixCls}-hover`;
+ }
+
+ const cells = [];
+
+ const expandIcon = (
+
+ );
+
+ for (let i = 0; i < columns.length; i++) {
+ if (expandIconAsCell && i === 0) {
+ cells.push(
+
+ {expandIcon}
+ |
+ );
+ }
+ const isColumnHaveExpandIcon = (expandIconAsCell || expandRowByClick)
+ ? false : (i === expandIconColumnIndex);
+ cells.push(
+
+ );
+ }
+ const style = { height };
+ if (!visible) {
+ style.display = 'none';
+ }
+
+ return (
+
+ {cells}
+
+ );
+ },
+});
+
+export default TableRow;
diff --git a/src/animation.scss b/src/animation.scss
new file mode 100644
index 0000000..a6d4e92
--- /dev/null
+++ b/src/animation.scss
@@ -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/src/bordered.scss b/src/bordered.scss
new file mode 100644
index 0000000..f3ad2e8
--- /dev/null
+++ b/src/bordered.scss
@@ -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/src/createStore.js b/src/createStore.js
new file mode 100644
index 0000000..883ab0f
--- /dev/null
+++ b/src/createStore.js
@@ -0,0 +1,30 @@
+export default function createStore(initialState) {
+ let state = initialState;
+ const listeners = [];
+
+ function setState(partial) {
+ state = { ...state, ...partial };
+ for (let i = 0; i < listeners.length; i++) {
+ listeners[i]();
+ }
+ }
+
+ function getState() {
+ return state;
+ }
+
+ function subscribe(listener) {
+ listeners.push(listener);
+
+ return function unsubscribe() {
+ const index = listeners.indexOf(listener);
+ listeners.splice(index, 1);
+ };
+ }
+
+ return {
+ setState,
+ getState,
+ subscribe,
+ };
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..8175c76
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,8 @@
+const Table = require('./Table');
+const Column = require('./Column');
+const ColumnGroup = require('./ColumnGroup');
+
+Table.Column = Column;
+Table.ColumnGroup = ColumnGroup;
+
+module.exports = Table;
diff --git a/src/index.scss b/src/index.scss
new file mode 100644
index 0000000..97aa433
--- /dev/null
+++ b/src/index.scss
@@ -0,0 +1,210 @@
+@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;
+ }
+
+ .@{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;
+ }
+
+ 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;
+ }
+
+ tr {
+ transition: all .3s ease;
+ &:hover {
+ background: #eaf8fe;
+ }
+ &.@{tablePrefixCls}-row-hover {
+ background: #eaf8fe;
+ }
+ }
+
+ th, td {
+ padding: @vertical-padding @horizontal-padding;
+ }
+}
+
+.@{tablePrefixCls} {
+ &-expand-icon-col {
+ width: 10px;
+ }
+ &-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;
+ z-index: 1;
+ 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/src/utils.js b/src/utils.js
new file mode 100644
index 0000000..f76b1d9
--- /dev/null
+++ b/src/utils.js
@@ -0,0 +1,64 @@
+import warning from 'warning';
+
+let scrollbarWidth;
+
+// 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() {
+ if (typeof document === 'undefined' || typeof window === 'undefined') {
+ return 0;
+ }
+ if (scrollbarWidth) {
+ return scrollbarWidth;
+ }
+ const scrollDiv = document.createElement('div');
+ for (const scrollProp in scrollbarMeasure) {
+ if (scrollbarMeasure.hasOwnProperty(scrollProp)) {
+ scrollDiv.style[scrollProp] = scrollbarMeasure[scrollProp];
+ }
+ }
+ document.body.appendChild(scrollDiv);
+ const width = scrollDiv.offsetWidth - scrollDiv.clientWidth;
+ document.body.removeChild(scrollDiv);
+ scrollbarWidth = width;
+ return scrollbarWidth;
+}
+
+export function debounce(func, wait, immediate) {
+ let timeout;
+ return 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);
+ }
+ };
+}
+
+const warned = {};
+export function warningOnce(condition, format, args) {
+ if (!warned[format]) {
+ warning(condition, format, args);
+ warned[format] = true;
+ }
+}
diff --git a/test/index.test.js b/test/index.test.js
new file mode 100644
index 0000000..fdd17b3
--- /dev/null
+++ b/test/index.test.js
@@ -0,0 +1,4 @@
+import React from 'react';
+import {shallow, mount, render} from 'enzyme';
+import {expect} from 'chai';
+import Table from '../src/index';
\ No newline at end of file