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