diff --git a/packages/react-component-library/src/a11y/storyAccessibilityConfig.ts b/packages/react-component-library/src/a11y/storyAccessibilityConfig.ts index e8f5d5b148..3102945d09 100644 --- a/packages/react-component-library/src/a11y/storyAccessibilityConfig.ts +++ b/packages/react-component-library/src/a11y/storyAccessibilityConfig.ts @@ -15,6 +15,12 @@ export const storyAccessibilityConfig = { enabled: false, }, ], + 'Data Grid': [ + { + id: 'empty-table-header', + enabled: false, + }, + ], 'Date Picker': [ { id: 'aria-required-attr', diff --git a/packages/react-component-library/src/components/DataGrid/DataGrid.stories.tsx b/packages/react-component-library/src/components/DataGrid/DataGrid.stories.tsx index f44a7cc6bc..e133be3bb8 100644 --- a/packages/react-component-library/src/components/DataGrid/DataGrid.stories.tsx +++ b/packages/react-component-library/src/components/DataGrid/DataGrid.stories.tsx @@ -5,6 +5,7 @@ import { fn } from '@storybook/test' import { StoryFn, Meta } from '@storybook/react' import { ColumnDef } from '@tanstack/react-table' +import { storyAccessibilityConfig } from '../../a11y/storyAccessibilityConfig' import { DataGrid, TABLE_COLUMN_ALIGNMENT } from '.' import { Badge } from '../Badge' @@ -15,6 +16,14 @@ type Order = { price: string } +const disableEmptyTableHeaderRule = { + a11y: { + config: { + rules: storyAccessibilityConfig['Data Grid'], + }, + }, +} + // const generateRandomData = (length: number): Order[] => { // const data: Order[] = Array.from({ length }, (_, i) => { // return { @@ -321,10 +330,59 @@ ColumnAlignment.args = { onSelectedRowsChange: fn(), } -const mergedColumns = columns.map((item, index) => ({ - ...item, - ...columnsWithArbitraryContent[index], - enableSorting: true, +const groupedColumns = [ + { + header: 'Group 1', + columns: [ + { + header: 'Name', + accessorKey: 'productName', + enableSorting: false, + }, + { + header: 'Price', + accessorKey: 'price', + enableSorting: false, + }, + ], + }, + { + header: 'Group 2', + columns: [ + { + header: 'Quantity', + accessorKey: 'quantity', + enableSorting: false, + }, + ], + }, +] + +export const ColumnGrouping: StoryFn = (props) => { + return ( + + + + ) +} + +ColumnGrouping.storyName = 'Column grouping' +ColumnGrouping.args = { + data, + isFullWidth: true, + onSelectedRowsChange: fn(), +} + +const mergedColumns = groupedColumns.map((group) => ({ + ...group, + columns: group.columns.map((column) => ({ + ...column, + ...columnsWithArbitraryContent.find( + // @ts-ignore + (c) => c.accessorKey === column.accessorKey + ), + enableSorting: true, + })), })) export const KitchenSink: StoryFn = (props) => { @@ -342,6 +400,7 @@ export const KitchenSink: StoryFn = (props) => { } KitchenSink.storyName = 'Kitchen sink' +KitchenSink.parameters = disableEmptyTableHeaderRule KitchenSink.args = { columns: mergedColumns, data, diff --git a/packages/react-component-library/src/components/DataGrid/DataGrid.test.tsx b/packages/react-component-library/src/components/DataGrid/DataGrid.test.tsx index aa33391e3e..225d1cf181 100644 --- a/packages/react-component-library/src/components/DataGrid/DataGrid.test.tsx +++ b/packages/react-component-library/src/components/DataGrid/DataGrid.test.tsx @@ -488,4 +488,65 @@ describe('DataGrid', () => { }) }) }) + + describe('with column grouping', () => { + const columnGroups: ColumnDef[] = [ + { + header: 'Group 1', + columns: [ + { + accessorKey: 'first', + header: 'First', + cell: (info) => info.getValue(), + enableSorting: true, + }, + { + accessorKey: 'second', + header: 'Second', + cell: (info) => info.getValue(), + enableSorting: false, + }, + ], + }, + { + header: 'Group 2', + columns: [ + { + accessorKey: 'third', + header: 'Third', + cell: (info) => info.getValue(), + enableSorting: true, + }, + ], + }, + ] + + beforeEach(() => { + render() + }) + + it('should render the column groups correctly', () => { + expect(screen.getByText('Group 1')).toBeInTheDocument() + expect(screen.getByText('Group 2')).toBeInTheDocument() + + expect(screen.getByText('First')).toBeInTheDocument() + expect(screen.getByText('Second')).toBeInTheDocument() + expect(screen.getByText('Third')).toBeInTheDocument() + }) + + it('should render the column groups with the correct order', () => { + const firstRow = screen.getAllByRole('row')[0] + const firstRowHeaders = within(firstRow).getAllByRole('columnheader') + + expect(firstRowHeaders[0]).toHaveTextContent('Group 1') + expect(firstRowHeaders[1]).toHaveTextContent('Group 2') + + const secondRow = screen.getAllByRole('row')[1] + const secondRowHeaders = within(secondRow).getAllByRole('columnheader') + + expect(secondRowHeaders[0]).toHaveTextContent('First') + expect(secondRowHeaders[1]).toHaveTextContent('Second') + expect(secondRowHeaders[2]).toHaveTextContent('Third') + }) + }) }) diff --git a/packages/react-component-library/src/components/DataGrid/DataGrid.tsx b/packages/react-component-library/src/components/DataGrid/DataGrid.tsx index a53c235779..fd15bd4c7e 100644 --- a/packages/react-component-library/src/components/DataGrid/DataGrid.tsx +++ b/packages/react-component-library/src/components/DataGrid/DataGrid.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line @typescript-eslint/triple-slash-reference /// -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useMemo } from 'react' import _noop from 'lodash/noop' import { flexRender, @@ -214,6 +214,12 @@ export const DataGrid = (props: DataGridProps) => { ) }, [rowSelection, table, onSelectedRowsChange]) + const hasGroupedHeaders = useMemo(() => { + return table.getHeaderGroups().reduce((acc, group) => { + return acc || group.headers.some((header) => header.depth > 1) + }, false) + }, [table]) + return ( (props: DataGridProps) => { {table .getHeaderGroups() - .map(({ id: headerGroupId, headers }: HeaderGroup) => ( + .map(({ id: headerGroupId, headers, depth }: HeaderGroup) => ( {headers.map( ({ @@ -239,14 +245,17 @@ export const DataGrid = (props: DataGridProps) => { id: headerId, isPlaceholder, getContext, + colSpan, }: Header) => (
{isPlaceholder diff --git a/packages/react-component-library/src/components/DataGrid/partials/StyledCol.tsx b/packages/react-component-library/src/components/DataGrid/partials/StyledCol.tsx index cb05fa3d86..55a1a9b813 100644 --- a/packages/react-component-library/src/components/DataGrid/partials/StyledCol.tsx +++ b/packages/react-component-library/src/components/DataGrid/partials/StyledCol.tsx @@ -7,18 +7,19 @@ interface StyledColProps { $isSortable?: boolean $alignment?: 'left' | 'right' | 'center' $width?: number + $isHeaderGroup?: boolean } export const StyledCol = styled.th` position: relative; padding: ${spacing('9')} ${spacing('4')} ${spacing('9')} ${spacing('8')}; width: ${({ $width }) => $width || 'auto'}; + border-bottom: 1px solid ${color('neutral', '200')}; + color: ${color('neutral', '600')}; + font-weight: 700; text-align: left; + text-transform: 'capitalize'; font-size: ${fontSize('s')}; - color: ${color('neutral', '600')}; - font-weight: 600; - text-transform: capitalize; - border-bottom: 1px solid ${color('neutral', '200')}; > div { display: flex; @@ -43,6 +44,18 @@ export const StyledCol = styled.th` background: ${color('neutral', '200')}; } + ${({ $isHeaderGroup }) => + $isHeaderGroup && + css` + text-transform: uppercase; + font-size: ${fontSize('m')}; + + &:not(:last-of-type) > div::after { + top: 100%; + height: 80px; + } + `} + ${({ $isSortable }) => $isSortable && css`