Last active
January 31, 2017 20:58
-
-
Save andyhite/a2ae85d9c4e79910d17e019cb50b470c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { storiesOf, action } from '@kadira/storybook'; | |
import { Table } from '../src'; | |
const columns = [ | |
{ header: 'Foo', key: 'foo.bar', style: { width: '30%' } }, | |
{ header: 'Bar', key: 'bar', style: { width: '30%' } }, | |
{ header: 'Baz', key: 'baz', style: { width: '40%' } }, | |
]; | |
const rows = [ | |
{ foo: { bar: 'Foo 1' }, bar: 'Bar 1', baz: 'Baz 1', disabled: true, removed: true }, | |
{ foo: { bar: 'Foo 2' }, bar: null, baz: null, error: true }, | |
{ foo: { bar: 'Foo 3' }, bar: '', baz: 'Baz 3'}, | |
{ foo: null, bar: 'Bar 4', baz: 'Baz 4' }, | |
]; | |
storiesOf('Table', module).add('Default', () => { | |
return ( | |
<Table columns={columns} rows={rows} /> | |
); | |
}).add('With no rows', () => { | |
return ( | |
<Table columns={columns} rows={[]} emptyText="There are no rows" /> | |
); | |
}).add('With a header click action', () => { | |
return ( | |
<Table | |
columns={columns.map((column) => ( | |
{ ...column, headerIcon: 'arrow_drop_down' } | |
))} | |
rows={rows} | |
onClickHeader={action('click header')} | |
/> | |
); | |
}).add('With selectable rows', () => { | |
return ( | |
<Table selectable | |
columns={columns} | |
rows={rows} | |
onSelectRow={action('select row')} | |
onSelectAll={action('select all')} | |
/> | |
); | |
}).add('With expandable rows', () => { | |
return ( | |
<Table expandable columns={columns} rows={rows}> | |
{(row) => ( | |
<div> | |
Expanded {row.baz} | |
</div> | |
)} | |
</Table> | |
); | |
}).add('With fancy expandable rows', () => { | |
return ( | |
<Table fancy expandable columns={columns} rows={rows}> | |
{(row) => ( | |
<div> | |
Expanded {row.baz} | |
</div> | |
)} | |
</Table> | |
); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import '../../styles/settings'; | |
.root { | |
display: flex; | |
flex-direction: column; | |
margin-bottom: $gutter; position: relative; | |
z-index: 0; | |
} | |
.head { | |
font-weight: bold; | |
} | |
.headerGroup, | |
.cellGroup { | |
display: flex; | |
flex-direction: row; | |
flex-wrap: nowrap; | |
} | |
.header, | |
.cell { | |
align-items: center; | |
display: flex; | |
flex-grow: 1; | |
flex-shrink: 1; | |
padding: $gutter / 2; | |
&.fixed { | |
flex-grow: 0; | |
flex-shrink: 0; | |
} | |
&.centered { | |
justify-content: center; | |
} | |
&.removed { | |
text-decoration: line-through; | |
} | |
} | |
.header { | |
&.clickable { | |
cursor: pointer; | |
&:hover { | |
color: $dark-green; | |
} | |
} | |
} | |
.row { | |
background-color: $white; | |
border-bottom: 1px solid transparent; | |
border-top: 1px solid transparent; | |
display: flex; | |
flex-direction: column; | |
min-height: $line-height - 2px; | |
padding: 0 ($gutter / 2); | |
.body &:nth-child(odd) { | |
background-color: $light-green; | |
} | |
.expandable .body & { | |
position: relative; | |
&:hover { | |
border-bottom: 1px solid darken($light-green, 10%); | |
border-top: 1px solid darken($light-green, 10%); | |
z-index: 1; | |
} | |
&.expanded { | |
border-bottom: 1px solid darken($light-green, 20%); | |
border-top: 1px solid darken($light-green, 20%); | |
z-index: 2; | |
.cellGroup { | |
border-bottom: 1px solid rgba($dark-green, 0.15); | |
} | |
&:hover { | |
z-index: 2; | |
} | |
} | |
.cellGroup { | |
cursor: pointer; | |
} | |
} | |
.fancy .body &.expanded { | |
transform: scale(1.01,1.01); | |
} | |
.detail { | |
padding: $gutter / 2; | |
} | |
&.error { | |
color: $red; | |
} | |
} | |
.errorIcon { | |
margin-right: $gutter / 2; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, PropTypes } from 'react'; | |
import { getStyles } from '../../styles'; | |
import { keypath } from '../../utils'; | |
import CheckBoxInput from '../CheckBoxInput'; | |
import Icon from '../Icon'; | |
const renderValueAtKeypath = (row, { key, renderer }) => { | |
let value = key ? keypath.get(row, key) : row; | |
if (renderer) value = renderer.call(null, value, row); | |
return value || <span dangerouslySetInnerHTML={{ __html: '—' }} />; | |
}; | |
const factory = (defaultTheme) => { | |
class Table extends Component { | |
state = { | |
expandedRowIndex: null, | |
} | |
handleClickHeader = (header, headerIndex) => { | |
if (this.props.onClickHeader) { | |
this.props.onClickHeader(header, headerIndex); | |
} | |
} | |
handleExpandRow = (event, rowIndex) => { | |
if (event.target.nodeName === "INPUT") return; | |
const { expandable } = this.props; | |
const { expandedRowIndex } = this.state; | |
if (expandable) { | |
if (rowIndex === expandedRowIndex) { | |
this.setState({ | |
expandedRowIndex: null, | |
}); | |
} else { | |
this.setState({ | |
expandedRowIndex: rowIndex, | |
}); | |
} | |
} | |
} | |
handleSelectRow = (row, rowIndex) => { | |
if (this.props.onSelectRow) { | |
this.props.onSelectRow(row, rowIndex); | |
} | |
} | |
handleDeselectRow = (row, rowIndex) => { | |
if (this.props.onDeselectRow) { | |
this.props.onDeselectRow(row, rowIndex); | |
} | |
} | |
handleSelectAll = () => { | |
if (this.props.onSelectAll) { | |
this.props.onSelectAll(); | |
} | |
} | |
handleDeselectAll = () => { | |
if (this.props.onDeselectAll) { | |
this.props.onDeselectAll(); | |
} | |
} | |
render() { | |
const { handleClickHeader, handleExpandRow, handleSelectRow, handleDeselectRow, handleSelectAll, handleDeselectAll } = this; | |
const { allSelected, children, columns, emptyText, expandable, fancy, onClickHeader, rows, selectable = false, theme } = this.props; | |
const { expandedRowIndex } = this.state; | |
const styles = getStyles(defaultTheme, theme, { | |
root: { | |
selectable, | |
expandable, | |
fancy, | |
}, | |
header: ({ fixed, clickable }) => ({ | |
fixed, | |
clickable, | |
}), | |
row: ({ error, expanded }) => ({ | |
error, | |
expanded, | |
}), | |
cell: ({ fixed, centered, removed, error }) => ({ | |
fixed, | |
centered, | |
removed, | |
error, | |
}), | |
}); | |
return ( | |
<div className={styles.root} data-ui="Table"> | |
<div className={styles.head}> | |
<div className={styles.row()}> | |
<div className={styles.headerGroup}> | |
{selectable && ( | |
<div className={styles.header({ fixed: true })}> | |
<CheckBoxInput | |
onChange={allSelected ? handleDeselectAll : handleSelectAll} | |
checked={allSelected} | |
/> | |
</div> | |
)} | |
{columns.map((column, i) => ( | |
<div key={i} | |
className={styles.header({ clickable: !!onClickHeader, fixed: column.fixed })} | |
onClick={() => handleClickHeader(column, i)} | |
style={column.style || {}} | |
> | |
{column.header} | |
{column.headerIcon && ( | |
<Icon theme={{ root: styles.headerIcon }} name={column.headerIcon} /> | |
)} | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
<div className={styles.body}> | |
{rows.length > 0 ? ( | |
rows.map((row, i) => ( | |
<div className={styles.row({ expanded: expandedRowIndex === i, error: row.error })} key={i}> | |
<div className={styles.cellGroup} onClick={(event) => handleExpandRow(event, i)}> | |
{selectable && ( | |
<div className={styles.cell({ fixed: true })}> | |
<CheckBoxInput | |
onChange={() => row.selected ? handleDeselectRow(row, i) : handleSelectRow(row, i)} | |
checked={!row.disabled && row.selected} | |
disabled={row.disabled} | |
/> | |
</div> | |
)} | |
{columns.map((column, ii) => ( | |
<div key={ii} | |
className={styles.cell({ fixed: column.fixed, removed: row.removed })} | |
style={column.style || {}} | |
> | |
{row.error && ii === 0 && ( | |
<Icon name="error" theme={{ root: styles.errorIcon }} /> | |
)} | |
{renderValueAtKeypath(row, column)} | |
</div> | |
))} | |
</div> | |
{expandedRowIndex === i && ( | |
<div className={styles.detail}> | |
{children.call(this, row)} | |
</div> | |
)} | |
</div> | |
)) | |
) : ( | |
<div className={styles.row()}> | |
<div className={styles.cellGroup}> | |
<div className={styles.cell({ centered: true })}> | |
{emptyText || 'No data available'} | |
</div> | |
</div> | |
</div> | |
)} | |
</div> | |
</div> | |
); | |
} | |
} | |
Table.propTypes = { | |
allSelected: PropTypes.bool, | |
children: PropTypes.func, | |
columns: PropTypes.arrayOf( | |
PropTypes.shape({ | |
header: PropTypes.string, | |
key: PropTypes.string, | |
headerIcon: PropTypes.string, | |
}) | |
).isRequired, | |
emptyText: PropTypes.node, | |
expandable: PropTypes.bool, | |
fancy: PropTypes.bool, | |
onClickHeader: PropTypes.func, | |
rows: PropTypes.arrayOf( | |
PropTypes.object, | |
).isRequired, | |
selectable: PropTypes.bool, | |
theme: PropTypes.object, | |
}; | |
return Table; | |
}; | |
export default factory; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment