Skip to content

Commit

Permalink
feat(Table): add sort and selection
Browse files Browse the repository at this point in the history
  • Loading branch information
ej9x committed Jan 14, 2019
1 parent d66aaf7 commit 20703bb
Show file tree
Hide file tree
Showing 13 changed files with 343 additions and 16 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions e2e/__tests__/table.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ const SUITES = [
baisy.suite('Components/Table', 'with data'),
baisy.suite('Components/Table', 'with loader'),
baisy.suite('Components/Table', 'without data'),
baisy.suite('Components/Table', 'with sort')
.setEnhancer(async (iframe) => {
await (await await iframe.waitForXPath('//*[contains(text(),"Id")]')).click();
await (await await iframe.waitForXPath('//*[contains(text(),"Id")]')).click();
await (await await iframe.waitForXPath('//*[contains(text(),"Email")]')).click();
}),
baisy.suite('Components/Table', 'with selection')
.setEnhancer(async (iframe) => {
await (await await iframe.waitForXPath('(//i)[4]')).click();
}),
baisy.suite('Components/Table', 'with selection', 'all selected')
.setEnhancer(async (iframe) => {
await (await await iframe.waitForXPath('//i')).click();
}),
];


Expand Down
4 changes: 3 additions & 1 deletion src/components/Checkbox/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class Checkbox extends PureComponent<CheckboxProps> {
type="checkbox"
tagName="input"
/>
<CheckboxTextTag tagName="div" nowrap={ nowrap }>{ label }</CheckboxTextTag>
<If condition={ !!label }>
<CheckboxTextTag tagName="div" nowrap={ nowrap }>{ label }</CheckboxTextTag>
</If>
</CheckboxWrapperTag>
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/Checkbox/Checkbox.theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ const CheckboxTag = createStyledTag(`${name}Tag`, {
display: 'none',
});

const CheckboxTextTag = createStyledTag(`${name}Text`, props => ({
paddingLeft: props.label ? '12px' : '0',
const CheckboxTextTag = createStyledTag(`${name}Text`, {
paddingLeft: '12px',
cursor: 'pointer',
}));
});

export { theme, CheckboxSquareTag, CheckboxTag, CheckboxWrapperTag, CheckboxTextTag, CheckboxIconTag };

12 changes: 10 additions & 2 deletions src/components/Table/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { TableBodyCell, theme as tableBodyCellTheme } from './TableBodyCell';
type TablePlateProps = {
children?: React$Node,
stretch?: boolean,
};
}

const name = 'tablePlate';

