UNPKG

wix-style-react

Version:
364 lines (321 loc) • 10.4 kB
import React, {Component} from 'react'; import PropTypes from 'prop-types'; import s from './DataTable.scss'; import classNames from 'classnames'; import InfiniteScroll from './InfiniteScroll'; import WixComponent from '../BaseComponents/WixComponent'; import SortByArrowUp from 'wix-style-react/new-icons/system/SortByArrowUp'; import {Add, Check, ChevronDown} from 'wix-style-react/new-icons'; import {OpenModal} from 'wix-style-react/new-icons'; import * as Icons from 'wix-style-react/new-icons'; import {Hint} from 'wix-style-react/new-icons'; import X from 'wix-style-react/new-icons/X'; import {Animator} from 'wix-animations'; export const DataTableHeader = props => ( <div> <table style={{width: props.width}} className={s.table}> <TableHeader {...props}/> </table> </div> ); DataTableHeader.propTypes = { width: PropTypes.number }; class DataTable extends WixComponent { constructor(props) { super(props); let state = {selectedRows: {}}; if (props.infiniteScroll) { state = {...state, ...this.createInitialScrollingState(props)}; } this.state = state; } componentWillReceiveProps(nextProps) { let isLoadingMore = false; if (this.props.infiniteScroll && nextProps.data !== this.props.data) { if (nextProps.data instanceof Array && this.props.data instanceof Array) { if (this.props.data.every((elem, index) => { return nextProps.data.length > index && nextProps.data[index] === elem; })) { isLoadingMore = true; const lastPage = this.calcLastPage(nextProps); const currentPage = this.state.currentPage < lastPage ? this.state.currentPage + 1 : this.state.currentPage; this.setState({lastPage, currentPage}); } } if (!isLoadingMore) { this.setState(this.createInitialScrollingState(nextProps)); } } } createInitialScrollingState(props) { return {currentPage: 0, lastPage: this.calcLastPage(props)}; } render() { const {data, showHeaderWhenEmpty, infiniteScroll, itemsPerPage} = this.props; if (!data.length && !showHeaderWhenEmpty) { return null; } const rowsToRender = infiniteScroll ? data.slice(0, ((this.state.currentPage + 1) * itemsPerPage)) : data; const table = this.renderTable(rowsToRender); if (infiniteScroll) { return this.wrapWithInfiniteScroll(table); } return table; } wrapWithInfiniteScroll = table => { return ( <InfiniteScroll pageStart={0} loadMore={this.loadMore} hasMore={this.state.currentPage < this.state.lastPage || (this.props.hasMore)} loader={this.props.loader} useWindow={this.props.useWindow} scrollElement={this.props.scrollElement} > {table} </InfiniteScroll> ); }; renderTable = rowsToRender => { const style = {width: this.props.width}; return ( <div> <table id={this.props.id} style={style} className={s.table}> {!this.props.hideHeader && <TableHeader {...this.props}/>} {this.renderBody(rowsToRender)} <Add/> <Check/> <ChevronDown/> <OpenModal/> <Hint/> <Icons.ChevronDown/> <X/> </table> </div> ); }; renderBody = rows => ( <tbody> {rows.map(this.renderRow)} </tbody> ); onRowClick = (rowData, rowNum) => { const {onRowClick, rowDetails} = this.props; onRowClick && onRowClick(rowData, rowNum); rowDetails && this.toggleRowDetails(rowNum); } renderRow = (rowData, rowNum) => { const {onRowClick, onMouseEnterRow, onMouseLeaveRow, rowDataHook, dynamicRowClass, rowDetails} = this.props; const rowClasses = [this.props.rowClass]; const optionalRowProps = {}; const handlers = [ {rowEventHandler: this.onRowClick, eventHandler: 'onClick'}, {rowEventHandler: onMouseEnterRow, eventHandler: 'onMouseEnter'}, {rowEventHandler: onMouseLeaveRow, eventHandler: 'onMouseLeave'} ]; handlers.forEach(({rowEventHandler, eventHandler}) => { if (rowEventHandler) { optionalRowProps[eventHandler] = event => { if (event.isDefaultPrevented()) { return; } rowEventHandler(rowData, rowNum); }; } }); if (onRowClick) { rowClasses.push(s.clickableDataRow); } if (rowDetails) { rowClasses.push(s.animatedDataRow); } if (rowDataHook) { if (typeof rowDataHook === 'string') { optionalRowProps['data-hook'] = rowDataHook; } else { optionalRowProps['data-hook'] = rowDataHook(rowData, rowNum); } } if (dynamicRowClass) { rowClasses.push(dynamicRowClass(rowData, rowNum)); } optionalRowProps.className = classNames(rowClasses); const rowsToRender = [( <tr data-table-row="dataTableRow" key={rowNum} {...optionalRowProps}> {this.props.columns.map((column, colNum) => this.renderCell(rowData, column, rowNum, colNum))} </tr> )]; if (rowDetails) { const showDetails = !!this.state.selectedRows[rowNum]; rowsToRender.push( <tr key={`${rowNum}_details`} className={classNames(s.rowDetails)}> <td data-hook={`${rowNum}_details`} className={classNames(s.details, showDetails ? s.active : '')} colSpan={this.props.columns.length} > <div className={classNames(s.rowDetailsInner)}> <Animator show={showDetails} height> {rowDetails(rowData, rowNum)} </Animator> </div> </td> </tr> ); } return rowsToRender; }; renderCell = (rowData, column, rowNum, colNum) => { const classes = classNames({[s.important]: column.important}); const width = rowNum === 0 && this.props.hideHeader ? column.width : undefined; return (<td style={column.style} width={width} className={classes} key={colNum} > {column.render && column.render(rowData, rowNum)} </td>); }; calcLastPage = ({data, itemsPerPage}) => Math.ceil(data.length / itemsPerPage) - 1; loadMore = () => { if (this.state.currentPage < this.state.lastPage) { this.setState({currentPage: this.state.currentPage + 1}); } else { this.props.loadMore && this.props.loadMore(); } } toggleRowDetails = selectedRow => { let selectedRows = {[selectedRow]: !this.state.selectedRows[selectedRow]}; if (this.props.allowMultiDetailsExpansion) { selectedRows = Object.assign({}, this.state.selectedRows, {[selectedRow]: !this.state.selectedRows[selectedRow]}); } this.setState({selectedRows}); } } class TableHeader extends Component { static propTypes = { onSortClick: PropTypes.func, thPadding: PropTypes.string, thHeight: PropTypes.string, thFontSize: PropTypes.string, thBorder: PropTypes.string, thColor: PropTypes.string, thOpacity: PropTypes.string, thLetterSpacing: PropTypes.string, columns: PropTypes.array }; renderSortingArrow = (sortDescending, colNum) => { if (sortDescending === undefined) { return null; } const sortDirectionClassName = sortDescending ? s.sortArrowAsc : s.sortArrowDesc; return <span data-hook={`${colNum}_title`} className={sortDirectionClassName}><SortByArrowUp/></span>; }; renderHeaderCell = (column, colNum) => { const style = { width: column.width, padding: this.props.thPadding, height: this.props.thHeight, fontSize: this.props.thFontSize, border: this.props.thBorder, color: this.props.thColor, opacity: this.props.thOpacity, letterSpacing: this.props.thLetterSpacing, cursor: column.sortable === undefined ? 'arrow' : 'pointer' }; const optionalHeaderCellProps = {}; if (column.sortable) { optionalHeaderCellProps.onClick = () => this.props.onSortClick && this.props.onSortClick(column, colNum); } return ( <th style={style} key={colNum} {...optionalHeaderCellProps} > {column.title}{this.renderSortingArrow(column.sortDescending, colNum)} </th>); }; render() { return ( <thead> <tr> {this.props.columns.map(this.renderHeaderCell)} </tr> </thead>); } } function validateData(props, propName) { if (props[propName]) { if (props[propName].constructor && props[propName].constructor.name && props[propName].constructor.name.toLowerCase().indexOf('array') > -1) { return null; } else { return Error('Data element must be an array type'); } } return null; } DataTable.defaultProps = { data: [], columns: [], showHeaderWhenEmpty: false, infiniteScroll: false, itemsPerPage: 20, width: '100%', loadMore: null, hasMore: false, loader: <div className="loader">Loading ...</div>, scrollElement: null, useWindow: true, thPadding: '5px', thHeight: '36px', thFontSize: '12px' }; DataTable.propTypes = { id: PropTypes.string, data: validateData, columns: PropTypes.arrayOf(PropTypes.shape({ title: PropTypes.oneOfType([ PropTypes.node, PropTypes.string ]).isRequired, render: PropTypes.func.isRequired, sortable: PropTypes.bool, sortDescending: PropTypes.bool })), showHeaderWhenEmpty: PropTypes.bool, rowDataHook: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]), rowClass: PropTypes.string, dynamicRowClass: PropTypes.func, onRowClick: PropTypes.func, onMouseEnterRow: PropTypes.func, onMouseLeaveRow: PropTypes.func, infiniteScroll: PropTypes.bool, itemsPerPage: PropTypes.number, width: PropTypes.string, loadMore: PropTypes.func, hasMore: PropTypes.bool, loader: PropTypes.node, useWindow: PropTypes.bool, scrollElement: PropTypes.object, thPadding: PropTypes.string, thHeight: PropTypes.string, thFontSize: PropTypes.string, thBorder: PropTypes.string, thColor: PropTypes.string, thOpacity: PropTypes.string, thLetterSpacing: PropTypes.string, rowDetails: PropTypes.func, allowMultiDetailsExpansion: PropTypes.bool, hideHeader: PropTypes.bool }; DataTable.displayName = 'DataTable'; export default DataTable;