coreui
Version:
Platform Core UI
233 lines (207 loc) • 7.35 kB
JavaScript
import React, { PropTypes } from 'react';
import Button from '../Button';
import NumberPickerInput from '../NumberPickerInput';
import Shared from '../../Shared';
import TextInput from '../TextInput';
import tableHandlers from './TableHandlers';
import tableHelpers from './TableHelpers';
import compose from 'recompose/compose';
import cx from 'classnames/dedupe';
import mapProps from 'recompose/mapProps';
import withHandlers from 'recompose/withHandlers';
import withState from 'recompose/withState';
import { inc, is, merge, partial } from 'ramda';
const systemStyles = { };
const maybeRenderPager = (baseTableProps) => {
const {
maxPageIndex, onPageChange, onNextPageClick,
onPrevPageClick, pageIndex, prevPageIndex, pagination,
} = baseTableProps;
const effectivePageIndex = is(Number, pageIndex) ? pageIndex : prevPageIndex;
const pageNumber = inc(effectivePageIndex);
const nextDisabled = effectivePageIndex === maxPageIndex;
const prevDisabled = effectivePageIndex === 0;
return pagination && (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button
className={cx('btn-link', { disabled: prevDisabled })}
onClick={onPrevPageClick}
style={{ color: prevDisabled ? '#ccc' : '#2975e9' }}
>
<div style={{ display: 'flex', alignItems: 'middle' }}>
<span className="icon icon-chevron-left" />
<span title="Previous">Previous</span>
</div>
</Button>
<NumberPickerInput
max={inc(maxPageIndex)}
min={1}
onChange={onPageChange}
style={{ maxWidth: 100 }}
value={is(Number, pageIndex) ? pageNumber : pageIndex}
/>
<Button
className={cx('btn-link', { disabled: nextDisabled })}
onClick={onNextPageClick}
style={{ color: nextDisabled ? '#ccc' : '#2975e9' }}
>
<div style={{ display: 'flex', alignItems: 'middle' }}>
<span title="Next">Next</span>
<span className="icon icon-chevron-right" />
</div>
</Button>
</div>
);
};
const maybeRenderSearch = (baseTableProps) => {
const { onSearchChange, searchable, searchPlaceholder, searchValue } = baseTableProps;
return searchable && (
<div className="form-group">
<div className="search">
<TextInput
className="search-input"
onChange={onSearchChange}
placeholder={searchPlaceholder}
style={{ maxWidth: 300 }}
type="search"
value={searchValue}
/>
</div>
</div>
);
};
const maybeRenderSortIcons = (baseTableProps, { id, isSortable }) => {
const { sortAscending, sortField } = baseTableProps;
const isSorted = (sortField === id);
const className = isSortable && cx('icon', {
'icon-chevron-down': isSorted && !sortAscending,
'icon-chevron-up': isSorted && sortAscending,
'icon-chevrons-vertical': !isSorted,
});
return isSortable && <span {...{ className }} />;
};
const renderHeaderCell = (baseTableProps, c, i) => {
const { onHeaderClick } = baseTableProps;
const { isSortable } = c;
return (
<th
key={i}
onClick={() => isSortable && onHeaderClick(c)}
style={{ cursor: isSortable ? 'pointer' : 'normal' }}
>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{c.displayName}
{maybeRenderSortIcons(baseTableProps, c)}
</div>
</th>
);
};
const renderCell = (data, { component, id }) => {
const Component = component;
const cellData = data[id];
return (
<td key={id}>
{Component ? <Component data={cellData} /> : cellData}
</td>
);
};
const renderRow = (baseTableProps, data, i) => {
const { columns, onRowClick, selectedRows, selection, valueField } = baseTableProps;
const rowId = valueField && data[valueField];
return (
<tr
className={cx({ 'table-active': valueField && selectedRows.has(rowId) })}
key={valueField ? rowId : i}
onClick={() => selection && valueField && onRowClick(data)}
>
{columns.map(partial(renderCell, [data]))}
</tr>
);
};
const TableBase = (props) => {
const { className, columns, data, style } = props;
return (
<div>
{maybeRenderSearch(props)}
<table {...{ className, style }}>
<thead><tr>{columns.map(partial(renderHeaderCell, [props]))}</tr></thead>
<tbody>
{data.length ?
data.map(partial(renderRow, [props])) :
<tr><td colSpan={columns.length}>No rows to display</td></tr>
}
</tbody>
</table>
{maybeRenderPager(props)}
</div>
);
};
TableBase.propTypes = {
className: PropTypes.string,
columns: PropTypes.array,
data: PropTypes.array.isRequired,
maxPageIndex: PropTypes.number,
onHeaderClick: PropTypes.func,
onNextPageClick: PropTypes.func,
onPageChange: PropTypes.func,
onPrevPageClick: PropTypes.func,
onRowClick: PropTypes.func,
onSearchChange: PropTypes.func,
pageIndex: PropTypes.number,
pageSize: PropTypes.number,
prevPageIndex: PropTypes.number,
searchable: PropTypes.oneOfType([PropTypes.bool, PropTypes.instanceOf(Set)]),
searchPlaceholder: PropTypes.string,
searchValue: PropTypes.string,
selectedRows: PropTypes.instanceOf(Set),
selection: PropTypes.bool,
selectMultiple: PropTypes.bool,
sortable: PropTypes.oneOfType([PropTypes.bool, PropTypes.instanceOf(Set)]),
sortAscending: PropTypes.bool,
sortField: PropTypes.string,
style: PropTypes.object,
valueField: PropTypes.string,
};
const TableContainer = compose(
withState('pageIndex', 'setPageIndex', 0),
withState('prevPageIndex', 'setPrevPageIndex', 0),
withState('searchValue', 'setSearchValue', ''),
withState('selectedRows', 'setSelectedRows', (props) => new Set(props.selectedRows)),
withState('sortAscending', 'setSortAscending', (props) => props.sortAscending),
withState('sortField', 'setSortField', (props) => props.sortField),
mapProps(({ className, sheet, style, theme, ...rest }) => ({
className: cx(sheet.classes.table, theme.classes.table, className),
style: merge(theme.styles.table, style),
...rest,
})),
mapProps(partial(tableHelpers.normalizedProps, [tableHelpers])),
withHandlers(tableHandlers(tableHelpers))
)(TableBase);
const StyledTable = Shared.injectSheet(systemStyles)(TableContainer);
const Table = (props) =>
<StyledTable {...props}>{props.children}</StyledTable>;
Table.defaultProps = tableHelpers.tableDefaultProps();
Table.displayName = 'Table';
Table.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
columns: PropTypes.array,
data: PropTypes.array.isRequired,
onClick: PropTypes.func,
onSort: PropTypes.func,
pageSize: PropTypes.number,
pagination: PropTypes.bool,
searchable: PropTypes.oneOfType([PropTypes.bool, PropTypes.array]),
searchPlaceholder: PropTypes.string,
selectedRows: PropTypes.instanceOf(Set),
selection: PropTypes.bool,
selectMultiple: PropTypes.bool,
sortable: PropTypes.oneOfType([PropTypes.bool, PropTypes.array]),
sortAscending: PropTypes.bool,
sortField: PropTypes.string,
style: PropTypes.object,
theme: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
valueField: PropTypes.string,
};
Shared.registerComponent('Table', Table);
export default Table;