Expand Down Expand Up @@ -46,10 +46,18 @@ const theme = {
const TableTag = createStyledTag(name);

function Table({
action,
children,
columns,
data,
onActionClick,
...rest
}: TablePlateProps) {
return <TableTag { ...rest } tagName={ Grid.Layout }>{ children }</TableTag>;
return (
<TableTag { ...rest } tagName={ Grid.Layout }>
{ children }
</TableTag>
);
}

Table.defaultProps = {
Expand Down
73 changes: 72 additions & 1 deletion src/components/Table/Table.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

import React from 'react';

const TABLE_COLUMNS = [{
name: 'id',
title: 'Id',
width: '300px',
}, {
name: 'createdAt',
title: 'Created At',
}, {
name: 'updatedAt',
title: 'Updated At',
}, {
name: 'firstName',
title: 'First Name',
}, {
name: 'lastName',
title: 'Last Name',
}, {
name: 'email',
title: 'Email',
}];

const TABLE_DATA = [{
id: '1',
createdAt: '2018-09-03T09:59:43.000Z',
Expand Down Expand Up @@ -214,8 +235,26 @@ const TABLE_DATA = [{
email: '[email protected]',
}];


class TableState extends React.Component {
state = {
tableState: {},
}

setTableState = (tableState) => {
this.setState({ tableState });
}

render() {
const { children } = this.props;
const { tableState } = this.state;

return children({ tableState, setTableState: this.setTableState });
}
}

export default (asStory) => {
asStory('Components/Table', module, (story, { Table, Link, Dropdown, Icon, Menu, Button }) => {
asStory('Components/Table', module, (story, { Table, TableBuilder, Link, Dropdown, Icon, Menu, Button }) => {
story
.add('default', () => (
<div style={{ display: 'flex', height: '600px' }}>
Expand Down Expand Up @@ -373,6 +412,38 @@ export default (asStory) => {
/>
</Table>
</div>
))

.add('with sort', () => (
<div style={{ display: 'flex', height: '600px' }}>
<TableState>
{ ({ tableState, setTableState }) => (
<TableBuilder
columns={ TABLE_COLUMNS }
data={ TABLE_DATA }
action="Create Client" onActionClick={ () => alert('Create') }
onChange={ setTableState }
tableState={ tableState }
/>
) }
</TableState>
</div>
))
.add('with selection', () => (
<div style={{ display: 'flex', height: '600px' }}>
<TableState>
{ ({ tableState, setTableState }) => (
<TableBuilder
columns={ TABLE_COLUMNS }
data={ TABLE_DATA }
action="Create Client" onActionClick={ () => alert('Create') }
onChange={ setTableState }
tableState={ tableState }
withSelection
/>
) }
</TableState>
</div>
));
});
};
Expand Down
3 changes: 2 additions & 1 deletion src/components/Table/TableBodyCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from 'react';

import { createStyledTag, createComponentTheme } from '../../utils';
import { Row } from '../FlexLayout';

type TableBodyCellProps = {
children?: React$Node,
Expand Down Expand Up @@ -29,7 +30,7 @@ function TableBodyCell({
children,
...rest
}: TableBodyCellProps) {
return <TableBodyCellTag { ...rest } tagName="div">{ children }</TableBodyCellTag>;
return <TableBodyCellTag { ...rest } tagName={ Row }>{ children }</TableBodyCellTag>;
}

export { TableBodyCell, theme };
190 changes: 190 additions & 0 deletions src/components/Table/TableBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// @flow

import React, { Component } from 'react';

import fp from 'lodash/fp';
import { TableHeader } from './TableHeader';
import { TableBody } from './TableBody';
import { TableBodyRow } from './TableBodyRow';
import { TableHeaderCell } from './TableHeaderCell';
import { TableBodyCell } from './TableBodyCell';
import { Table } from './Table';
import { Checkbox } from '../Checkbox';


type ColumnType = {
title: string,
name: string,
sortEnable?: boolean,
}

type TableState = {
sort?: Array<{ name: string, order: 'ASC' | 'DESC' }>,
selection?: Array<string>
}

type TableBulderProps = {
columns: Array<ColumnType>,
data: Array<Object>,
onActionClick?: () => void,
action?: React$Node,

tableState: TableState,
withSelection?: boolean,
onChange: TableState => void,
}

class TableBuilder extends Component<TableBulderProps> {

static defaultProps = {
columns: [],
data: [],
tableState: {
sort: [],
selection: [],
},
}

getGridColumns = () => {
const { columns, withSelection } = this.props;

return withSelection
? `80px repeat(${columns.length}, 1fr)`
: `repeat(${columns.length}, 1fr)`;
}

onSort = fp.memoize((name: string) => (order: 'ASC' | 'DESC') => {
const { onChange, tableState = {}} = this.props;
const previousSort = tableState.sort || [];

const sort = previousSort
.filter(({ name: columnName }) => columnName !== name)
.concat([{ name, order }]);

onChange({
...tableState,
sort,
});
})

getColumnOrder = (name: string) => {
const { tableState = {}} = this.props;
const sort = tableState.sort || [];

const { order } = sort
.find(({ name: columnName }) => columnName === name)
|| {};

return order;
}


selectAllRows = () => {
const { onChange, tableState = {}, data } = this.props;
const isAllRowsSelected = this.hasAllRowsSelection();
const allIds = fp.map('id', data);

const selection = isAllRowsSelected
? []
: allIds;

onChange({
...tableState,
selection,
});
}

selectRow = fp.memoize((id: string) => () => {
const { onChange, tableState = {}} = this.props;
const previousSelection = tableState.selection || [];
const isRowSelected = this.hasRowSelection(id);

const selection = isRowSelected
? fp.xor(previousSelection, [id])
: fp.uniq([...previousSelection, id]);

onChange({
...tableState,
selection,
});
})

hasRowSelection = (id: string) => {
const { tableState: { selection } = {}} = this.props;

return fp.findIndex(
fp.equals(id),
selection,
) >= 0;
}

hasAllRowsSelection = () => {
const { tableState: { selection } = {}, data } = this.props;
const allIds = fp.map('id', data);

return fp.isEmpty(fp.xor(selection, allIds));
}


renderHeader = () => {
const { columns, withSelection } = this.props;

return (
<TableHeader columns={ this.getGridColumns() }>
<If condition={ !!withSelection }>
<TableHeaderCell justifyContent="center">
<Checkbox onChange={ this.selectAllRows } checked={ this.hasAllRowsSelection() } />
</TableHeaderCell>
</If>
{ columns.map(({ title, name }) => (
<TableHeaderCell
key={ name }
cursor="pointer"
onSort={ this.onSort(name) }
order={ this.getColumnOrder(name) }
>
{ title }
</TableHeaderCell>
))
}
</TableHeader>
);
}

renderBody = () => {
const { columns, onActionClick, action, data, withSelection } = this.props;

return (
<TableBody data={ data } onActionClick={ onActionClick } action={ action } >
{
(rowData) => (
<TableBodyRow columns={ this.getGridColumns() } key={ rowData.id }>
<If condition={ !!withSelection }>
<TableBodyCell justifyContent="center">
<Checkbox onChange={ this.selectRow(rowData.id) } checked={ this.hasRowSelection(rowData.id) } />
</TableBodyCell>
</If>
{ columns.map(({ name }) => (
<TableBodyCell key={ name }>{ rowData[name] }</TableBodyCell>
)) }
</TableBodyRow>
)
}
</TableBody >
);
}

render() {
return (
<Table>
{ this.renderHeader() }
{ this.renderBody() }
</Table>
);
}
}

export { TableBuilder };

export type { TableBulderProps, ColumnType };

Loading

0 comments on commit 20703bb

Please sign in to comment.