]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22049 Allow table to use grid template and column widths
authorIsmail Cherri <ismail.cherri@sonarsource.com>
Mon, 15 Apr 2024 15:57:32 +0000 (17:57 +0200)
committerMatteo Mara <matteo.mara@sonarsource.com>
Tue, 30 Apr 2024 08:59:04 +0000 (10:59 +0200)
server/sonar-web/design-system/src/components/Table.tsx [deleted file]
server/sonar-web/design-system/src/components/__tests__/Table-test.tsx [deleted file]
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/sonar-aligned/components/Table.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/sonar-aligned/components/__tests__/Table-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/sonar-aligned/components/index.ts

diff --git a/server/sonar-web/design-system/src/components/Table.tsx b/server/sonar-web/design-system/src/components/Table.tsx
deleted file mode 100644 (file)
index 44c09e3..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import styled from '@emotion/styled';
-import classNames from 'classnames';
-import { times } from 'lodash';
-import { ComponentProps, createContext, ReactNode, useContext } from 'react';
-import tw from 'twin.macro';
-import { themeBorder, themeColor } from '../helpers';
-import { FCProps } from '../types/misc';
-
-export interface TableProps extends ComponentProps<'table'> {
-  caption?: ReactNode;
-  columnCount: number;
-  columnWidths?: Array<number | string>;
-  header?: ReactNode;
-  noHeaderTopBorder?: boolean;
-  noSidePadding?: boolean;
-  withRoundedBorder?: boolean;
-}
-
-export function Table(props: TableProps) {
-  const {
-    className,
-    columnCount,
-    columnWidths = [],
-    header,
-    caption,
-    children,
-    noHeaderTopBorder,
-    noSidePadding,
-    withRoundedBorder,
-    ...rest
-  } = props;
-
-  return (
-    <StyledTable
-      className={classNames(
-        {
-          'no-header-top-border': noHeaderTopBorder,
-          'no-side-padding': noSidePadding,
-          'with-rounded-border': withRoundedBorder,
-        },
-        className,
-      )}
-      {...rest}
-    >
-      <colgroup>
-        {times(columnCount, (i) => (
-          <col key={i} width={columnWidths[i] ?? 'auto'} />
-        ))}
-      </colgroup>
-
-      {caption && (
-        <caption>
-          <div className="sw-py-4 sw-text-middle sw-flex sw-justify-center sw-body-sm-highlight">
-            {caption}
-          </div>
-        </caption>
-      )}
-
-      {header && (
-        <thead>
-          <CellTypeContext.Provider value="th">{header}</CellTypeContext.Provider>
-        </thead>
-      )}
-
-      <tbody>{children}</tbody>
-    </StyledTable>
-  );
-}
-
-export const TableSeparator = styled.tr`
-  ${tw`sw-h-4`}
-  border-top: ${themeBorder('default')};
-`;
-
-export const TableRow = styled.tr`
-  td,
-  th {
-    border-top: ${themeBorder('default')};
-  }
-
-  .no-header-top-border & th {
-    ${tw`sw-border-t-0`}
-  }
-
-  td:first-of-type,
-  th:first-of-type,
-  td:last-child,
-  th:last-child {
-    border-right: ${themeBorder('default', 'transparent')};
-    border-left: ${themeBorder('default', 'transparent')};
-  }
-
-  .no-side-padding & {
-    td:first-of-type,
-    th:first-of-type {
-      ${tw`sw-pl-0`}
-    }
-
-    td:last-child,
-    th:last-child {
-      ${tw`sw-pr-0`}
-    }
-  }
-
-  &:last-child > td {
-    border-bottom: ${themeBorder('default')};
-  }
-`;
-
-interface TableRowInteractiveProps extends FCProps<typeof TableRow> {
-  selected?: boolean;
-}
-
-function TableRowInteractiveBase({
-  className,
-  children,
-  selected,
-  ...props
-}: TableRowInteractiveProps) {
-  return (
-    <TableRow aria-selected={selected} className={classNames(className, { selected })} {...props}>
-      {children}
-    </TableRow>
-  );
-}
-
-export const TableRowInteractive = styled(TableRowInteractiveBase)`
-  &:hover > td,
-  &.selected > td,
-  &.selected > th,
-  th.selected,
-  td.selected {
-    background: ${themeColor('tableRowHover')};
-  }
-
-  &.selected > td:first-of-type,
-  &.selected > th:first-of-type,
-  th.selected:first-of-type,
-  td.selected:first-of-type {
-    border-left: ${themeBorder('default', 'tableRowSelected')};
-  }
-
-  &.selected > td,
-  &.selected > th,
-  th.selected,
-  td.selected {
-    border-top: ${themeBorder('default', 'tableRowSelected')};
-    border-bottom: ${themeBorder('default', 'tableRowSelected')};
-  }
-
-  &.selected > td:last-child,
-  &.selected > th:last-child,
-  th.selected:last-child,
-  td.selected:last-child {
-    border-right: ${themeBorder('default', 'tableRowSelected')};
-  }
-
-  &.selected + &:not(.selected) > td {
-    border-top: none;
-  }
-`;
-
-const CellTypeContext = createContext<'th' | 'td'>('td');
-type CellComponentProps = ComponentProps<'th' | 'td'>;
-
-export function CellComponent(props: CellComponentProps) {
-  const containerType = useContext(CellTypeContext);
-  return <CellComponentStyled as={containerType} {...props} />;
-}
-
-export function ContentCell({
-  children,
-  cellClassName,
-  className,
-  ...props
-}: CellComponentProps & { cellClassName?: string }) {
-  return (
-    <CellComponent className={cellClassName} {...props}>
-      <div
-        className={classNames('sw-text-left sw-justify-start sw-flex sw-items-center', className)}
-      >
-        {children}
-      </div>
-    </CellComponent>
-  );
-}
-
-export function NumericalCell({ children, ...props }: CellComponentProps) {
-  return (
-    <CellComponent {...props}>
-      <div className="sw-text-right sw-justify-end sw-flex sw-items-center">{children}</div>
-    </CellComponent>
-  );
-}
-
-export function RatingCell({ children, ...props }: CellComponentProps) {
-  return (
-    <CellComponent {...props}>
-      <div className="sw-text-right sw-justify-end sw-flex sw-items-center">{children}</div>
-    </CellComponent>
-  );
-}
-
-export function ActionCell({ children, ...props }: CellComponentProps) {
-  return (
-    <CellComponent {...props}>
-      <div className="sw-text-right sw-justify-end sw-flex sw-items-center">{children}</div>
-    </CellComponent>
-  );
-}
-
-export function CheckboxCell({ children, ...props }: CellComponentProps) {
-  return (
-    <CellComponent {...props}>
-      <div className="sw-text-center sw-justify-center sw-flex sw-items-center">{children}</div>
-    </CellComponent>
-  );
-}
-
-const StyledTable = styled.table`
-  width: 100%;
-  border-collapse: collapse;
-
-  &.with-rounded-border {
-    border-collapse: separate;
-    border: ${themeBorder('default', 'breakdownBorder')};
-    ${tw`sw-rounded-1`};
-
-    th:first-of-type {
-      ${tw`sw-rounded-tl-1`};
-    }
-    th:last-of-type {
-      ${tw`sw-rounded-tr-1`};
-    }
-
-    tr:last-child > td {
-      border-bottom: none;
-    }
-  }
-`;
-
-const CellComponentStyled = styled.td`
-  color: ${themeColor('pageContent')};
-  ${tw`sw-body-sm`}
-  ${tw`sw-py-4 sw-px-2`}
-  ${tw`sw-align-middle`}
-
-  thead > tr > & {
-    color: ${themeColor('pageTitle')};
-
-    ${tw`sw-body-sm-highlight`}
-  }
-`;
diff --git a/server/sonar-web/design-system/src/components/__tests__/Table-test.tsx b/server/sonar-web/design-system/src/components/__tests__/Table-test.tsx
deleted file mode 100644 (file)
index c8e3bf8..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import { screen } from '@testing-library/react';
-import { render } from '../../helpers/testUtils';
-import {
-  CheckboxCell,
-  ContentCell,
-  NumericalCell,
-  Table,
-  TableProps,
-  TableRow,
-  TableRowInteractive,
-} from '../Table';
-
-it('check that the html structure and style is correct for a regular table', () => {
-  renderTable({
-    columnCount: 3,
-    'aria-colcount': 3,
-    header: (
-      <TableRow>
-        <ContentCell>ContentCellHeader</ContentCell>
-        <NumericalCell>NumericalCellHeader</NumericalCell>
-        <CheckboxCell>CheckboxCellHeader</CheckboxCell>
-      </TableRow>
-    ),
-    children: (
-      <>
-        <TableRowInteractive>
-          <ContentCell>ContentCell 1</ContentCell>
-          <NumericalCell>NumericalCell 1</NumericalCell>
-          <CheckboxCell>CheckboxCell 1</CheckboxCell>
-        </TableRowInteractive>
-        <TableRowInteractive selected>
-          <ContentCell>ContentCell 2</ContentCell>
-          <NumericalCell>NumericalCell 2</NumericalCell>
-          <CheckboxCell>CheckboxCell 2</CheckboxCell>
-        </TableRowInteractive>
-        <TableRow>
-          <ContentCell aria-colspan={3}>ContentCell 3</ContentCell>
-        </TableRow>
-        <TableRowInteractive>
-          <NumericalCell aria-colindex={2}>NumericalCell 4</NumericalCell>
-          <CheckboxCell aria-colindex={3}>CheckboxCell 4</CheckboxCell>
-        </TableRowInteractive>
-      </>
-    ),
-  });
-
-  // Table should have accessible attribute
-  expect(screen.getByRole('table')).toHaveAttribute('aria-colcount', '3');
-
-  // Rows should have accessible attributes
-  expect(
-    screen.getByRole('row', { name: 'ContentCellHeader NumericalCellHeader CheckboxCellHeader' }),
-  ).toBeInTheDocument();
-  expect(
-    screen.getByRole('row', {
-      name: 'ContentCell 1 NumericalCell 1 CheckboxCell 1',
-    }),
-  ).toBeInTheDocument();
-  expect(
-    screen.getByRole('row', {
-      name: 'ContentCell 1 NumericalCell 1 CheckboxCell 1',
-    }),
-  ).not.toHaveAttribute('aria-selected');
-  expect(
-    screen.getByRole('row', {
-      selected: true,
-      name: 'ContentCell 2 NumericalCell 2 CheckboxCell 2',
-    }),
-  ).toBeInTheDocument();
-  expect(
-    screen.getByRole('row', {
-      name: 'NumericalCell 4 CheckboxCell 4',
-    }),
-  ).toBeInTheDocument();
-
-  // Cells should have accessible attributes
-  expect(screen.getByRole('cell', { name: 'NumericalCell 4' })).toHaveAttribute(
-    'aria-colindex',
-    '2',
-  );
-  expect(screen.getByRole('cell', { name: 'CheckboxCell 4' })).toHaveAttribute(
-    'aria-colindex',
-    '3',
-  );
-});
-
-function renderTable(props: TableProps) {
-  return render(<Table {...props}>{props.children}</Table>);
-}
index ae2932330ec1792e30708b5c8f3b4b507a65e5c1..35e06bdc5976a035c496ac1187ecd27db692c6b6 100644 (file)
@@ -71,7 +71,6 @@ export * from './SonarQubeLogo';
 export { Spinner } from './Spinner';
 export * from './SpotlightTour';
 export * from './Switch';
