Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/tables/src/elements/Cell.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Table } from './Table';
import { Body } from './Body';
import { Row } from './Row';
import { Cell } from './Cell';
import { Head } from './Head';
import { HeaderRow } from './HeaderRow';

describe('Cell', () => {
it('passes ref to underlying DOM element', () => {
Expand Down Expand Up @@ -113,4 +115,18 @@ describe('Cell', () => {

expect(getByTestId('cell').firstChild).toHaveStyle(hideVisually());
});

it('renders dangerouslySetInnerHTML content', () => {
const { getByTestId } = render(
<Table>
<Head>
<HeaderRow>
<Cell data-test-id="cell" dangerouslySetInnerHTML={{ __html: '<b>Name</b>' }} />
</HeaderRow>
</Head>
</Table>
);

expect(getByTestId('cell').innerHTML).toBe('<b>Name</b>');
});
});
32 changes: 18 additions & 14 deletions packages/tables/src/elements/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { StyledCell, StyledHiddenCell } from '../styled';
import { useTableContext } from '../utils/useTableContext';
import { HeaderRowContext } from '../utils/useHeaderRowContext';
import { ICellProps } from '../types';

/**
Expand All @@ -21,20 +22,23 @@ export const Cell = React.forwardRef<HTMLTableCellElement, ICellProps>(
const { size } = useTableContext();

return (
<StyledCell
ref={ref}
$size={size}
$isMinimum={isMinimum}
$isTruncated={isTruncated}
$hasOverflow={hasOverflow}
{...props}
>
{hidden && props.children ? (
<StyledHiddenCell>{props.children}</StyledHiddenCell>
) : (
props.children
)}
</StyledCell>
// the cell's content (for example, a nested table) is not in a header row
<HeaderRowContext.Provider value={false}>
<StyledCell
ref={ref}
$size={size}
$isMinimum={isMinimum}
$isTruncated={isTruncated}
$hasOverflow={hasOverflow}
{...props}
>
{hidden && props.children ? (
<StyledHiddenCell>{props.children}</StyledHiddenCell>
) : (
props.children
)}
</StyledCell>
</HeaderRowContext.Provider>
);
}
);
Expand Down
135 changes: 135 additions & 0 deletions packages/tables/src/elements/HeaderCell.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { Table } from './Table';
import { Head } from './Head';
import { HeaderRow } from './HeaderRow';
import { HeaderCell } from './HeaderCell';
import { Body } from './Body';
import { Row } from './Row';
import { Cell } from './Cell';

describe('HeaderCell', () => {
it('passes ref to underlying DOM element', () => {
Expand Down Expand Up @@ -87,4 +90,136 @@ describe('HeaderCell', () => {

expect(getByTestId('headerCell').firstChild).toHaveStyle(hideVisually());
});

it('renders dangerouslySetInnerHTML content', () => {
const { getByTestId } = render(
<Table>
<Head>
<HeaderRow>
<HeaderCell
data-test-id="headerCell"
dangerouslySetInnerHTML={{ __html: '<b>Name</b>' }}
/>
</HeaderRow>
</Head>
</Table>
);

expect(getByTestId('headerCell').innerHTML).toBe('<b>Name</b>');
});

describe('scope', () => {
it('defaults to column scope in a header row', () => {
const { getByTestId } = render(
<Table>
<Head>
<HeaderRow>
<HeaderCell data-test-id="headerCell">Name</HeaderCell>
</HeaderRow>
</Head>
</Table>
);

expect(getByTestId('headerCell')).toHaveAttribute('scope', 'col');
});

it('keeps an explicit scope', () => {
const { getByTestId } = render(
<Table>
<Head>
<HeaderRow>
<HeaderCell data-test-id="headerCell" scope="colgroup" colSpan={2}>
Group
</HeaderCell>
</HeaderRow>
</Head>
</Table>
);

expect(getByTestId('headerCell')).toHaveAttribute('scope', 'colgroup');
});

it('applies the default when the given scope is undefined', () => {
const { getByTestId } = render(
<Table>
<Head>
<HeaderRow>
<HeaderCell data-test-id="headerCell" scope={undefined}>
Name
</HeaderCell>
</HeaderRow>
</Head>
</Table>
);

expect(getByTestId('headerCell')).toHaveAttribute('scope', 'col');
});

it('sets no scope in a body row, where the browser infers it', () => {
const { getByTestId } = render(
<Table>
<Body>
<Row>
<HeaderCell data-test-id="columnHeader">Name</HeaderCell>
<HeaderCell>Price</HeaderCell>
</Row>
<Row>
<HeaderCell data-test-id="rowHeader">Apple</HeaderCell>
<Cell>1</Cell>
</Row>
<tr>
<HeaderCell data-test-id="plainRowHeader">Pear</HeaderCell>
<Cell>2</Cell>
</tr>
</Body>
</Table>
);

expect(getByTestId('columnHeader')).not.toHaveAttribute('scope');
expect(getByTestId('rowHeader')).not.toHaveAttribute('scope');
expect(getByTestId('plainRowHeader')).not.toHaveAttribute('scope');
});

it('does not apply the header row default inside cell content', () => {
const { getByTestId } = render(
<Table>
<Head>
<HeaderRow>
<HeaderCell>
<table>
<tbody>
<tr>
<HeaderCell data-test-id="inHeaderCell">Name</HeaderCell>
</tr>
</tbody>
</table>
</HeaderCell>
<Cell>
<table>
<tbody>
<tr>
<HeaderCell data-test-id="inCell">Name</HeaderCell>
</tr>
</tbody>
</table>
</Cell>
<td>
<Table>
<Body>
<tr>
<HeaderCell data-test-id="inNestedTable">Name</HeaderCell>
</tr>
</Body>
</Table>
</td>
</HeaderRow>
</Head>
</Table>
);

expect(getByTestId('inHeaderCell')).not.toHaveAttribute('scope');
expect(getByTestId('inCell')).not.toHaveAttribute('scope');
expect(getByTestId('inNestedTable')).not.toHaveAttribute('scope');
});
});
});
36 changes: 21 additions & 15 deletions packages/tables/src/elements/HeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, { forwardRef } from 'react';
import { IHeaderCellProps } from '../types';
import { StyledHeaderCell, StyledHiddenCell } from '../styled';
import { useTableContext } from '../utils/useTableContext';
import { HeaderRowContext, useHeaderRowContext } from '../utils/useHeaderRowContext';
import { Cell } from './Cell';

/**
Expand All @@ -17,24 +18,29 @@ import { Cell } from './Cell';
* @extends ThHTMLAttributes<HTMLTableCellElement>
*/
export const HeaderCell = forwardRef<HTMLTableCellElement, IHeaderCellProps>(
({ hidden, isMinimum, isTruncated, hasOverflow, ...props }, ref) => {
({ hidden, isMinimum, isTruncated, hasOverflow, scope, ...props }, ref) => {
const { size } = useTableContext();
const isInHeaderRow = useHeaderRowContext();

return (
<StyledHeaderCell
ref={ref}
$size={size}
$isMinimum={isMinimum}
$isTruncated={isTruncated}
$hasOverflow={hasOverflow}
{...props}
>
{hidden && props.children ? (
<StyledHiddenCell>{props.children}</StyledHiddenCell>
) : (
props.children
)}
</StyledHeaderCell>
// the cell's content (for example, a nested table) is not in a header row
<HeaderRowContext.Provider value={false}>
<StyledHeaderCell
ref={ref}
scope={scope ?? (isInHeaderRow ? 'col' : undefined)}
$size={size}
$isMinimum={isMinimum}
$isTruncated={isTruncated}
$hasOverflow={hasOverflow}
{...props}
>
{hidden && props.children ? (
<StyledHiddenCell>{props.children}</StyledHiddenCell>
) : (
props.children
)}
</StyledHeaderCell>
</HeaderRowContext.Provider>
);
}
);
Expand Down
7 changes: 6 additions & 1 deletion packages/tables/src/elements/HeaderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import React, { HTMLAttributes } from 'react';
import { StyledHeaderRow } from '../styled';
import { useTableContext } from '../utils/useTableContext';
import { HeaderRowContext } from '../utils/useHeaderRowContext';

/**
* @deprecated use `Table.HeaderRow` instead
Expand All @@ -18,7 +19,11 @@ export const HeaderRow = React.forwardRef<HTMLTableRowElement, HTMLAttributes<HT
(props, ref) => {
const { size } = useTableContext();

return <StyledHeaderRow ref={ref} $size={size} {...props} />;
return (
<HeaderRowContext.Provider value>
<StyledHeaderRow ref={ref} $size={size} {...props} />
</HeaderRowContext.Provider>
);
}
);

Expand Down
48 changes: 48 additions & 0 deletions packages/tables/src/elements/SortableCell.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Table } from './Table';
import { Head } from './Head';
import { HeaderRow } from './HeaderRow';
import { SortableCell } from './SortableCell';
import { Body } from './Body';
import { Row } from './Row';
import { StyledSortableButton } from '../styled';

describe('SortableCell', () => {
Expand Down Expand Up @@ -76,4 +78,50 @@ describe('SortableCell', () => {
` as any
});
});

describe('scope', () => {
it('defaults to column scope in a header row', () => {
const { getByTestId } = render(
<Table>
<Head>
<HeaderRow>
<SortableCell cellProps={{ 'data-test-id': 'cell' }}>Name</SortableCell>
</HeaderRow>
</Head>
</Table>
);

expect(getByTestId('cell')).toHaveAttribute('scope', 'col');
});

it('sets no scope in a body row', () => {
const { getByTestId } = render(
<Table>
<Body>
<Row>
<SortableCell cellProps={{ 'data-test-id': 'cell' }}>Name</SortableCell>
</Row>
</Body>
</Table>
);

expect(getByTestId('cell')).not.toHaveAttribute('scope');
});

it('keeps an explicit scope from cell props', () => {
const { getByTestId } = render(
<Table>
<Head>
<HeaderRow>
<SortableCell cellProps={{ 'data-test-id': 'cell', scope: 'colgroup' }}>
Name
</SortableCell>
</HeaderRow>
</Head>
</Table>
);

expect(getByTestId('cell')).toHaveAttribute('scope', 'colgroup');
});
});
});
5 changes: 4 additions & 1 deletion packages/tables/src/elements/SortableCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
StyledSortableStrokeIconWrapper,
StyledSortableFillIconWrapper
} from '../styled';
import { useHeaderRowContext } from '../utils/useHeaderRowContext';

/**
* @deprecated use `Table.SortableCell` instead
Expand All @@ -24,7 +25,8 @@ import {
*/
export const SortableCell = forwardRef<HTMLButtonElement, ISortableCellProps>(
({ sort, cellProps = {}, width, children, ...sortableButtonProps }, ref) => {
const { isMinimum, isTruncated, hasOverflow, ...otherCellProps } = cellProps;
const { isMinimum, isTruncated, hasOverflow, scope, ...otherCellProps } = cellProps;
const isInHeaderRow = useHeaderRowContext();
let ariaSortValue = 'none';

if (sort === 'asc') {
Expand All @@ -38,6 +40,7 @@ export const SortableCell = forwardRef<HTMLButtonElement, ISortableCellProps>(
return (
<StyledHeaderCell
aria-sort={ariaSortValue}
scope={scope ?? (isInHeaderRow ? 'col' : undefined)}
width={width}
$isMinimum={isMinimum}
$isTruncated={isTruncated}
Expand Down
Loading
Loading