Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiTableHeaderCell] Add a subdued sortable icon for columns that can be sorted #7656

Merged
merged 10 commits into from
Apr 10, 2024
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';
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
}

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
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
type="button"
css={styles.euiTableHeaderCell__button}
className={classNames('euiTableHeaderButton', {
'euiTableHeaderButton-isSorted': isSorted,
})}
onClick={onSort}
data-test-subj="tableHeaderSortButton"
>
<CellContents {...cellContentsProps} />
</button>
) : (
<CellContents {...cellContentsProps} />
)}
</CellComponent>
);
};
Loading