From 5fa0bd4b171434f1cb4be2e4c384759d8c13c1e6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 3 Jul 2023 09:18:06 +1000 Subject: [PATCH 1/6] Introducing table footer to gumdrops, fixed bug with "Infinity" for ImagePreview --- components/atoms/ImagePreview.jsx | 6 ++---- components/molecules/Table.jsx | 30 ++++++++++++++++++++++++++++ components/molecules/TableFooter.jsx | 12 +++++++++++ stories/molecules/Table.mdx | 8 ++++++++ stories/molecules/Table.stories.js | 18 ++++++++++++++++- 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 components/molecules/TableFooter.jsx diff --git a/components/atoms/ImagePreview.jsx b/components/atoms/ImagePreview.jsx index 866ad55..bb94841 100644 --- a/components/atoms/ImagePreview.jsx +++ b/components/atoms/ImagePreview.jsx @@ -115,10 +115,8 @@ function ImagePreview({ break; } if ( - typeof tooltipX === 'number' && - typeof tooltipY === 'number' && - !isNaN(tooltipY) && - !isNaN(tooltipX) + isFinite(tooltipY) && + isFinite(tooltipX) ) { return { left: tooltipX, diff --git a/components/molecules/Table.jsx b/components/molecules/Table.jsx index 469bfe0..96ddfd2 100644 --- a/components/molecules/Table.jsx +++ b/components/molecules/Table.jsx @@ -8,6 +8,7 @@ import Heading from './TableHeading'; import Body from './TableBody'; import Row from './TableRow'; import Data from './TableData'; +import Footer from './TableFooter'; import Icon from '../atoms/Icon'; import { deprecateLog } from '../utils/deprecate'; @@ -153,6 +154,7 @@ class Table extends Component { hasHeader, onRowClick, isSecondary, + footer, }) { const { sortBy, data } = this.state; return ( @@ -297,6 +299,17 @@ class Table extends Component { ); })} + {typeof footer === 'undefined' ? null : typeof footer === 'object' && !React.isValidElement(footer) ? : React.isValidElement(footer) ? footer : null} ); } @@ -328,6 +341,23 @@ class Table extends Component { } Table.propTypes = { + /** An `Object` matching the column keys or JSX element */ + footer: function(props, propName, componentName) { + const propValue = props[propName]; + // not a required value so if undefined, don't throw an error + // If undefined, don't throw an error + if (typeof propValue === 'undefined') { + return; + } + const isObject = typeof propValue === 'object'; + // Validate the prop value + if (!React.isValidElement(propValue) || !isObject) { + return new Error( + `Invalid prop ${propName} supplied to ${componentName}. + Expected undefined, a JSX element, or an object matching keys of columns.` + ); + } + }, // necessary for controlled tables /** An `Array` of `Objects` with simple key/value pairs. */ data: function(props, propName, componentName) { diff --git a/components/molecules/TableFooter.jsx b/components/molecules/TableFooter.jsx new file mode 100644 index 0000000..8f63efd --- /dev/null +++ b/components/molecules/TableFooter.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; + +const TableFooter = ({ className, children }) => {children}; + +TableFooter.propTypes = { + className: PropTypes.string, + children: PropTypes.node +}; + +export default TableFooter; diff --git a/stories/molecules/Table.mdx b/stories/molecules/Table.mdx index bd15098..a3e2504 100644 --- a/stories/molecules/Table.mdx +++ b/stories/molecules/Table.mdx @@ -225,6 +225,14 @@ const People = () => ; +## Component Example Footer + + + +## Component Example Source Footer + + + ## Table Props diff --git a/stories/molecules/Table.stories.js b/stories/molecules/Table.stories.js index dac6759..997e76b 100644 --- a/stories/molecules/Table.stories.js +++ b/stories/molecules/Table.stories.js @@ -1,5 +1,6 @@ import Table from '../../components/molecules/Table'; import TableData from '../../components/molecules/TableData'; +import TableFooter from '../../components/molecules/TableFooter'; import ToolTip from '../../components/atoms/Tooltip'; const columnsSimple = ['id', 'name', 'title', 'age', 'height']; @@ -179,6 +180,7 @@ const Template = args => { ); }; + export const Simple = Template.bind({}); Simple.args = { columns: columnsSimple, @@ -196,7 +198,7 @@ Custom.parameters = { controls: { exclude: ['children', 'data'] } }; export const Advanced = Template.bind({}); Advanced.args = { columns: columnsAdvanced, - numOfRows: 10 + numOfRows: 10, }; Advanced.parameters = { controls: { exclude: ['children', 'data'] } }; @@ -207,3 +209,17 @@ Expandable.args = { customRowKey: 'id' }; Expandable.parameters = { controls: { exclude: ['children', 'data'] } }; + +export const Footer = Template.bind({}); + +Footer.args = { + columns: columnsAdvanced, + numOfRows: 5, + footer: { + id: '123', + name: '', + title: '', + age: 'Average Age: 30', + height: 'Average Height: 5\'8"', + } +} From 597dec023166655e1a600aef94ee0af005ded41b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 3 Jul 2023 09:21:45 +1000 Subject: [PATCH 2/6] Adding changelog entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1674584..e48c212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # GumDrops Changelog +# 1.16.1 + +- Introduce TableFooter component for tables +- Fixed bug with ImagePreview component + # 1.16.0 - Upgrade Storybook version to 7 - requires react and react-dom be added as dev dependencies From cef9e094b4cafe1d68e6c26d81b4ae72cbedd4da Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 3 Jul 2023 09:46:11 +1000 Subject: [PATCH 3/6] Adding some tests --- _tests/molecules/Table.test.js | 67 ++++++++++++++++++- .../__snapshots__/Table.test.js.snap | 40 +++++++++++ components/molecules/Table.jsx | 2 +- components/molecules/TableFooter.jsx | 2 +- 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/_tests/molecules/Table.test.js b/_tests/molecules/Table.test.js index d2414f8..8e9b7c2 100644 --- a/_tests/molecules/Table.test.js +++ b/_tests/molecules/Table.test.js @@ -7,6 +7,8 @@ import React from 'react'; import Table from '../../components/molecules/Table'; import TableBody from '../../components/molecules/TableBody'; import TableData from '../../components/molecules/TableData'; +import TableFooter from '../../components/molecules/TableFooter'; +import TableRow from '../../components/molecules/TableRow'; const defaultProps = { data: [ @@ -193,11 +195,72 @@ test('Expect
to expand the row when the chevron is clicked', () => { } ] }; - const { queryByTestId, debug } = render(
); - debug(); + const { queryByTestId } = render(
); const expandableRow = queryByTestId('expandable-row-Foo'); expect(expandableRow.querySelector('.gds-table--collapsible').style.height).toEqual('0px'); fireEvent.click(queryByTestId('row-Foo-key-1')); expect(queryByTestId('row-Foo-key-1')).toBeInTheDocument(); expect(expandableRow.querySelector('.gds-table--collapsible').style.height).toEqual('100px'); }); + + +test('Expect
to render the table footer component as an object', () => { + const props = { + ...defaultProps, + customRowKey: 'foo', + columns: [ + { + key: 'foo', + children: 'Foo', + headingProps: { style: { width: 100, onClick: jest.fn() } } + }, + { + key: 'bar', + children: 'Bar', + renderExpandableColumn(cellData) { + return
Expandable - {cellData}
; + } + } + ], + footer: { + foo: 'Bar Footer', + bar: 'Foo Footer' + } + }; + const { queryByTestId, debug } = render(
); + debug(); + const footer = queryByTestId('table-footer'); + expect(footer).toBeInTheDocument(); + expect(footer).toMatchSnapshot(); +}); + +test('Expect
to render the table footer component as a JSX.Element', () => { + const props = { + ...defaultProps, + customRowKey: 'foo', + columns: [ + { + key: 'foo', + children: 'Foo', + headingProps: { style: { width: 100, onClick: jest.fn() } } + }, + { + key: 'bar', + children: 'Bar', + renderExpandableColumn(cellData) { + return
Expandable - {cellData}
; + } + } + ], + footer: + + Footer! + + + }; + const { queryByTestId, debug } = render(
); + debug(); + const footer = queryByTestId('table-footer-manual'); + expect(footer).toBeInTheDocument(); + expect(footer).toMatchSnapshot(); +}); diff --git a/_tests/molecules/__snapshots__/Table.test.js.snap b/_tests/molecules/__snapshots__/Table.test.js.snap index 695a5c7..e3840d9 100644 --- a/_tests/molecules/__snapshots__/Table.test.js.snap +++ b/_tests/molecules/__snapshots__/Table.test.js.snap @@ -76,6 +76,46 @@ exports[`Expect
to render properly 1`] = `
`; +exports[`Expect to render the table footer component as a JSX.Element 1`] = ` + + + + + +`; + +exports[`Expect
+ Footer! +
to render the table footer component as an object 1`] = ` + + + + + + +`; + exports[`Expect
+ Bar Footer + + Foo Footer +
to render using advanced configs 1`] = `
- {typeof footer === 'undefined' ? null : typeof footer === 'object' && !React.isValidElement(footer) ? {children}; +const TableFooter = ({ className, children, ...rest }) => {children}; TableFooter.propTypes = { className: PropTypes.string, From e1c6e4bc40ce8886bb73f65a5b80e5440dc7b0e1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 3 Jul 2023 09:46:21 +1000 Subject: [PATCH 4/6] Removing debugger --- _tests/molecules/Table.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/_tests/molecules/Table.test.js b/_tests/molecules/Table.test.js index 8e9b7c2..5d04663 100644 --- a/_tests/molecules/Table.test.js +++ b/_tests/molecules/Table.test.js @@ -258,8 +258,7 @@ test('Expect
to render the table footer component as a JSX.Element', () }; - const { queryByTestId, debug } = render(
); - debug(); + const { queryByTestId } = render(
); const footer = queryByTestId('table-footer-manual'); expect(footer).toBeInTheDocument(); expect(footer).toMatchSnapshot(); From 9f0ea0ac1f3e88a30fbc9c11ac657fa25ec36c52 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 5 Jul 2023 11:19:47 +1000 Subject: [PATCH 5/6] Adding table footer docs --- stories/molecules/Table.mdx | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/stories/molecules/Table.mdx b/stories/molecules/Table.mdx index a3e2504..87b3dd8 100644 --- a/stories/molecules/Table.mdx +++ b/stories/molecules/Table.mdx @@ -191,6 +191,62 @@ const columns = [{ const People = () =>
; ``` +### Table Footer + +The table footer can be used to display totals or other summary information. The footer is rendered as a `` element. + +The footer will allow you to have a "static" row at the bottom of the table that will not be affected by sorting. + +The footer prop can accept JSX or an object containing a key/value pair object where the "keys" are the column keys. + +The following example will create a table with a footer that will display the total age of all the people in the table. + +```jsx +const columns = [ + { key: 'name', children: 'First Name' }, + { key: 'age', children: 'Age (years)' }, +]; +const data = [ + { name: 'Alice', age: 20, id: 'x123' }, + { name: 'Bob', age: 24, id: 'y456' } +]; +const footer = { + name: 'Total Age', + age: data.reduce((acc, row) => acc + row.age, 0) // -> 44 +} +const People = () =>
; +``` + + +### Custom Table Footer + +We can also include JSX to render a custom table footer by importing the components manually. + +```jsx +import TableFooter from 'gumdrops/TableFooter'; +import TableRow from 'gumdrops/TableRow'; +import TableData from 'gumdrops/TableData'; +const columns = [ + { key: 'name', children: 'First Name' }, + { key: 'age', children: 'Age (years)' }, +]; +const data = [ + { name: 'Alice', age: 20, id: 'x123' }, + { name: 'Bob', age: 24, id: 'y456' } +]; + +function CustomFooter() { + return + + + The average age is {data.reduce((acc, row) => acc + row.age, 0) / data.length} + + + +} +const People = () =>
; + +``` ## Component Example Simple From 97d83705ffbcdce04c64e13690b9e6d20b3d3beb Mon Sep 17 00:00:00 2001 From: Shannon Hochkins Date: Thu, 6 Jul 2023 10:50:52 +1000 Subject: [PATCH 6/6] 1.17.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cd06d49..1bdd7ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gumdrops", - "version": "1.17.2", + "version": "1.17.3", "description": "GumGum's React Components Library", "license": "MIT", "files": [