Skip to content
Merged
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
1 change: 1 addition & 0 deletions changelogs/upcoming/7656.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Updated `EuiTableHeaderCell` to show a subdued `sortable` icon for columns that are not currently sorted but can be
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
</div>
</th>
<th
aria-live="polite"
aria-sort="ascending"
class="euiTableHeaderCell emotion-euiTableHeaderCell"
data-test-subj="tableHeaderCell_name_0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ exports[`EuiTableHeaderCell sorting does not render a button with readOnly 1`] =
<thead>
<tr>
<th
aria-live="polite"
aria-sort="descending"
class="euiTableHeaderCell emotion-euiTableHeaderCell"
role="columnheader"
Expand All @@ -115,12 +114,47 @@ exports[`EuiTableHeaderCell sorting does not render a button with readOnly 1`] =
</table>
`;

exports[`EuiTableHeaderCell sorting is rendered with isSortAscending 1`] = `
exports[`EuiTableHeaderCell sorting renders a button with onSort 1`] = `
<table>
<thead>
<tr>
<th
aria-sort="descending"
class="euiTableHeaderCell emotion-euiTableHeaderCell"
role="columnheader"
scope="col"
>
<button
class="euiTableHeaderButton euiTableHeaderButton-isSorted emotion-euiTableHeaderCell__button"
data-test-subj="tableHeaderSortButton"
type="button"
>
<div
class="euiTableCellContent emotion-euiTableCellContent-euiTableHeaderCell__content"
>
<span
class="eui-textTruncate"
title="Test"
>
Test
</span>
<span
class="euiTableSortIcon"
data-euiicon-type="sortDown"
/>
</div>
</button>
</th>
</tr>
</thead>
</table>
`;

exports[`EuiTableHeaderCell sorting renders a sort arrow upwards with isSortAscending 1`] = `
<table>
<thead>
<tr>
<th
aria-live="polite"
aria-sort="ascending"
class="euiTableHeaderCell emotion-euiTableHeaderCell"
role="columnheader"
Expand All @@ -146,12 +180,11 @@ exports[`EuiTableHeaderCell sorting is rendered with isSortAscending 1`] = `
</table>
`;

exports[`EuiTableHeaderCell sorting is rendered with isSorted 1`] = `
exports[`EuiTableHeaderCell sorting renders a sort arrow with isSorted 1`] = `
<table>
<thead>
<tr>
<th
aria-live="polite"
aria-sort="descending"
class="euiTableHeaderCell emotion-euiTableHeaderCell"
role="columnheader"
Expand All @@ -177,19 +210,18 @@ exports[`EuiTableHeaderCell sorting is rendered with isSorted 1`] = `
</table>
`;

exports[`EuiTableHeaderCell sorting renders a button with onSort 1`] = `
exports[`EuiTableHeaderCell sorting renders with a sortable icon if \`onSort\` is passed 1`] = `
<table>
<thead>
<tr>
<th
aria-live="polite"
aria-sort="descending"
aria-sort="none"
class="euiTableHeaderCell emotion-euiTableHeaderCell"
role="columnheader"
scope="col"
>
<button
class="euiTableHeaderButton euiTableHeaderButton-isSorted emotion-euiTableHeaderCell__button"
class="euiTableHeaderButton emotion-euiTableHeaderCell__button"
data-test-subj="tableHeaderSortButton"
type="button"
>
Expand All @@ -203,8 +235,9 @@ exports[`EuiTableHeaderCell sorting renders a button with onSort 1`] = `
Test
</span>
<span
class="euiTableSortIcon"
data-euiicon-type="sortDown"
class="euiTableSortIcon euiTableSortIcon--sortable"
color="subdued"
data-euiicon-type="sortable"
/>
</div>
</button>
Expand Down
22 changes: 20 additions & 2 deletions src/components/table/table_cells_shared.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

import { css } from '@emotion/react';

