diff --git a/API.md b/API.md index 9725bf6..0f72501 100644 --- a/API.md +++ b/API.md @@ -893,36 +893,77 @@ Table Simple ```jsx - +const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const columnTableNames = ['one', 'two', 'three', ['i', 'am', 'named', 'individually']]; + +
``` rowIncludesHeading ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const rowTableNamesWithTitles = ['heading', 'one', ['i', 'am', 'named', 'individually'], 'three']; + +
``` rowIncludesHeading, no titles ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const rowTableNames = ['one', ['i', 'am', 'named', 'individually'], 'three']; + +
``` rowIncludesHeading, no titles, small single row ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const rowTableNames = ['one', ['i', 'am', 'named', 'individually'], 'three']; + +
``` -rowIncludesHeading, with flexible columns +rowIncludesHeading, with titles, with flexible columns ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const rowTableNamesWithTitles = ['heading', 'one', ['i', 'am', 'named', 'individually'], 'three']; + +
``` ### Properties Prop | Required | Default | Type | Description :--- | :------- | :------ | :--- | :---------- - `flexibleColumns` | | `````` | bool | + `flexibleColumns` | | ```false``` | bool | `name` | | `````` | string | + `nameByRow` | | ```false``` | bool | `names` | | ```[]``` | arrayOf[object Object] | - `rowIncludesHeading` | | `````` | bool | + `rowIncludesHeading` | | ```false``` | bool | `rows` | true | `````` | arrayOf[object Object] | `titles` | | `````` | arrayOf[object Object] | diff --git a/components/table/README.md b/components/table/README.md index 235daf8..c6674b6 100644 --- a/components/table/README.md +++ b/components/table/README.md @@ -11,36 +11,77 @@ Table Simple ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const columnTableNames = ['one', 'two', 'three', ['i', 'am', 'named', 'individually']]; + +
``` rowIncludesHeading ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const rowTableNamesWithTitles = ['heading', 'one', ['i', 'am', 'named', 'individually'], 'three']; + +
``` rowIncludesHeading, no titles ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const rowTableNames = ['one', ['i', 'am', 'named', 'individually'], 'three']; + +
``` rowIncludesHeading, no titles, small single row ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const rowTableNames = ['one', ['i', 'am', 'named', 'individually'], 'three']; + +
``` -rowIncludesHeading, with flexible columns +rowIncludesHeading, with titles, with flexible columns ```jsx -
+const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; +const arrayExampleContent = [ + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], +]; +const rowTableNamesWithTitles = ['heading', 'one', ['i', 'am', 'named', 'individually'], 'three']; + +
``` ### Properties Prop | Required | Default | Type | Description :--- | :------- | :------ | :--- | :---------- - `flexibleColumns` | | `````` | bool | + `flexibleColumns` | | ```false``` | bool | `name` | | `````` | string | + `nameByRow` | | ```false``` | bool | `names` | | ```[]``` | arrayOf[object Object] | - `rowIncludesHeading` | | `````` | bool | + `rowIncludesHeading` | | ```false``` | bool | `rows` | true | `````` | arrayOf[object Object] | `titles` | | `````` | arrayOf[object Object] | diff --git a/components/table/src/__snapshots__/test.js.snap b/components/table/src/__snapshots__/test.js.snap index 00a3dd1..1e52920 100644 --- a/components/table/src/__snapshots__/test.js.snap +++ b/components/table/src/__snapshots__/test.js.snap @@ -9,7 +9,7 @@ exports[`Table matches snapshot 1`] = ` Heading 4 @@ -22,6 +22,7 @@ exports[`Table matches snapshot 1`] = ` Content 1-4 @@ -33,6 +34,7 @@ exports[`Table matches snapshot 1`] = ` Content 2-4 @@ -44,6 +46,7 @@ exports[`Table matches snapshot 1`] = ` Content 3-4 diff --git a/components/table/src/index.js b/components/table/src/index.js index 1c0ecdb..8316b7f 100644 --- a/components/table/src/index.js +++ b/components/table/src/index.js @@ -15,39 +15,50 @@ const TableContainer = styled('table', { ({ flexibleColumns }) => ({ tableLayout: flexibleColumns ? 'auto' : 'fixed' }), ); -const TableHeading = styled('th', { - forwardProps: ['name'], -})(({ rowHeading, columnCount }) => ({ +const cellStyles = { ':first-child': { - padding: '15px 8px 15px 0', - width: rowHeading && columnCount < 4 ? '25%' : undefined, + padding: '15px 4px 15px 0', }, ':last-child': { - padding: '15px 0 15px 8px', + padding: '15px 0 15px 4px', }, borderBottom: '1px solid #bfc1c3', display: 'table-cell', fontSize: '14px', - fontWeight: 'bold', - textAlign: 'left', - verticalAlign: 'baseline', -})); + padding: '15px 4px', + verticalAlign: 'top', +}; const TableData = styled('td', { // use `forwardProps` here as by default emotion doesn't allow setting `name` prop on a `td` forwardProps: ['name'], -})({ - ':first-child': { - padding: '15px 8px 15px 0', - }, - ':last-child': { - padding: '15px 0 15px 8px', - }, - borderBottom: '1px solid #bfc1c3', - display: 'table-cell', - fontSize: '14px', - verticalAlign: 'baseline', -}); +})(cellStyles); + +const TableHeading = styled('th')( + cellStyles, + ({rowHeading, columnCount}) => ( + { + fontWeight: 'bold', + textAlign: 'left', + width: rowHeading && columnCount < 4 ? '25%' : undefined, + } + ), +); + +const getName = (names, row, column, nameByRow) => { + if (nameByRow) { + return Array.isArray(names[row]) ? names[row][column] : names[row]; + } + return Array.isArray(names[column]) ? names[column][row] : names[column]; +}; + +const calculateIndex = (titles, nameByRow, index) => { + if (nameByRow) { + // Only if there are headings at the top, we need to increment the row + return (titles && titles.length) ? (index + 1) : index; + } + return (index + 1); +}; /** * @@ -55,30 +66,70 @@ const TableData = styled('td', { * * Simple * ```jsx - *
+ * const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; + * const arrayExampleContent = [ + * ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + * ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + * ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], + * ]; + * const columnTableNames = ['one', 'two', 'three', ['i', 'am', 'named', 'individually']]; + * + *
* ``` * * rowIncludesHeading * ```jsx - *
+ * const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; + * const arrayExampleContent = [ + * ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + * ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + * ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], + * ]; + * const rowTableNamesWithTitles = ['heading', 'one', ['i', 'am', 'named', 'individually'], 'three']; + * + *
* ``` * * rowIncludesHeading, no titles * ```jsx - *
+ * const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; + * const arrayExampleContent = [ + * ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + * ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + * ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], + * ]; + * const rowTableNames = ['one', ['i', 'am', 'named', 'individually'], 'three']; + * + *
* ``` * * rowIncludesHeading, no titles, small single row * ```jsx - *
+ * const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; + * const arrayExampleContent = [ + * ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + * ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + * ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], + * ]; + * const rowTableNames = ['one', ['i', 'am', 'named', 'individually'], 'three']; + * + *
* ``` * - * rowIncludesHeading, with flexible columns + * rowIncludesHeading, with titles, with flexible columns * ```jsx - *
+ * const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; + * const arrayExampleContent = [ + * ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], + * ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], + * ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], + * ]; + * const rowTableNamesWithTitles = ['heading', 'one', ['i', 'am', 'named', 'individually'], 'three']; + * + *
* ``` */ -const Table = ({ name, names = [], rowIncludesHeading, titles, rows, flexibleColumns }) => ( +const Table = ({ name, names, rowIncludesHeading, nameByRow, titles, rows, flexibleColumns }) => ( {titles && titles.length && ( @@ -87,7 +138,9 @@ const Table = ({ name, names = [], rowIncludesHeading, titles, rows, flexibleCol {titles.map((title, index) => ( // disable false-positive rule - this is an access into an array of strings, not object access // eslint-disable-next-line security/detect-object-injection - + {title} ))} @@ -100,13 +153,19 @@ const Table = ({ name, names = [], rowIncludesHeading, titles, rows, flexibleCol {row.map( (item, itemIndex) => rowIncludesHeading && itemIndex === 0 ? ( - + {item} ) : ( // disable false-positive rule - this is an access into an array of strings, not object access // eslint-disable-next-line security/detect-object-injection - + {item} ), @@ -120,10 +179,23 @@ const Table = ({ name, names = [], rowIncludesHeading, titles, rows, flexibleCol Table.propTypes = { flexibleColumns: PropTypes.bool, name: PropTypes.string, - names: PropTypes.arrayOf(PropTypes.string), + names: PropTypes.arrayOf( + PropTypes.oneOfType([ + PropTypes.string, + PropTypes.arrayOf(PropTypes.string), + ]), + ), + nameByRow: PropTypes.bool, rowIncludesHeading: PropTypes.bool, rows: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.node]))).isRequired, titles: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.node])), }; +Table.defaultProps = { + flexibleColumns: false, + nameByRow: false, + names: [], + rowIncludesHeading: false, +}; + export default Table; diff --git a/components/table/src/stories.js b/components/table/src/stories.js index 22b7e43..a3a754b 100644 --- a/components/table/src/stories.js +++ b/components/table/src/stories.js @@ -8,11 +8,14 @@ import ReadMe from '../README.md'; const arrayExampleHeadings = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4']; const arrayExampleContent = [ - ['Content 1-1', 'Content 1-2', 'Content 3', 'Content 4'], + ['Content 1-1', 'Content 1-2', 'Content 1-3', 'Content 1-4'], ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], ]; -const exampleNames = ['one', 'two', 'three', 'four']; + +const columnTableNames = ['one', 'two', 'three', ['i', 'am', 'named', 'individually']]; +const rowTableNamesWithTitles = ['heading', 'one', ['i', 'am', 'named', 'individually'], 'three']; +const rowTableNames = ['one', ['i', 'am', 'named', 'individually'], 'three']; const stories = storiesOf('Tables/Table', module); const examples = storiesOf('Tables/Table/Examples', module); @@ -21,21 +24,21 @@ stories.addDecorator(WithDocsCustom(ReadMe)); stories.addDecorator(withKnobs); stories.add('Component default', () => -
, +
, ); -examples.add('rowIncludesHeading', () => -
, +examples.add('rowIncludesHeading, with titles', () => +
, ); examples.add('rowIncludesHeading, no titles', () => -
, +
, ); examples.add('rowIncludesHeading, no titles, small single row', () => -
, +
, ); examples.add('rowIncludesHeading, with flexible columns', () => -
, +
, ); diff --git a/components/table/src/test.js b/components/table/src/test.js index 9a135a4..5b1726e 100644 --- a/components/table/src/test.js +++ b/components/table/src/test.js @@ -14,7 +14,11 @@ describe('Table', () => { ['Content 2-1', 'Content 2-2', 'Content 2-3', 'Content 2-4'], ['Content 3-1', 'Content 3-2', 'Content 3-3', 'Content 3-4'], ]; - const names = ['one', 'two', 'three', 'four']; + + const columnTableNames = ['one', 'two', 'three', ['i', 'am', 'named', 'individually']]; + const rowTableNamesWithTitles = ['heading', 'one', ['i', 'am', 'named', 'individually'], 'three']; + const rowTableNames = ['one', ['i', 'am', 'named', 'individually'], 'three']; + let wrapper; it('renders as a table', () => { @@ -30,17 +34,76 @@ describe('Table', () => { expect(wrapper.find('tr')).toHaveLength(4); }); + it('names each cell according to its column', () => { + wrapper.setProps({name: 'name', names: columnTableNames}); + const th = wrapper.find('TableHeading').at(2); + expect(th.prop('name')).toBe('three'); + const td = wrapper.find('TableData').at(2); + expect(td.prop('name')).toBe('three'); + }); + it('renders rows with header column', () => { - wrapper.setProps({rowIncludesHeading: true}); + wrapper.setProps({rowIncludesHeading: true, nameByRow: true}); expect(wrapper.find('TableHeading')).toHaveLength(7); }); - it('names each cell according to its column', () => { - wrapper.setProps({name: 'name', names}); - const th = wrapper.find('TableHeading').at(2); + it('names each cell according to its row, no titles', () => { + wrapper.setProps({ titles: undefined, names: rowTableNames }); + + let th = wrapper.find('TableHeading').at(0); + expect(th.prop('name')).toBe('one'); + + let td = wrapper.find('TableData').at(0); + expect(td.prop('name')).toBe('one'); + + th = wrapper.find('TableHeading').at(1); + expect(th.prop('name')).toBe('i'); + + td = wrapper.find('TableData').at(3); + expect(td.prop('name')).toBe('am'); + + td = wrapper.find('TableData').at(4); + expect(td.prop('name')).toBe('named'); + + td = wrapper.find('TableData').at(5); + expect(td.prop('name')).toBe('individually'); + + th = wrapper.find('TableHeading').at(2); expect(th.prop('name')).toBe('three'); - const td = wrapper.find('TableData').at(2); - expect(td.prop('name')).toBe('four'); + + td = wrapper.find('TableData').at(8); + expect(td.prop('name')).toBe('three'); + }); + + it('names each cell according to its row, with titles', () => { + wrapper.setProps({ titles, names: rowTableNamesWithTitles }); + + let th = wrapper.find('TableHeading').at(3); + expect(th.prop('name')).toBe('heading'); + + th = wrapper.find('TableHeading').at(4); + expect(th.prop('name')).toBe('one'); + + let td = wrapper.find('TableData').at(0); + expect(td.prop('name')).toBe('one'); + + th = wrapper.find('TableHeading').at(5); + expect(th.prop('name')).toBe('i'); + + td = wrapper.find('TableData').at(3); + expect(td.prop('name')).toBe('am'); + + td = wrapper.find('TableData').at(4); + expect(td.prop('name')).toBe('named'); + + td = wrapper.find('TableData').at(5); + expect(td.prop('name')).toBe('individually'); + + th = wrapper.find('TableHeading').at(6); + expect(th.prop('name')).toBe('three'); + + td = wrapper.find('TableData').at(8); + expect(td.prop('name')).toBe('three'); }); it('sets th width according to rowHeading prop', () => {