From 80006c15e7f345256d953366b47d1ac70704abc9 Mon Sep 17 00:00:00 2001 From: Savut Sang Date: Wed, 24 Apr 2024 15:33:03 -0400 Subject: [PATCH] feat(table)!: expandable rows (#759) * feat(table): expandable rows * fix: accessibility and subrows * fix: tests * fix: story * fix: force deploy storybook * fix: post comments * fix: comments * fix: remove condition from cell render --- .../src/components/table/table-footer.tsx | 6 +- .../src/components/table/table-header.tsx | 11 +- .../react/src/components/table/table-row.tsx | 15 +- .../react/src/components/table/table.test.tsx | 47 +- .../src/components/table/table.test.tsx.snap | 1301 ++++------------- packages/react/src/components/table/table.tsx | 227 ++- packages/react/src/components/table/types.ts | 8 +- packages/react/src/i18n/translations.ts | 6 + packages/react/src/index.ts | 3 +- packages/storybook/stories/table.stories.tsx | 217 ++- packages/storybook/stories/tabs.stories.tsx | 63 +- 11 files changed, 727 insertions(+), 1177 deletions(-) diff --git a/packages/react/src/components/table/table-footer.tsx b/packages/react/src/components/table/table-footer.tsx index 9e81a992fc..28a45765ca 100644 --- a/packages/react/src/components/table/table-footer.tsx +++ b/packages/react/src/components/table/table-footer.tsx @@ -7,11 +7,11 @@ import { flexRender, RowData, } from '@tanstack/react-table'; -import { CustomColumnDef } from './types'; +import { TableColumn } from './types'; interface CustomFooter extends Header { column: Column & { - columnDef: CustomColumnDef; + columnDef: TableColumn; }; } @@ -73,7 +73,7 @@ export const TableFooter = ({ footerGroup, sticky, }: TableFooterProps): ReactElement => ( - + {footerGroup.headers.map((footer) => getFooter(footer as CustomFooter, sticky))} ); diff --git a/packages/react/src/components/table/table-header.tsx b/packages/react/src/components/table/table-header.tsx index 9819625f26..403be6a390 100644 --- a/packages/react/src/components/table/table-header.tsx +++ b/packages/react/src/components/table/table-header.tsx @@ -7,12 +7,13 @@ import { flexRender, RowData, } from '@tanstack/react-table'; +import { devConsole } from '../../utils/dev-console'; import { SortButtonIcon, SortState } from './sort-button-icon'; -import { CustomColumnDef } from './types'; +import { TableColumn } from './types'; interface CustomHeader extends Header { column: Column & { - columnDef: CustomColumnDef; + columnDef: TableColumn; }; } @@ -77,12 +78,12 @@ function getHeading( } if (!header.column.columnDef.header && !header.column.columnDef.headerAriaLabel) { - console.warn( + devConsole.warn( `aria-label missing for column ${header.id} without text. please add headerAriaLabel to column.`, ); } - if (header.column.getCanSort()) { + if (header.column.columnDef.sortable) { return ( ({ headerGroup, sticky, }: TableHeaderProps): ReactElement => ( - + {headerGroup.headers.map((header) => getHeading(header as CustomHeader))} ); diff --git a/packages/react/src/components/table/table-row.tsx b/packages/react/src/components/table/table-row.tsx index dfa03e0a6e..2924874875 100644 --- a/packages/react/src/components/table/table-row.tsx +++ b/packages/react/src/components/table/table-row.tsx @@ -8,18 +8,18 @@ import { } from '@tanstack/react-table'; import styled, { css, FlattenInterpolation, ThemedStyledProps, ThemeProps } from 'styled-components'; import { ResolvedTheme } from '../../themes/theme'; -import { CustomColumnDef } from './types'; +import { TableColumn } from './types'; interface StyledTableRowProps { - $clickable: boolean; - $error: boolean; - $selected: boolean; + $clickable?: boolean; + $error?: boolean; + $selected?: boolean; $striped?: boolean; } interface CustomCell extends Cell { column: Column & { - columnDef: CustomColumnDef; + columnDef: TableColumn; }; } @@ -66,7 +66,7 @@ function getCellBackgroundCss({ `; } -const StyledTableRow = styled.tr` +export const StyledTableRow = styled.tr` &:not(:first-child) { border-top: 1px solid ${({ theme }) => theme.component['table-row-border-color']}; } @@ -167,14 +167,13 @@ export const TableRow = ({ }: TableRowProps): ReactElement => ( onClick && onClick(row)} // eslint-disable-next-line react/jsx-props-no-spreading {...(onClick ? { tabIndex: 0, role: 'button' } : null)} + data-testid={`table-row-${row.id}`} > {row.getVisibleCells().map((cell) => getCell(cell as CustomCell))} diff --git a/packages/react/src/components/table/table.test.tsx b/packages/react/src/components/table/table.test.tsx index 028f6184dc..cebfc867f3 100644 --- a/packages/react/src/components/table/table.test.tsx +++ b/packages/react/src/components/table/table.test.tsx @@ -1,7 +1,8 @@ import { getByTestId } from '../../test-utils/enzyme-selectors'; import { mountWithProviders, mountWithTheme, renderWithProviders } from '../../test-utils/renderer'; import { DeviceType } from '../device-context-provider/device-context-provider'; -import { Table, TableColumn, TableProps } from './table'; +import { Table, TableProps } from './table'; +import { TableColumn } from './types'; interface TestData { column1: string; @@ -33,17 +34,17 @@ const data: TestData[] = [ ]; function renderTable( - columnsArray: TableColumn, + columnsArray: TableColumn[], currentDevice?: DeviceType, props?: TablePropsLite, ): cheerio.Cheerio { return renderWithProviders( - columns={columnsArray} data={data} {...props} />, + , currentDevice, ); } -const columnsWithHeaderAriaLabel: TableColumn = [ +const columnsWithHeaderAriaLabel: TableColumn[] = [ { header: 'Column 1', headerAriaLabel: 'column 1 aria label', @@ -56,7 +57,7 @@ const columnsWithHeaderAriaLabel: TableColumn = [ }, ]; -const columns: TableColumn = [ +const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -67,7 +68,7 @@ const columns: TableColumn = [ }, ]; -const columnsTextAligned: TableColumn = [ +const columnsTextAligned: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -80,7 +81,7 @@ const columnsTextAligned: TableColumn = [ }, ]; -const columnsSorted: TableColumn = [ +const columnsSorted: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -93,7 +94,7 @@ const columnsSorted: TableColumn = [ }, ]; -const columnsSticky: TableColumn = [ +const columnsSticky: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -147,11 +148,13 @@ const stickyColumnsData: TestData3Columns[] = [ describe('Table', () => { test('column sorting should be set to defaultSort value when defaultSort is set', () => { - const wrapper = mountWithProviders(
); + const wrapper = mountWithProviders( +
, + ); expect(getByTestId(wrapper, 'sort-icon').prop('sort')).toBe('ascending'); }); @@ -159,7 +162,7 @@ describe('Table', () => { test('onRowClick callback is called when a row is clicked', () => { const callback = jest.fn(); const wrapper = mountWithTheme( - +
{ const callback = jest.fn(); mountWithTheme( - +
{ test('onSelectedRowsChange callback is called when row-checkbox is checked', () => { const callback = jest.fn(); const wrapper = mountWithTheme( - +
{ test('onSelectedRowsChange callback is called with all rows when row-checkbox-all is checked', () => { const callback = jest.fn(); const wrapper = mountWithTheme( - +
{ }); test('has error rows styles', () => { - const tree = renderWithProviders( columns={columns} data={errorData} />); + const tree = renderWithProviders(
); expect(tree).toMatchSnapshot(); }); test('has selectable rows styles', () => { - const tree = renderWithProviders( selectableRows columns={columns} data={data} />); + const tree = renderWithProviders(
); expect(tree).toMatchSnapshot(); }); test('has sticky header styles', () => { - const tree = renderWithProviders( stickyHeader columns={columns} data={data} />); + const tree = renderWithProviders(
); expect(tree).toMatchSnapshot(); }); test('has sticky column styles', () => { - const tree = renderWithProviders( columns={columnsSticky} data={stickyColumnsData} />); + const tree = renderWithProviders(
); expect(tree).toMatchSnapshot(); }); test('has aria-label on header columns', () => { const tree = renderWithProviders( - +
, diff --git a/packages/react/src/components/table/table.test.tsx.snap b/packages/react/src/components/table/table.test.tsx.snap index a2d17045ca..ebc116c384 100644 --- a/packages/react/src/components/table/table.test.tsx.snap +++ b/packages/react/src/components/table/table.test.tsx.snap @@ -1,40 +1,22 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Table has aria-label on header columns 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7 td { +.c5 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -54,10 +36,6 @@ exports[`Table has aria-label on header columns 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -88,22 +66,14 @@ exports[`Table has aria-label on header columns 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -118,87 +88,62 @@ exports[`Table has aria-label on header columns 1`] = ` > + /> @@ -208,19 +153,19 @@ exports[`Table has aria-label on header columns 1`] = ` `; exports[`Table has clickable rows styles 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7:focus { +.c5:focus { position: relative; } -.c7:focus::after { +.c5:focus::after { box-shadow: inset 0 0 0 2px #84C6EA,inset 0 0 0 3px #006296; content: ''; height: calc(100% + 3px); @@ -232,40 +177,22 @@ exports[`Table has clickable rows styles 1`] = ` z-index: 3; } -.c7:hover { +.c5:hover { cursor: pointer; } -.c7:hover td { +.c5:hover td { background-color: #DBDEE1; } -.c7:not(:hover) td { +.c5:not(:hover) td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -285,10 +212,6 @@ exports[`Table has clickable rows styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -319,22 +242,14 @@ exports[`Table has clickable rows styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -348,93 +263,69 @@ exports[`Table has clickable rows styles 1`] = ` class="c2" > @@ -444,64 +335,28 @@ exports[`Table has clickable rows styles 1`] = ` `; exports[`Table has custom text alignment 1`] = ` -.c9 { +.c6 { background-color: inherit; } -.c9:not(:first-child) { +.c6:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c9 td { +.c6 td { background-color: inherit; } -.c10 { +.c7 { background-color: inherit; text-align: right; } -.c11 { +.c8 { background-color: inherit; text-align: center; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: right; -} - -.c4:focus { - outline: none; -} - -.c7 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: center; -} - -.c7:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -518,14 +373,14 @@ exports[`Table has custom text alignment 1`] = ` width: 100%; } -.c6 { +.c4 { background-color: inherit; box-sizing: border-box; position: relative; text-align: center; } -.c6:before { +.c4:before { border-bottom: 1px solid #DBDEE1; bottom: 0; content: ''; @@ -538,10 +393,6 @@ exports[`Table has custom text alignment 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -572,22 +423,14 @@ exports[`Table has custom text alignment 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c8 { +.c5 { background: inherit; } @@ -601,87 +444,63 @@ exports[`Table has custom text alignment 1`] = ` class="c2" > @@ -691,40 +510,22 @@ exports[`Table has custom text alignment 1`] = ` `; exports[`Table has desktop styles 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7 td { +.c5 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -744,10 +545,6 @@ exports[`Table has desktop styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -778,22 +575,14 @@ exports[`Table has desktop styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -804,90 +593,66 @@ exports[`Table has desktop styles 1`] = ` class="c1" > - - + @@ -897,16 +662,16 @@ exports[`Table has desktop styles 1`] = ` `; exports[`Table has error rows styles 1`] = ` -.c7 { +.c5 { position: relative; background-color: #FAEAE9; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7::after { +.c5::after { box-shadow: inset 0 0 0 1px #CD2C23; content: ''; height: calc(100% + 1px); @@ -917,7 +682,7 @@ exports[`Table has error rows styles 1`] = ` z-index: 3; } -.c7 td:first-child::after { +.c5 td:first-child::after { border-left: 1px solid #CD2C23; content: ''; height: 100%; @@ -927,44 +692,26 @@ exports[`Table has error rows styles 1`] = ` z-index: 3; } -.c7 td { +.c5 td { background-color: inherit; } -.c9 { +.c7 { background-color: inherit; } -.c9:not(:first-child) { +.c7:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c9 td { +.c7 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -984,10 +731,6 @@ exports[`Table has error rows styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -1018,22 +761,14 @@ exports[`Table has error rows styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -1047,87 +782,63 @@ exports[`Table has error rows styles 1`] = ` class="c2" > @@ -1137,40 +848,22 @@ exports[`Table has error rows styles 1`] = ` `; exports[`Table has mobile styles 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7 td { +.c5 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -1190,10 +883,6 @@ exports[`Table has mobile styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -1224,22 +913,14 @@ exports[`Table has mobile styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -1253,87 +934,63 @@ exports[`Table has mobile styles 1`] = ` class="c2" > @@ -1343,40 +1000,22 @@ exports[`Table has mobile styles 1`] = ` `; exports[`Table has rowNumbers styles 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7 td { +.c5 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -1396,10 +1035,6 @@ exports[`Table has rowNumbers styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -1430,6 +1065,10 @@ exports[`Table has rowNumbers styles 1`] = ` } .c0 .eq-table__util-column { + width: 1px; +} + +.c7 { box-sizing: border-box; color: #60666E; font-size: 0.75rem; @@ -1445,7 +1084,7 @@ exports[`Table has rowNumbers styles 1`] = ` background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -1459,118 +1098,94 @@ exports[`Table has rowNumbers styles 1`] = ` class="c2" > @@ -1580,40 +1195,22 @@ exports[`Table has rowNumbers styles 1`] = ` `; exports[`Table has selectable rows styles 1`] = ` -.c15 { +.c13 { background-color: inherit; } -.c15:not(:first-child) { +.c13:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c15 td { +.c13 td { background-color: inherit; } -.c16 { +.c14 { background-color: inherit; } -.c12 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c12:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -1633,10 +1230,6 @@ exports[`Table has selectable rows styles 1`] = ` background-color: inherit; } -.c13 { - margin-left: var(--spacing-1x); -} - .c9 { color: #FFFFFF; display: none; @@ -1758,22 +1351,14 @@ exports[`Table has selectable rows styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c14 { +.c12 { background: inherit; } @@ -1787,7 +1372,7 @@ exports[`Table has selectable rows styles 1`] = ` class="c2" > @@ -2002,40 +1563,22 @@ exports[`Table has selectable rows styles 1`] = ` `; exports[`Table has small rowSize styles 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7 td { +.c5 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -2055,10 +1598,6 @@ exports[`Table has small rowSize styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -2089,22 +1628,14 @@ exports[`Table has small rowSize styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -2118,87 +1649,63 @@ exports[`Table has small rowSize styles 1`] = ` class="c2" > @@ -2295,15 +1802,7 @@ exports[`Table has sorting styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { @@ -2414,19 +1913,19 @@ exports[`Table has sorting styles 1`] = ` `; exports[`Table has sticky column styles 1`] = ` -.c9 { +.c7 { background-color: inherit; } -.c9:not(:first-child) { +.c7:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c9 td { +.c7 td { background-color: inherit; } -.c10 { +.c8 { background-color: inherit; left: 0px; position: -webkit-sticky; @@ -2434,7 +1933,7 @@ exports[`Table has sticky column styles 1`] = ` z-index: 2; } -.c11 { +.c9 { background-color: inherit; left: 75px; position: -webkit-sticky; @@ -2442,28 +1941,10 @@ exports[`Table has sticky column styles 1`] = ` z-index: 2; } -.c12 { +.c10 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -2483,7 +1964,7 @@ exports[`Table has sticky column styles 1`] = ` width: 100%; } -.c6 { +.c4 { background-color: inherit; box-sizing: border-box; position: relative; @@ -2493,7 +1974,7 @@ exports[`Table has sticky column styles 1`] = ` z-index: 5; } -.c6:before { +.c4:before { border-bottom: 1px solid #DBDEE1; bottom: 0; content: ''; @@ -2502,13 +1983,13 @@ exports[`Table has sticky column styles 1`] = ` width: 100%; } -.c7 { +.c5 { background-color: inherit; box-sizing: border-box; position: relative; } -.c7:before { +.c5:before { border-bottom: 1px solid #DBDEE1; bottom: 0; content: ''; @@ -2521,10 +2002,6 @@ exports[`Table has sticky column styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -2555,22 +2032,14 @@ exports[`Table has sticky column styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c8 { +.c6 { background: inherit; } @@ -2584,120 +2053,84 @@ exports[`Table has sticky column styles 1`] = ` class="c2" > @@ -2707,40 +2140,22 @@ exports[`Table has sticky column styles 1`] = ` `; exports[`Table has sticky header styles 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7 td { +.c5 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -2764,10 +2179,6 @@ exports[`Table has sticky header styles 1`] = ` z-index: 6; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -2798,22 +2209,14 @@ exports[`Table has sticky header styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -2827,87 +2230,63 @@ exports[`Table has sticky header styles 1`] = ` class="c2" > @@ -2917,44 +2296,26 @@ exports[`Table has sticky header styles 1`] = ` `; exports[`Table has striped styles 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7:nth-child(odd) { +.c5:nth-child(odd) { background-color: #FAFAFA; } -.c7 td { +.c5 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -2974,10 +2335,6 @@ exports[`Table has striped styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -3008,22 +2365,14 @@ exports[`Table has striped styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -3037,87 +2386,63 @@ exports[`Table has striped styles 1`] = ` class="c2" > @@ -3127,40 +2452,22 @@ exports[`Table has striped styles 1`] = ` `; exports[`Table has tablet styles 1`] = ` -.c7 { +.c5 { background-color: inherit; } -.c7:not(:first-child) { +.c5:not(:first-child) { border-top: 1px solid #DBDEE1; } -.c7 td { +.c5 td { background-color: inherit; } -.c8 { +.c6 { background-color: inherit; } -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - font: inherit; - text-align: left; -} - -.c4:focus { - outline: none; -} - .c3 { background-color: inherit; box-sizing: border-box; @@ -3180,10 +2487,6 @@ exports[`Table has tablet styles 1`] = ` background-color: inherit; } -.c5 { - margin-left: var(--spacing-1x); -} - .c0 { background: #FFFFFF; border-collapse: collapse; @@ -3214,22 +2517,14 @@ exports[`Table has tablet styles 1`] = ` } .c0 .eq-table__util-column { - box-sizing: border-box; - color: #60666E; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - -webkit-transform: translateX(-50%); - -ms-transform: translateX(-50%); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } .c1 { background: inherit; } -.c6 { +.c4 { background: inherit; } @@ -3243,87 +2538,63 @@ exports[`Table has tablet styles 1`] = ` class="c2" > diff --git a/packages/react/src/components/table/table.tsx b/packages/react/src/components/table/table.tsx index e777f5c76a..fa88b13cea 100644 --- a/packages/react/src/components/table/table.tsx +++ b/packages/react/src/components/table/table.tsx @@ -1,7 +1,6 @@ -import { ReactElement, useRef, useState, useMemo, useEffect } from 'react'; +import { ReactElement, useRef, useState, useMemo, useEffect, Fragment } from 'react'; import styled from 'styled-components'; import { - HeaderContext, Row, getCoreRowModel, getSortedRowModel, @@ -10,22 +9,24 @@ import { ColumnSort, Updater, functionalUpdate, + getExpandedRowModel, + ExpandedState, + TableOptions, + RowSelectionState, } from '@tanstack/react-table'; -import { TableRow } from './table-row'; +import { TFunction } from 'i18next'; +import { useTranslation } from '../../i18n/use-translation'; +import { IconButton } from '../buttons/icon-button'; +import { StyledTableRow, TableRow } from './table-row'; import { TableHeader } from './table-header'; import { TableFooter } from './table-footer'; import { Checkbox } from '../checkbox/checkbox'; import { DeviceType, useDeviceContext } from '../device-context-provider/device-context-provider'; -import { CustomColumnDef } from './types'; +import { TableData, TableColumn } from './types'; type RowSize = 'small' | 'medium'; -export type TableColumn = CustomColumnDef[]; -export type TableRow = T & { error?: boolean }; - -interface CustomRowProps { - error?: boolean; -} +type UtilityColumnType = 'selection' | 'numbers' | 'expand'; function getThPadding(device: DeviceType, rowSize?: RowSize): string { if (rowSize === 'small') { @@ -69,50 +70,6 @@ function getTdPadding(device: DeviceType, rowSize?: RowSize): string { const utilColumnClassName = 'eq-table__util-column'; -function getCustomColumn(type: string): CustomColumnDef { - return { - id: type, - header(props: HeaderContext) { - if (type === 'selection') { - const { table } = props; - return ( - - ); - } - // For 'numbers' type or any other type, return null or an empty header - return null; - }, - cell({ row }) { - if (type === 'selection') { - return ( - - ); - } - - if (type === 'numbers') { - return ( - - {row.index + 1} - - ); - } - - return null; - }, - }; -} - interface StyledTableProps { $clickableRows: boolean; $device: DeviceType; @@ -148,16 +105,20 @@ const StyledTable = styled.table` } .${utilColumnClassName} { - box-sizing: border-box; - color: ${({ theme }) => theme.component['table-cell-number-text-color']}; - font-size: 0.75rem; - margin-left: 50%; - min-width: var(--size-2halfx); - transform: translateX(-50%); - width: var(--size-2halfx); + width: 1px; } `; +const RowNumber = styled.span` + box-sizing: border-box; + color: ${({ theme }) => theme.component['table-cell-number-text-color']}; + font-size: 0.75rem; + margin-left: 50%; + min-width: var(--size-2halfx); + transform: translateX(-50%); + width: var(--size-2halfx); +`; + const StyledTHead = styled.thead` background: inherit; `; @@ -170,10 +131,79 @@ const StyledTFoot = styled.tfoot` background: inherit; `; +const ExpandButton = styled(IconButton) <{ $expanded: boolean }>` + transform: rotate(${({ $expanded }) => ($expanded ? 90 : 0)}deg); + transition: transform 0.2s ease-in-out; + + &[aria-expanded='true'] { + background-color: transparent; + } +`; + +function getUtilityColumn(type: UtilityColumnType, t: TFunction<'translation'>): TableColumn { + const column: TableColumn = { + id: type, + className: utilColumnClassName, + }; + + switch (type) { + case 'selection': + column.header = ({ table }) => ( + + ); + column.cell = ({ row }) => ( + + ); + break; + + case 'numbers': + column.cell = ({ row }) => {row.index + 1}; + break; + + case 'expand': + column.cell = ({ row }) => { + const isExpanded = row.getIsExpanded(); + if (!row.getCanExpand()) { + return null; + } + return ( + 0 + ? t('subrowsAriaLabel', { count: row.subRows.length }) + : undefined + } + /> + ); + }; + break; + } + + return column; +} + export interface TableProps { data: T[]; defaultSort?: ColumnSort; - columns: CustomColumnDef[]; + columns: TableColumn[]; + expandableRows?: 'single' | 'multiple'; /** * Adds row numbers * @default false @@ -203,7 +233,8 @@ export const Table = ({ className, data, defaultSort, - columns: defaultColumns, + columns: providedColumns, + expandableRows, stickyHeader = false, stickyFooter = false, rowNumbers = false, @@ -215,38 +246,62 @@ export const Table = ({ onSelectedRowsChange, onSort, }: TableProps): ReactElement => { + const { t } = useTranslation('table'); const tableRef = useRef(null); const { device } = useDeviceContext(); const [sorting, setSorting] = useState(defaultSort ? [defaultSort] : []); - const [rowSelection, setRowSelection] = useState({}); + const [rowSelection, setRowSelection] = useState({}); + const [expanded, setExpanded] = useState({}); - // Add custom columns for row numbers and row selection + // Add utility columns for row numbers and row selection const columns = useMemo(() => { - const cols = [...defaultColumns]; + const cols = [...providedColumns]; + if (selectableRows) { - cols.unshift(getCustomColumn('selection')); + cols.unshift(getUtilityColumn('selection', t)); + } else if (expandableRows) { + cols.unshift(getUtilityColumn('expand', t)); } else if (rowNumbers) { - cols.unshift(getCustomColumn('numbers')); + cols.unshift(getUtilityColumn('numbers', t)); } + return cols; - }, [selectableRows, rowNumbers, defaultColumns]); + }, [selectableRows, expandableRows, rowNumbers, providedColumns, t]); - const tableOptions = { + const tableOptions: TableOptions = { data, columns, state: { sorting, rowSelection, + expanded, }, enableMultiSort: false, manualSorting: manualSort, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getSubRows: ((row) => (row as any).subRows), + getExpandedRowModel: getExpandedRowModel(), + getRowCanExpand: + expandableRows + ? ((row) => row.subRows.length > 0 || !!(row.original as TableData).subContent) + : undefined, onSortingChange: (updater: Updater) => { const newValue = functionalUpdate(updater, sorting); setSorting(newValue); onSort?.(newValue[0] ?? null); }, + onExpandedChange: (updater: Updater) => { + let newValue = functionalUpdate(updater, expanded); + + // Hackish because onExpandedChange doesn't provide the currently expanded/collapsed row + if (expandableRows === 'single' && Object.keys(newValue).length > 1) { + newValue = functionalUpdate(updater, {}); + } + + setExpanded(newValue); + }, enableRowSelection: true, onRowSelectionChange: setRowSelection, }; @@ -260,7 +315,6 @@ export const Table = ({ const selectedRowIds = currentRowSelection; const selectedIndexes = Object.keys(selectedRowIds).filter((index) => selectedRowIds[index]); const selectedRows = selectedIndexes.map((index) => data[parseInt(index, 10)]); - onSelectedRowsChange(selectedRows); } }, [selectableRows, currentRowSelection, onSelectedRowsChange, data]); @@ -277,25 +331,40 @@ export const Table = ({ {table.getHeaderGroups().map((headerGroup) => ( + key={headerGroup.id} headerGroup={headerGroup} sticky={stickyHeader} /> ))} - {table.getRowModel().rows.map((row) => ( - - striped={striped} - error={!!(row.original as CustomRowProps).error} - row={row} - onClick={onRowClick} - /> - ))} + {table.getRowModel().rows.map((row) => { + const rowOriginal = row.original as TableData; + return ( + + + striped={striped} + error={!!rowOriginal.error} + row={row} + onClick={onRowClick} + /> + {rowOriginal.subContent && row.getIsExpanded() && ( + + + + )} + + ); + })} {hasFooter && ( {table.getFooterGroups().map((footerGroup) => ( + key={footerGroup.id} footerGroup={footerGroup} sticky={stickyFooter} /> diff --git a/packages/react/src/components/table/types.ts b/packages/react/src/components/table/types.ts index b2ba3c0c65..5f9f5b5e76 100644 --- a/packages/react/src/components/table/types.ts +++ b/packages/react/src/components/table/types.ts @@ -1,7 +1,13 @@ import { ColumnDef, RowData } from '@tanstack/react-table'; import { CSSProperties } from 'react'; -export type CustomColumnDef = ColumnDef & { +export type TableData = TData & { + error?: boolean; + subRows?: TableData[]; + subContent?: React.ReactNode; +}; + +export type TableColumn = ColumnDef & { className?: string; footerColSpan?: number; headerAriaLabel?: string; diff --git a/packages/react/src/i18n/translations.ts b/packages/react/src/i18n/translations.ts index b856530cd9..ace4cfdd2e 100644 --- a/packages/react/src/i18n/translations.ts +++ b/packages/react/src/i18n/translations.ts @@ -114,6 +114,9 @@ export const Translations = { 'tag-medium': { deleteButtonAriaLabel: 'Remove tag {{label}}', }, + table: { + subrowsAriaLabel: '{{count}} subrows for this item', + }, 'text-area': { validationErrorMessage: 'This text area is invalid', maxLengthValidationErrorMessage: @@ -244,6 +247,9 @@ export const Translations = { 'increment-button-aria-label': 'Augmenter le nombre', 'decrement-button-aria-label': 'Diminuer le nombre', }, + table: { + subrowsAriaLabel: '{{count}} sous-éléments pour cet élément', + }, 'tag-medium': { deleteButtonAriaLabel: 'Retirer l\'étiquette {{label}}', }, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 46ad9ccbb9..ce70474563 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -67,7 +67,8 @@ export { SideDrawer } from './components/side-drawer/side-drawer'; export { GlobalBanner, GlobalBannerType } from './components/global-banner/global-banner'; export * from './components/tooltip/tooltip'; export * from './components/toggletip/toggletip'; -export { Table, TableColumn, TableRow } from './components/table/table'; +export { Table } from './components/table/table'; +export { TableColumn, TableData } from './components/table/types'; export { Modal } from './components/modal/modal'; export { ModalDialog } from './components/modal/modal-dialog'; export { Tag, TagProps, TagValue } from './components/tag/tag'; diff --git a/packages/storybook/stories/table.stories.tsx b/packages/storybook/stories/table.stories.tsx index bdadbaa4db..da5afc4619 100644 --- a/packages/storybook/stories/table.stories.tsx +++ b/packages/storybook/stories/table.stories.tsx @@ -1,5 +1,5 @@ import { ReactElement, useMemo, useRef, useState } from 'react'; -import { Button, Pagination, Table, TableColumn, TableRow, TextInput, Tooltip } from '@equisoft/design-elements-react'; +import { Button, Pagination, Table, TableColumn, TableData, TextInput, Tooltip } from '@equisoft/design-elements-react'; import { StoryFn as Story } from '@storybook/react'; import styled from 'styled-components'; import { rawCodeParameters } from './utils/parameters'; @@ -17,7 +17,7 @@ interface Data { } export const Normal: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -32,7 +32,7 @@ export const Normal: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -69,7 +69,7 @@ const StyledTable = styled( `; export const WithColumnClassnames: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -87,7 +87,7 @@ export const WithColumnClassnames: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -116,7 +116,7 @@ interface FooterData { } export const WithFooter: Story = () => { - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -146,7 +146,7 @@ export const WithFooter: Story = () => { }; // Calculate the total sum of 'numbers' - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -172,7 +172,7 @@ export const WithFooter: Story = () => { }; export const ErrorRows: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -187,7 +187,7 @@ export const ErrorRows: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -212,7 +212,7 @@ export const ErrorRows: Story = () => { }; export const Striped: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -227,7 +227,7 @@ export const Striped: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -250,7 +250,7 @@ export const Striped: Story = () => { }; export const RowNumbers: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -265,7 +265,7 @@ export const RowNumbers: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -288,7 +288,7 @@ export const RowNumbers: Story = () => { }; export const SmallRows: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -303,7 +303,7 @@ export const SmallRows: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -333,7 +333,7 @@ export const RowClickCallback: Story = () => { href: string; } - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -348,7 +348,7 @@ export const RowClickCallback: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -376,7 +376,7 @@ export const RowClickCallback: Story = () => { }; export const CustomTextAlignment: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -393,7 +393,7 @@ export const CustomTextAlignment: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'Hello', column2: 'World', @@ -454,7 +454,7 @@ export const CustomColumns: Story = () => { }, ]; - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Category', accessorKey: 'category', @@ -470,7 +470,7 @@ export const CustomColumns: Story = () => { ]; return ( - columns={columns} data={data} /> +
- + Column 1 - -
Hello World
Hello Planet
Hello Galaxy - + Column 1 - + Column 2
Hello World
Hello Planet
Hello Galaxy - + Column 1 - + Column 2
Hello World
Hello Planet
Hello Galaxy
- - - + class="c2" + > + + Column 1 + + Column 2
Hello World
Hello Planet
Hello Galaxy - + Column 1 - + Column 2
Hello World
Hello World
Hello World - + Column 1 - + Column 2
Hello World
Hello Planet
Hello Galaxy - + Column 1 - + Column 2
1 Hello World
2 Hello Planet
3 Hello Galaxy - + Column 1 - + Column 2
Hello World
Hello Planet
Hello Galaxy - + Column 1 - + Column 2
Hello World
Hello Planet
Hello Galaxy - + Column 1 - + Column 2 - + Column 3
Hello Big World
Hello Big World
Hello Big World - + Column 1 - + Column 2
Hello World
Hello Planet
Hello Galaxy - + Column 1 - + Column 2
Hello World
Hello Planet
Hello Galaxy - + Column 1 - + Column 2
Hello World
Hello Planet
Hello Galaxy + + {rowOriginal.subContent} +
); }; @@ -481,7 +481,7 @@ export const SortableRows: Story = () => { column3: number; } - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -498,7 +498,7 @@ export const SortableRows: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -511,7 +511,7 @@ export const SortableRows: Story = () => { }, ]; return ( - columns={columns} data={data} defaultSort={{ id: 'column2', desc: false }} /> +
); }; @@ -522,7 +522,7 @@ export const SelectableRows: Story = () => { column3: number; } - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -537,7 +537,7 @@ export const SelectableRows: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -550,7 +550,140 @@ export const SelectableRows: Story = () => { }, ]; return ( - selectableRows columns={columns} data={data} onSelectedRowsChange={console.info} /> +
+ ); +}; + +export const ExpandableSubrowsMultiple: Story = () => { + interface ExpandableData { + id: string; + name: string; + } + + const columns: TableColumn[] = [ + { + header: 'ID', + accessorKey: 'id', + }, + { + header: 'Name', + accessorKey: 'name', + }, + ]; + + const data: TableData[] = [ + { + id: '1', + name: 'AAA', + subRows: [ + { id: '1.A', name: 'AAA-1' }, + { id: '1.B', name: 'AAA-2' }, + ], + }, + { + id: '2', + name: 'BBB', + subRows: [ + { id: '2.A', name: 'BBB-1' }, + { id: '2.B', name: 'BBB-2' }, + ], + }, + ]; + return ( +
+ ); +}; + +export const ExpandableSubrowsSingle: Story = () => { + interface ExpandableData { + id: string; + name: string; + } + + const columns: TableColumn[] = [ + { + header: 'ID', + accessorKey: 'id', + }, + { + header: 'Name', + accessorKey: 'name', + }, + ]; + + const data: TableData[] = [ + { + id: '1', + name: 'AAA', + subRows: [ + { id: '1.1', name: 'AAA-1' }, + { id: '1.2', name: 'AAA-2' }, + ], + }, + { + id: '2', + name: 'BBB', + subRows: [ + { id: '2.1', name: 'BBB-1' }, + { id: '2.2', name: 'BBB-2' }, + ], + }, + ]; + return ( +
+ ); +}; + +export const ExpandableSubContent: Story = () => { + interface ExpandableData { + id: string; + name: string; + } + + const columns: TableColumn[] = [ + { + header: 'ID', + accessorKey: 'id', + }, + { + header: 'Name', + accessorKey: 'name', + }, + ]; + + const data: TableData[] = [ + { + id: '1', + name: 'AAA', + subContent: 'Sub content in plain text', + }, + { + id: '2', + name: 'BBB', + subContent: ( + <> + Sub content with HTML +
+ BBB + + ), + }, + ]; + + return ( +
); }; @@ -569,7 +702,7 @@ interface StickyData { column12: string; column13: string; column14: string; - column15: string, + column15: string; } const ScrollableWrap = styled.div` @@ -578,7 +711,7 @@ const ScrollableWrap = styled.div` `; export const Sticky: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -643,7 +776,7 @@ export const Sticky: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -815,7 +948,7 @@ interface StickyHeaderFooterData { } export const StickyFooter: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -838,7 +971,7 @@ export const StickyFooter: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -903,7 +1036,7 @@ export const StickyFooter: Story = () => { }; export const StickyHeaderAndFooter: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -926,7 +1059,7 @@ export const StickyHeaderAndFooter: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -1001,7 +1134,7 @@ const StyledTableWithBackground = styled(Table)` `; export const WithBackgroundColor: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column 1', accessorKey: 'column1', @@ -1024,7 +1157,7 @@ export const WithBackgroundColor: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -1096,7 +1229,7 @@ export const WithBackgroundColor: Story = () => { }; export const HeaderAriaLabel: Story = () => { - const columns: TableColumn = [ + const columns: TableColumn[] = [ { header: 'Column with text', headerAriaLabel: '', @@ -1114,7 +1247,7 @@ export const HeaderAriaLabel: Story = () => { }, ]; - const data: TableRow[] = [ + const data: TableData[] = [ { column1: 'a', column2: 'a', @@ -1166,7 +1299,7 @@ export const Optimization: Story = () => { const allowEditingRef = useRef(); allowEditingRef.current = allowEditing; - const columns: TableColumn = useMemo(() => [ + const columns: TableColumn[] = useMemo(() => [ { header: 'ID', accessorKey: 'id', @@ -1214,7 +1347,7 @@ interface TablePaginationData { country: string; } -function makeData(): TableRow[] { +function makeData(): TableData[] { const countries = ['Canada', 'United States', 'France', 'Germany', 'Italy', 'Spain', 'Portugal', 'Japan']; return [...Array(35).keys()].map((i) => ({ id: i + 1, @@ -1243,10 +1376,10 @@ export const TableWithPagination: Story = () => { const ITEMS_PER_PAGE = 10; - const [data, setData] = useState[]>(makeData()); + const [data, setData] = useState[]>(makeData()); const [currentPage, setCurrentPage] = useState(1); - const columns: TableColumn = useMemo(() => [ + const columns: TableColumn[] = useMemo(() => [ { header: 'ID', accessorKey: 'id', diff --git a/packages/storybook/stories/tabs.stories.tsx b/packages/storybook/stories/tabs.stories.tsx index 6ed1955639..dc682532d6 100644 --- a/packages/storybook/stories/tabs.stories.tsx +++ b/packages/storybook/stories/tabs.stories.tsx @@ -1,4 +1,4 @@ -import { Tab, Tabs } from '@equisoft/design-elements-react'; +import { Card, Tab, Table, TableColumn, Tabs, TextArea } from '@equisoft/design-elements-react'; import { StoryFn as Story } from '@storybook/react'; import styled from 'styled-components'; import { rawCodeParameters } from './utils/parameters'; @@ -13,6 +13,67 @@ const StyledDiv = styled.div` padding: var(--spacing-2x); `; +interface Data { + column1: string; + column2: string; +} + +export const Normal: Story = () => { + const contactTableColumns: TableColumn[] = [ + { + header: 'First Name', + accessorKey: 'column1', + }, + { + header: 'Last Name', + accessorKey: 'column2', + }, + ]; + + const contactTableData: Data[] = [ + { + column1: 'First Name 1', + column2: 'First Name 2', + }, + { + column1: 'Last Name 1', + column2: 'Last Name 2', + }, + ]; + + const tabs: Tab[] = [ + { + title: 'Contact', + panelContent: ( + +
+ + ), + }, + { + title: 'Calendar', + panelContent: ( + + Monday : Doing something meaningful + Tuesday : Doing something else + + ), + }, + { + title: 'Note', + panelContent: ( + +