diff --git a/src/components/Table/Internal/Body/Body.types.ts b/src/components/Table/Internal/Body/Body.types.ts index 76613bfed..9a59f6495 100644 --- a/src/components/Table/Internal/Body/Body.types.ts +++ b/src/components/Table/Internal/Body/Body.types.ts @@ -4,6 +4,7 @@ import type { GetComponentProps, Key, GetRowKey, + RowHeaderCellComponent, } from '../OcTable.types'; export interface MeasureRowProps extends Omit { @@ -35,6 +36,7 @@ export interface BodyRowProps { expandedKeys: Set; rowComponent: CustomizeComponent; cellComponent: CustomizeComponent; + rowHeaderCellComponent?: RowHeaderCellComponent; onRow: GetComponentProps; rowExpandable: (record: RecordType) => boolean; rowExpandDisabled?: (record: RecordType) => boolean; @@ -74,6 +76,7 @@ export interface BodyProps { onRow: GetComponentProps; rowExpandable: (record: RecordType) => boolean; rowExpandDisabled?: (record: RecordType) => boolean; + rowHeaderCellComponent?: RowHeaderCellComponent; emptyNode: React.ReactNode; childrenColumnName: string; /** diff --git a/src/components/Table/Internal/Body/BodyRow.tsx b/src/components/Table/Internal/Body/BodyRow.tsx index 5410dd8e0..ebc8b68f6 100644 --- a/src/components/Table/Internal/Body/BodyRow.tsx +++ b/src/components/Table/Internal/Body/BodyRow.tsx @@ -32,6 +32,7 @@ function BodyRow( indent = 0, rowComponent: RowComponent, cellComponent, + rowHeaderCellComponent, childrenColumnName, onRowHoverEnter, onRowHoverLeave, @@ -170,7 +171,11 @@ function BodyRow( ellipsis={column.ellipsis} align={column.align} verticalAlign={column.verticalAlign} - component={cellComponent} + component={ + colIndex === 0 && rowHeaderCellComponent + ? rowHeaderCellComponent + : cellComponent + } key={key} record={record} index={index} diff --git a/src/components/Table/Internal/Body/index.tsx b/src/components/Table/Internal/Body/index.tsx index bb5c451b1..491ac2c03 100644 --- a/src/components/Table/Internal/Body/index.tsx +++ b/src/components/Table/Internal/Body/index.tsx @@ -29,6 +29,7 @@ function Body({ onRow, rowExpandable, rowExpandDisabled, + rowHeaderCellComponent, emptyNode, childrenColumnName, onRowHoverEnter, @@ -88,6 +89,7 @@ function Body({ renderIndex={renderIndex} rowComponent={trComponent} cellComponent={tdComponent} + rowHeaderCellComponent={rowHeaderCellComponent} expandedKeys={expandedKeys} onRow={onRow} getRowKey={getRowKey} diff --git a/src/components/Table/Internal/OcTable.tsx b/src/components/Table/Internal/OcTable.tsx index 51e5e07f6..fb77c5804 100644 --- a/src/components/Table/Internal/OcTable.tsx +++ b/src/components/Table/Internal/OcTable.tsx @@ -118,6 +118,7 @@ function OcTable( onHeaderRow, transformColumns, rowHoverBackgroundEnabled = true, + rowHeaderCellComponent, sticky, headerClassName, onRowHoverEnter, @@ -537,6 +538,7 @@ function OcTable( expandedKeys={mergedExpandedKeys} rowExpandable={rowExpandable} rowExpandDisabled={rowExpandDisabled} + rowHeaderCellComponent={rowHeaderCellComponent} getRowKey={getRowKey} onRow={onRow} emptyNode={emptyNode} diff --git a/src/components/Table/Internal/OcTable.types.ts b/src/components/Table/Internal/OcTable.types.ts index 3abf40639..5be993a34 100644 --- a/src/components/Table/Internal/OcTable.types.ts +++ b/src/components/Table/Internal/OcTable.types.ts @@ -311,6 +311,11 @@ export type GetComponent = ( // =================== Expand =================== export type ExpandableType = false | 'row' | 'nest'; +export enum RowHeaderCellComponent { + td = 'td', + th = 'th', +} + export type ExpandedRowRender = ( record: ValueType, index: number, @@ -518,6 +523,11 @@ export interface OcTableProps { * Override default Table elements. */ components?: TableComponents; + /** + * The component used for the first cell in each body row. + * Defaults to 'td'. Pass 'th' to render row header cells. + */ + rowHeaderCellComponent?: RowHeaderCellComponent; /** * The Table canvas direction. * options: 'ltr', 'rtl' diff --git a/src/components/Table/Internal/Tests/ExpandRow.test.js b/src/components/Table/Internal/Tests/ExpandRow.test.js index 81445809b..78932bf54 100644 --- a/src/components/Table/Internal/Tests/ExpandRow.test.js +++ b/src/components/Table/Internal/Tests/ExpandRow.test.js @@ -333,7 +333,10 @@ describe('Table.Expand', () => { ); expect(wrapper.find('.table-row-expand-icon-cell').at(0)).toBeTruthy(); - expect(wrapper.find('tbody tr').first().find('td')).toHaveLength(3); + const firstBodyRow = wrapper.find('tbody tr').first(); + expect( + firstBodyRow.find('th').length + firstBodyRow.find('td').length + ).toBe(3); }); }); diff --git a/src/components/Table/Internal/Tests/FixedColumn.test.js b/src/components/Table/Internal/Tests/FixedColumn.test.js index 7c282854b..bf8ec8e2c 100644 --- a/src/components/Table/Internal/Tests/FixedColumn.test.js +++ b/src/components/Table/Internal/Tests/FixedColumn.test.js @@ -190,10 +190,12 @@ describe('Table.FixedColumn', () => { /> ); - expect(wrapper.find('tr th').find('.table-cell-content')).toHaveLength(1); - expect(wrapper.find('tr td').find('.table-cell-content')).toHaveLength( - data.length - ); + expect( + wrapper.find('thead tr th').find('.table-cell-content') + ).toHaveLength(1); + expect( + wrapper.find('tbody tr td').find('.table-cell-content') + ).toHaveLength(data.length); }); it('fixed column renders correctly RTL', () => { diff --git a/src/components/Table/Internal/Tests/Hover.test.tsx b/src/components/Table/Internal/Tests/Hover.test.tsx index 2eb1b99f6..7cd9d1113 100644 --- a/src/components/Table/Internal/Tests/Hover.test.tsx +++ b/src/components/Table/Internal/Tests/Hover.test.tsx @@ -78,14 +78,14 @@ describe('Table.Hover', () => { }) ); - // Merge row check - expect(wrapper.find('tbody td')).toHaveLength(3); + // Merge row check (3 td = 3) + expect(wrapper.find('tbody td').length).toBe(3); - // Hover 0-0 + // Hover 0-0 (td with rowSpan=2) wrapper.find('tbody td').at(0).simulate('mouseEnter'); expect(wrapper.find('.table-cell-row-hover')).toHaveLength(3); - // Hover 0-1 + // Hover 0-1 (second td = row0,col1) wrapper.find('tbody td').at(1).simulate('mouseEnter'); expect(wrapper.find('.table-cell-row-hover')).toHaveLength(2); diff --git a/src/components/Table/Internal/Tests/Table.test.js b/src/components/Table/Internal/Tests/Table.test.js index 14b75cd29..0cecc03d3 100644 --- a/src/components/Table/Internal/Tests/Table.test.js +++ b/src/components/Table/Internal/Tests/Table.test.js @@ -338,8 +338,8 @@ describe('Table.Basic', () => { ]; wrapper.find('tbody tr').forEach((tr, ri) => { - tr.find('td').forEach((td, di) => { - expect(td.text()).toEqual(targetData[ri][di]); + targetData[ri].forEach((expected, di) => { + expect(tr.childAt(di).text()).toEqual(expected); }); }); }); @@ -351,7 +351,7 @@ describe('Table.Basic', () => { { key: 'key1', name: 'Mia' }, ]; const wrapper = mount(createTable({ data: localData })); - expect(wrapper.find('table td').first().text()).toBe(''); + expect(wrapper.find('tbody td').first().text()).toBe(''); }); it('renders colSpan correctly', () => { @@ -423,7 +423,7 @@ describe('Table.Basic', () => { }} /> ); - const props = wrapper.find('td').props(); + const props = wrapper.find('tbody td').props(); expect(props.style).toEqual(expect.objectContaining({ background: 'red' })); expect(props.className.includes('customize-render')).toBeTruthy(); expect(props['data-light']).toEqual('bamboo'); @@ -695,7 +695,7 @@ describe('Table.Basic', () => { color: 'green', textAlign: 'center', }); - expect(wrapper.find('td').first().props().style).toEqual({ + expect(wrapper.find('tbody td').first().props().style).toEqual({ color: 'red', textAlign: 'center', }); @@ -740,7 +740,7 @@ describe('Table.Basic', () => { color: 'green', verticalAlign: 'top', }); - expect(wrapper.find('td').first().props().style).toEqual({ + expect(wrapper.find('tbody td').first().props().style).toEqual({ color: 'red', verticalAlign: 'top', }); @@ -966,10 +966,10 @@ describe('Table.Basic', () => { } const wrapper = mount(); - expect(wrapper.find('td').text()).toEqual('false'); + expect(wrapper.find('tbody td').text()).toEqual('false'); wrapper.setState({ change: true }); - expect(wrapper.find('td').text()).toEqual('true'); + expect(wrapper.find('tbody td').text()).toEqual('true'); }); it('not crash with raw data', () => { diff --git a/src/components/Table/Internal/octable.module.scss b/src/components/Table/Internal/octable.module.scss index 7c2fd414a..bbbe3cb94 100644 --- a/src/components/Table/Internal/octable.module.scss +++ b/src/components/Table/Internal/octable.module.scss @@ -11,11 +11,16 @@ border-radius: $table-border-radius; } - th { + thead th { line-height: $text-line-height-1; @include text-overflow(); } + tbody th { + font-weight: inherit; + text-align: inherit; + } + th, td { position: relative; @@ -234,11 +239,13 @@ tbody { tr.table-row { &:nth-child(odd) { + th, td { background-color: var(--table-background-alternate-color); } &:hover { + th, td { background-color: var(--table-row-hover-background-color); } diff --git a/src/components/Table/Styles/bordered.scss b/src/components/Table/Styles/bordered.scss index 46ab04aaf..945be0871 100644 --- a/src/components/Table/Styles/bordered.scss +++ b/src/components/Table/Styles/bordered.scss @@ -16,6 +16,7 @@ border-spacing: 0; thead tr th, + tbody tr th, tbody tr td, tfoot tr th, tfoot tr td { @@ -52,10 +53,12 @@ } tbody { + tr:not(:last-of-type) th, tr:not(:last-of-type) td { border-bottom: var(--table-border); } + tr:last-of-type th, tr:last-of-type td { border-bottom: none; } @@ -151,6 +154,7 @@ border-right: none; } + tbody tr th, tbody tr td, tfoot tr td { &:first-of-type { @@ -158,7 +162,10 @@ } border-right: var(--table-border); + } + tbody tr td, + tfoot tr td { &:last-of-type { border-right: none; } @@ -184,6 +191,7 @@ } tbody { + tr:not(:last-of-type) th, tr:not(:last-of-type) td { border-bottom: var(--table-border); } @@ -269,6 +277,7 @@ border-right: var(--table-border); } + tbody tr th, tbody tr td, tfoot tr td { border-left: none; @@ -296,6 +305,7 @@ } tbody { + tr th, tr td { border-bottom: none; } @@ -382,6 +392,7 @@ border-spacing: 0; thead tr th, + tbody tr th, tbody tr td, tfoot tr th, tfoot tr td { @@ -391,6 +402,7 @@ } thead tr th, + tbody tr th, tbody tr td, tfoot tr th, tfoot tr td { @@ -426,6 +438,7 @@ } tbody { + tr:not(:last-of-type) th, tr:not(:last-of-type) td { border-bottom: var(--table-border); } @@ -518,6 +531,7 @@ } tbody { + tr:not(:last-of-type) th, tr:not(:last-of-type) td { border-bottom: var(--table-border); } @@ -589,6 +603,7 @@ } tbody { + tr th:not(:last-of-type), tr td:not(:last-of-type) { border-right: var(--table-border); } diff --git a/src/components/Table/Styles/table.module.scss b/src/components/Table/Styles/table.module.scss index ac77a674e..02806d7c2 100644 --- a/src/components/Table/Styles/table.module.scss +++ b/src/components/Table/Styles/table.module.scss @@ -133,6 +133,7 @@ } &.table-row-selected { + th, td { background: var(--table-row-selected-background-color); border-color: rgba(0, 0, 0, 0.03); @@ -459,6 +460,7 @@ &.table-expanded-row { &, &:hover { + th, td { background: var(--table-row-expanded-background-color); } @@ -628,13 +630,16 @@ &-row-hover { .table-tbody { tr { + &.table-row:hover th, &.table-row:hover td, + th.table-cell-row-hover, td.table-cell-row-hover { background: var(--table-row-hover-background-color); } &.table-row-selected { &:hover { + th, td { background: var(--table-row-selected-hover-background-color); } diff --git a/src/components/Table/Tests/Table.order.test.js b/src/components/Table/Tests/Table.order.test.js index 170774c45..df8ec3504 100644 --- a/src/components/Table/Tests/Table.order.test.js +++ b/src/components/Table/Tests/Table.order.test.js @@ -70,7 +70,10 @@ describe('Table.order', () => { }) ); - expect(wrapper.find('tr').last().find('td')).toHaveLength(3); + expect( + wrapper.find('tr').last().find('th').length + + wrapper.find('tr').last().find('td').length + ).toBe(3); wrapper.unmount(); }); }); diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx index 4c1257519..4d4520e82 100644 --- a/src/components/Table/index.tsx +++ b/src/components/Table/index.tsx @@ -13,6 +13,7 @@ export { TableRowSelection, TableSize, } from './Table.types'; +export { RowHeaderCellComponent } from './Internal/OcTable.types'; export { TablePaginationConfig }; export default Table;