diff --git a/packages/tables/src/elements/Cell.spec.tsx b/packages/tables/src/elements/Cell.spec.tsx index 8bcf2c6eaf3..c2566698e90 100644 --- a/packages/tables/src/elements/Cell.spec.tsx +++ b/packages/tables/src/elements/Cell.spec.tsx @@ -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', () => { @@ -113,4 +115,18 @@ describe('Cell', () => { expect(getByTestId('cell').firstChild).toHaveStyle(hideVisually()); }); + + it('renders dangerouslySetInnerHTML content', () => { + const { getByTestId } = render( + + + + Name' }} /> + + +
+ ); + + expect(getByTestId('cell').innerHTML).toBe('Name'); + }); }); diff --git a/packages/tables/src/elements/Cell.tsx b/packages/tables/src/elements/Cell.tsx index 42f6ce32943..e2f4d06cea2 100644 --- a/packages/tables/src/elements/Cell.tsx +++ b/packages/tables/src/elements/Cell.tsx @@ -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'; /** @@ -21,20 +22,23 @@ export const Cell = React.forwardRef( const { size } = useTableContext(); return ( - - {hidden && props.children ? ( - {props.children} - ) : ( - props.children - )} - + // the cell's content (for example, a nested table) is not in a header row + + + {hidden && props.children ? ( + {props.children} + ) : ( + props.children + )} + + ); } ); diff --git a/packages/tables/src/elements/HeaderCell.spec.tsx b/packages/tables/src/elements/HeaderCell.spec.tsx index 2a7d5d1af13..b9c96fbe772 100644 --- a/packages/tables/src/elements/HeaderCell.spec.tsx +++ b/packages/tables/src/elements/HeaderCell.spec.tsx @@ -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', () => { @@ -87,4 +90,136 @@ describe('HeaderCell', () => { expect(getByTestId('headerCell').firstChild).toHaveStyle(hideVisually()); }); + + it('renders dangerouslySetInnerHTML content', () => { + const { getByTestId } = render( + + + + Name' }} + /> + + +
+ ); + + expect(getByTestId('headerCell').innerHTML).toBe('Name'); + }); + + describe('scope', () => { + it('defaults to column scope in a header row', () => { + const { getByTestId } = render( + + + + Name + + +
+ ); + + expect(getByTestId('headerCell')).toHaveAttribute('scope', 'col'); + }); + + it('keeps an explicit scope', () => { + const { getByTestId } = render( + + + + + Group + + + +
+ ); + + expect(getByTestId('headerCell')).toHaveAttribute('scope', 'colgroup'); + }); + + it('applies the default when the given scope is undefined', () => { + const { getByTestId } = render( + + + + + Name + + + +
+ ); + + expect(getByTestId('headerCell')).toHaveAttribute('scope', 'col'); + }); + + it('sets no scope in a body row, where the browser infers it', () => { + const { getByTestId } = render( + + + + Name + Price + + + Apple + 1 + + + Pear + 2 + + +
+ ); + + 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( + + + + +
+ + + Name + + +
+ + + + + + Name + + +
+
+ + + + + Name + + +
+ + + + + ); + + expect(getByTestId('inHeaderCell')).not.toHaveAttribute('scope'); + expect(getByTestId('inCell')).not.toHaveAttribute('scope'); + expect(getByTestId('inNestedTable')).not.toHaveAttribute('scope'); + }); + }); }); diff --git a/packages/tables/src/elements/HeaderCell.tsx b/packages/tables/src/elements/HeaderCell.tsx index 4a7cb703c25..2ab1a307c36 100644 --- a/packages/tables/src/elements/HeaderCell.tsx +++ b/packages/tables/src/elements/HeaderCell.tsx @@ -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'; /** @@ -17,24 +18,29 @@ import { Cell } from './Cell'; * @extends ThHTMLAttributes */ export const HeaderCell = forwardRef( - ({ hidden, isMinimum, isTruncated, hasOverflow, ...props }, ref) => { + ({ hidden, isMinimum, isTruncated, hasOverflow, scope, ...props }, ref) => { const { size } = useTableContext(); + const isInHeaderRow = useHeaderRowContext(); return ( - - {hidden && props.children ? ( - {props.children} - ) : ( - props.children - )} - + // the cell's content (for example, a nested table) is not in a header row + + + {hidden && props.children ? ( + {props.children} + ) : ( + props.children + )} + + ); } ); diff --git a/packages/tables/src/elements/HeaderRow.tsx b/packages/tables/src/elements/HeaderRow.tsx index a57f751d1a3..13923f4cd7a 100644 --- a/packages/tables/src/elements/HeaderRow.tsx +++ b/packages/tables/src/elements/HeaderRow.tsx @@ -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 @@ -18,7 +19,11 @@ export const HeaderRow = React.forwardRef { const { size } = useTableContext(); - return ; + return ( + + + + ); } ); diff --git a/packages/tables/src/elements/SortableCell.spec.tsx b/packages/tables/src/elements/SortableCell.spec.tsx index c197cec06e1..e3183209006 100644 --- a/packages/tables/src/elements/SortableCell.spec.tsx +++ b/packages/tables/src/elements/SortableCell.spec.tsx @@ -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', () => { @@ -76,4 +78,50 @@ describe('SortableCell', () => { ` as any }); }); + + describe('scope', () => { + it('defaults to column scope in a header row', () => { + const { getByTestId } = render( + + + + Name + + +
+ ); + + expect(getByTestId('cell')).toHaveAttribute('scope', 'col'); + }); + + it('sets no scope in a body row', () => { + const { getByTestId } = render( + + + + Name + + +
+ ); + + expect(getByTestId('cell')).not.toHaveAttribute('scope'); + }); + + it('keeps an explicit scope from cell props', () => { + const { getByTestId } = render( + + + + + Name + + + +
+ ); + + expect(getByTestId('cell')).toHaveAttribute('scope', 'colgroup'); + }); + }); }); diff --git a/packages/tables/src/elements/SortableCell.tsx b/packages/tables/src/elements/SortableCell.tsx index 3df6d10ce10..f72aad21aa2 100644 --- a/packages/tables/src/elements/SortableCell.tsx +++ b/packages/tables/src/elements/SortableCell.tsx @@ -16,6 +16,7 @@ import { StyledSortableStrokeIconWrapper, StyledSortableFillIconWrapper } from '../styled'; +import { useHeaderRowContext } from '../utils/useHeaderRowContext'; /** * @deprecated use `Table.SortableCell` instead @@ -24,7 +25,8 @@ import { */ export const SortableCell = forwardRef( ({ 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') { @@ -38,6 +40,7 @@ export const SortableCell = forwardRef( return ( ( return ( - + {/* a nested table starts outside any header row */} + + + ); } diff --git a/packages/tables/src/utils/useHeaderRowContext.ts b/packages/tables/src/utils/useHeaderRowContext.ts new file mode 100644 index 00000000000..d1a166e45f7 --- /dev/null +++ b/packages/tables/src/utils/useHeaderRowContext.ts @@ -0,0 +1,15 @@ +/** + * Copyright Zendesk, Inc. + * + * Use of this source code is governed under the Apache License, Version 2.0 + * found at http://www.apache.org/licenses/LICENSE-2.0. + */ + +import React, { useContext } from 'react'; + +/** Whether a header cell sits directly in a `Table.HeaderRow` */ +export const HeaderRowContext = React.createContext(false); + +export const useHeaderRowContext = () => { + return useContext(HeaderRowContext); +};