-export * from './Table';
 export * from './Tabs';
 export * from './Tags';
 export * from './Text';
diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/Table.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/Table.tsx
new file mode 100644 (file)
index 0000000..6de60ff
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import classNames from 'classnames';
+import { isNumber, times } from 'lodash';
+import { ComponentProps, ReactNode, createContext, useContext } from 'react';
+import tw from 'twin.macro';
+import { FCProps } from '~types/misc';
+import { themeBorder, themeColor } from '../../helpers/theme';
+
+interface TableBaseProps extends ComponentProps<'table'> {
+  caption?: ReactNode;
+  header?: ReactNode;
+  noHeaderTopBorder?: boolean;
+  noSidePadding?: boolean;
+  withRoundedBorder?: boolean;
+}
+
+interface ColumnWidthsProps extends TableBaseProps {
+  columnCount: number;
+  columnWidths?: Array<number | string>;
+  gridTemplate?: never;
+}
+
+interface GridTemplateProps extends TableBaseProps {
+  columnCount?: never;
+  columnWidths?: never;
+  gridTemplate: string;
+}
+
+export type TableProps = ColumnWidthsProps | GridTemplateProps;
+
+export function Table(props: Readonly<TableProps>) {
+  const { columnCount, gridTemplate } = props;
+  const {
+    className,
+    columnWidths,
+    header,
+    caption,
+    children,
+    noHeaderTopBorder,
+    noSidePadding,
+    withRoundedBorder,
+    ...rest
+  } = props;
+
+  return (
+    <StyledTable
+      className={classNames(
+        {
+          'no-header-top-border': noHeaderTopBorder,
+          'no-side-padding': noSidePadding,
+          'with-rounded-border': withRoundedBorder,
+          'with-grid-template': gridTemplate !== undefined,
+        },
+        className,
+      )}
+      {...rest}
+    >
+      {isNumber(columnCount) && (
+        <colgroup>
+          {times(columnCount, (i) => (
+            <col key={i} width={columnWidths?.[i] ?? 'auto'} />
+          ))}
+        </colgroup>
+      )}
+
+      {caption && (
+        <caption>
+          <div className="sw-py-4 sw-text-middle sw-flex sw-justify-center sw-body-sm-highlight">
+            {caption}
+          </div>
+        </caption>
+      )}
+
+      {header && (
+        <thead>
+          <CellTypeContext.Provider value="th">{header}</CellTypeContext.Provider>
+        </thead>
+      )}
+
+      <tbody>{children}</tbody>
+    </StyledTable>
+  );
+}
+
+export const TableSeparator = styled.tr`
+  ${tw`sw-h-4`}
+  border-top: ${themeBorder('default')};
+`;
+
+export const TableRow = styled.tr`
+  td,
+  th {
+    border-top: ${themeBorder('default')};
+  }
+
+  .no-header-top-border & th {
+    ${tw`sw-border-t-0`}
+  }
+
+  td:first-of-type,
+  th:first-of-type,
+  td:last-child,
+  th:last-child {
+    border-right: ${themeBorder('default', 'transparent')};
+    border-left: ${themeBorder('default', 'transparent')};
+  }
+
+  .no-side-padding & {
+    td:first-of-type,
+    th:first-of-type {
+      ${tw`sw-pl-0`}
+    }
+
+    td:last-child,
+    th:last-child {
+      ${tw`sw-pr-0`}
+    }
+  }
+
+  &:last-child > td {
+    border-bottom: ${themeBorder('default')};
+  }
+`;
+
+interface TableRowInteractiveProps extends FCProps<typeof TableRow> {
+  selected?: boolean;
+}
+
+function TableRowInteractiveBase({
+  className,
+  children,
+  selected,
+  ...props
+}: Readonly<TableRowInteractiveProps>) {
+  return (
+    <TableRow aria-selected={selected} className={classNames(className, { selected })} {...props}>
+      {children}
+    </TableRow>
+  );
+}
+
+export const TableRowInteractive = styled(TableRowInteractiveBase)`
+  &:hover > td,
+  &.selected > td,
+  &.selected > th,
+  th.selected,
+  td.selected {
+    background: ${themeColor('tableRowHover')};
+  }
+
+  &.selected > td:first-of-type,
+  &.selected > th:first-of-type,
+  th.selected:first-of-type,
+  td.selected:first-of-type {
+    border-left: ${themeBorder('default', 'tableRowSelected')};
+  }
+
+  &.selected > td,
+  &.selected > th,
+  th.selected,
+  td.selected {
+    border-top: ${themeBorder('default', 'tableRowSelected')};
+    border-bottom: ${themeBorder('default', 'tableRowSelected')};
+  }
+
+  &.selected > td:last-child,
+  &.selected > th:last-child,
+  th.selected:last-child,
+  td.selected:last-child {
+    border-right: ${themeBorder('default', 'tableRowSelected')};
+  }
+
+  &.selected + &:not(.selected) > td {
+    border-top: none;
+  }
+`;
+
+const CellTypeContext = createContext<'th' | 'td'>('td');
+type CellComponentProps = ComponentProps<'th' | 'td'>;
+
+export function CellComponent(props: CellComponentProps) {
+  const containerType = useContext(CellTypeContext);
+  return <CellComponentStyled as={containerType} {...props} />;
+}
+
+export function ContentCell({
+  children,
+  cellClassName,
+  className,
+  ...props
+}: CellComponentProps & { cellClassName?: string }) {
+  return (
+    <CellComponent className={cellClassName} {...props}>
+      <div
+        className={classNames('sw-text-left sw-justify-start sw-flex sw-items-center', className)}
+      >
+        {children}
+      </div>
+    </CellComponent>
+  );
+}
+
+export function NumericalCell({ children, ...props }: CellComponentProps) {
+  return (
+    <CellComponent {...props}>
+      <div className="sw-text-right sw-justify-end sw-flex sw-items-center">{children}</div>
+    </CellComponent>
+  );
+}
+
+export function RatingCell({ children, ...props }: CellComponentProps) {
+  return (
+    <CellComponent {...props}>
+      <div className="sw-text-right sw-justify-end sw-flex sw-items-center">{children}</div>
+    </CellComponent>
+  );
+}
+
+export function ActionCell({ children, ...props }: CellComponentProps) {
+  return (
+    <CellComponent {...props}>
+      <div className="sw-text-right sw-justify-end sw-flex sw-items-center">{children}</div>
+    </CellComponent>
+  );
+}
+
+export function CheckboxCell({ children, ...props }: CellComponentProps) {
+  return (
+    <CellComponent {...props}>
+      <div className="sw-text-center sw-justify-center sw-flex sw-items-center">{children}</div>
+    </CellComponent>
+  );
+}
+
+const StyledTable = styled.table<{ gridTemplate?: string }>`
+  width: 100%;
+  border-collapse: collapse;
+
+  &.with-grid-template {
+    display: grid;
+    grid-template-columns: ${(props) => props.gridTemplate};
+    thead,
+    tbody,
+    tr {
+      display: contents;
+    }
+  }
+
+  &.with-rounded-border {
+    border-collapse: separate;
+    border: ${themeBorder('default', 'breakdownBorder')};
+    ${tw`sw-rounded-1`};
+
+    th:first-of-type {
+      ${tw`sw-rounded-tl-1`};
+    }
+    th:last-of-type {
+      ${tw`sw-rounded-tr-1`};
+    }
+
+    tr:last-child > td {
+      border-bottom: none;
+    }
+  }
+`;
+
+const CellComponentStyled = styled.td`
+  color: ${themeColor('pageContent')};
+  ${tw`sw-items-center`}
+  ${tw`sw-body-sm`}
+  ${tw`sw-py-4 sw-px-2`}
+  ${tw`sw-align-middle`}
+
+  thead > tr > & {
+    color: ${themeColor('pageTitle')};
+
+    ${tw`sw-body-sm-highlight`}
+  }
+`;
diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/__tests__/Table-test.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/__tests__/Table-test.tsx
new file mode 100644 (file)
index 0000000..affcb19
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { screen } from '@testing-library/react';
+import { render } from '../../../helpers/testUtils';
+import {
+  ActionCell,
+  CheckboxCell,
+  ContentCell,
+  NumericalCell,
+  RatingCell,
+  Table,
+  TableProps,
+  TableRow,
+  TableRowInteractive,
+} from '../Table';
+
+it.each([
+  [
+    'using column count and widths',
+    {
+      columnCount: 5,
+      columnWidths: ['1%', 'auto', '1%', '1%', '1%'],
+      'aria-colcount': 5,
+    },
+  ],
+  [
+    'using column count only',
+    {
+      columnCount: 5,
+      'aria-colcount': 5,
+    },
+  ],
+  [
+    'using column grid template only',
+    {
+      gridTemplate: '1fr auto 1fr 1fr 1fr',
+      'aria-colcount': 5,
+    },
+  ],
+])('check that the html structure and style is correct %s', (_, props) => {
+  renderTable({
+    ...props,
+    header: (
+      <TableRow>
+        <ContentCell>ContentCellHeader</ContentCell>
+        <NumericalCell>NumericalCellHeader</NumericalCell>
+        <CheckboxCell>CheckboxCellHeader</CheckboxCell>
+        <RatingCell>RatingCellHeader</RatingCell>
+        <ActionCell>ActionCellHeader</ActionCell>
+      </TableRow>
+    ),
+    children: (
+      <>
+        <TableRowInteractive>
+          <ContentCell>ContentCell 1</ContentCell>
+          <NumericalCell>NumericalCell 1</NumericalCell>
+          <CheckboxCell>CheckboxCell 1</CheckboxCell>
+          <RatingCell>RatingCell 1</RatingCell>
+          <ActionCell>ActionCell 1</ActionCell>
+        </TableRowInteractive>
+        <TableRowInteractive selected>
+          <ContentCell>ContentCell 2</ContentCell>
+          <NumericalCell>NumericalCell 2</NumericalCell>
+          <CheckboxCell>CheckboxCell 2</CheckboxCell>
+          <RatingCell>RatingCell 2</RatingCell>
+          <ActionCell>ActionCell 2</ActionCell>
+        </TableRowInteractive>
+        <TableRow>
+          <ContentCell aria-colspan={5}>ContentCell 3</ContentCell>
+        </TableRow>
+        <TableRowInteractive>
+          <NumericalCell aria-colindex={2}>NumericalCell 4</NumericalCell>
+          <CheckboxCell aria-colindex={3}>CheckboxCell 4</CheckboxCell>
+          <RatingCell aria-colindex={4}>RatingCell 4</RatingCell>
+          <ActionCell aria-colindex={5}>ActionCell 4</ActionCell>
+        </TableRowInteractive>
+      </>
+    ),
+  });
+
+  // Table should have accessible attribute
+  expect(screen.getByRole('table')).toHaveAttribute('aria-colcount', '5');
+
+  // Rows should have accessible attributes
+  expect(
+    screen.getByRole('row', {
+      name: 'ContentCellHeader NumericalCellHeader CheckboxCellHeader RatingCellHeader ActionCellHeader',
+    }),
+  ).toBeInTheDocument();
+  expect(
+    screen.getByRole('row', {
+      name: 'ContentCell 1 NumericalCell 1 CheckboxCell 1 RatingCell 1 ActionCell 1',
+    }),
+  ).toBeInTheDocument();
+  expect(
+    screen.getByRole('row', {
+      name: 'ContentCell 1 NumericalCell 1 CheckboxCell 1 RatingCell 1 ActionCell 1',
+    }),
+  ).not.toHaveAttribute('aria-selected');
+  expect(
+    screen.getByRole('row', {
+      selected: true,
+      name: 'ContentCell 2 NumericalCell 2 CheckboxCell 2 RatingCell 2 ActionCell 2',
+    }),
+  ).toBeInTheDocument();
+  expect(
+    screen.getByRole('row', {
+      name: 'NumericalCell 4 CheckboxCell 4 RatingCell 4 ActionCell 4',
+    }),
+  ).toBeInTheDocument();
+
+  // Cells should have accessible attributes
+  expect(screen.getByRole('cell', { name: 'NumericalCell 4' })).toHaveAttribute(
+    'aria-colindex',
+    '2',
+  );
+  expect(screen.getByRole('cell', { name: 'CheckboxCell 4' })).toHaveAttribute(
+    'aria-colindex',
+    '3',
+  );
+  expect(screen.getByRole('cell', { name: 'RatingCell 4' })).toHaveAttribute('aria-colindex', '4');
+  expect(screen.getByRole('cell', { name: 'ActionCell 4' })).toHaveAttribute('aria-colindex', '5');
+});
+
+function renderTable(props: TableProps) {
+  return render(<Table {...props}>{props.children}</Table>);
+}
index d9765390baafaa1445d3d1786044e27f8a88c471..459ba9d1655fd51a94f23f55e3f924674fb2d0b5 100644 (file)
@@ -20,5 +20,6 @@
 
 export * from './Card';
 export * from './MetricsRatingBadge';
+export * from './Table';
 export * from './buttons';
 export * from './typography';