import { UseEuiTheme } from '../../services';
import {
UseEuiTheme,
makeHighContrastColor,
tintOrShade,
} from '../../services';
import {
euiFontSize,
logicalCSS,
Expand All @@ -20,7 +24,7 @@ import { euiTableVariables } from './table.styles';
export const euiTableHeaderFooterCellStyles = (
euiThemeContext: UseEuiTheme
) => {
const { euiTheme } = euiThemeContext;
const { euiTheme, colorMode } = euiThemeContext;

// euiFontSize returns an object, so we keep object notation here to merge into css``
const sharedStyles = {
Expand All @@ -41,11 +45,25 @@ export const euiTableHeaderFooterCellStyles = (
euiTableHeaderCell__button: css`
${logicalCSS('width', '100%')}
font-weight: inherit;
line-height: inherit;

/* Tint the sortable icon a bit further */
.euiTableSortIcon--sortable {
color: ${makeHighContrastColor(
// Tint it arbitrarily high, the contrast util will take care of lowering back down to WCAG
tintOrShade(euiTheme.colors.subduedText, 0.9, colorMode),
3 // 3:1 ratio from https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html
)(euiTheme.colors.emptyShade)};
}

&:hover,
&:focus {
color: ${euiTheme.colors.primaryText};
text-decoration: underline;

.euiTableSortIcon--sortable {
color: ${euiTheme.colors.primaryText};
}
}
`,
euiTableFooterCell: css`
Expand Down
12 changes: 10 additions & 2 deletions src/components/table/table_header_cell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,23 @@ describe('EuiTableHeaderCell', () => {
});

describe('sorting', () => {
it('is rendered with isSorted', () => {
it('renders with a sortable icon if `onSort` is passed', () => {
const { container } = renderInTableHeader(
<EuiTableHeaderCell onSort={() => {}}>Test</EuiTableHeaderCell>
);

expect(container.firstChild).toMatchSnapshot();
});

it('renders a sort arrow with isSorted', () => {
const { container } = renderInTableHeader(
<EuiTableHeaderCell isSorted>Test</EuiTableHeaderCell>
);

expect(container.firstChild).toMatchSnapshot();
});

it('is rendered with isSortAscending', () => {
it('renders a sort arrow upwards with isSortAscending', () => {
const { container } = renderInTableHeader(
<EuiTableHeaderCell isSorted isSortAscending>
Test
Expand Down
97 changes: 43 additions & 54 deletions src/components/table/table_header_cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,17 @@ const CellContents = ({
align,
description,
children,
canSort,
isSorted,
isSortAscending,
showSortMsg,
}: {
className?: string;
align: HorizontalAlignment;
description: EuiTableHeaderCellProps['description'];
children: EuiTableHeaderCellProps['children'];
canSort?: boolean;
isSorted: EuiTableHeaderCellProps['isSorted'];
isSortAscending?: EuiTableHeaderCellProps['isSortAscending'];
showSortMsg: boolean;
}) => {
return (
<EuiTableCellContent
Expand Down Expand Up @@ -96,13 +96,20 @@ const CellContents = ({
<span>{description}</span>
</EuiScreenReaderOnly>
)}
{showSortMsg && isSorted && (
{isSorted ? (
<EuiIcon
className="euiTableSortIcon"
type={isSortAscending ? 'sortUp' : 'sortDown'}
size="m"
/>
)}
) : canSort ? (
<EuiIcon
className="euiTableSortIcon euiTableSortIcon--sortable"
type="sortable"
size="m"
color="subdued" // Tinted a bit further via CSS
/>
) : null}
</EuiTableCellContent>
);
};
Expand Down Expand Up @@ -135,67 +142,49 @@ export const EuiTableHeaderCell: FunctionComponent<EuiTableHeaderCellProps> = ({
const CellComponent = children ? 'th' : 'td';
const cellScope = CellComponent === 'th' ? scope ?? 'col' : undefined; // `scope` is only valid on `th` elements

const cellContents = (
<CellContents
css={styles.euiTableHeaderCell__content}
align={align}
description={description}
showSortMsg={true}
isSorted={isSorted}
isSortAscending={isSortAscending}
>
{children}
</CellContents>
);

if (onSort || isSorted) {
const buttonClasses = classNames('euiTableHeaderButton', {
'euiTableHeaderButton-isSorted': isSorted,
});

let ariaSortValue: HTMLAttributes<any>['aria-sort'] = 'none';
if (isSorted) {
ariaSortValue = isSortAscending ? 'ascending' : 'descending';
}

return (
<CellComponent
css={styles.euiTableHeaderCell}
className={classes}
scope={cellScope}
role="columnheader"
aria-sort={ariaSortValue}
aria-live="polite"
style={inlineStyles}
{...rest}
>
{onSort && !readOnly ? (
<button
type="button"
css={styles.euiTableHeaderCell__button}
className={buttonClasses}
onClick={onSort}
data-test-subj="tableHeaderSortButton"
>
{cellContents}
</button>
) : (
cellContents
)}
</CellComponent>
);
const canSort = !!(onSort && !readOnly);
let ariaSortValue: HTMLAttributes<HTMLTableCellElement>['aria-sort'];
if (isSorted) {
ariaSortValue = isSortAscending ? 'ascending' : 'descending';
} else if (canSort) {
ariaSortValue = 'none';
}

const cellContentsProps = {
css: styles.euiTableHeaderCell__content,
align,
description,
canSort,
isSorted,
isSortAscending,
children,
};

return (
<CellComponent
css={styles.euiTableHeaderCell}
className={classes}
scope={cellScope}
role="columnheader"
aria-sort={ariaSortValue}
style={inlineStyles}
{...rest}
>
{cellContents}
{canSort ? (
<button
type="button"
css={styles.euiTableHeaderCell__button}
className={classNames('euiTableHeaderButton', {
'euiTableHeaderButton-isSorted': isSorted,
})}
onClick={onSort}
data-test-subj="tableHeaderSortButton"
>
<CellContents {...cellContentsProps} />
</button>
) : (
<CellContents {...cellContentsProps} />
)}
</CellComponent>
);
};