diff --git a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap
index 7a3d934f8994..ac1427f38f3d 100644
--- a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap
+++ b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap
@@ -1070,6 +1070,7 @@ Array [
class="euiPopover__anchor eui-fullWidth"
>
- }
- isOpen={isPopoverOpen}
- closePopover={() => setIsPopoverOpen(false)}
- {...popoverArrowNavigationProps}
- >
-
-
+ {sortingArrow}
+
+ {display || displayAsText || id}
+
+
+
+ }
+ isOpen={isPopoverOpen}
+ closePopover={() => setIsPopoverOpen(false)}
+ {...popoverArrowNavigationProps}
+ >
+
+
+
+
+ {sortingScreenReaderText}
+
+
+
+
+ >
)}
);
};
+/**
+ * Column sorting utility helpers
+ */
+export const useSortingUtils = ({
+ sorting,
+ id,
+ showColumnActions,
+}: {
+ sorting?: EuiDataGridSorting;
+ id: string;
+ showColumnActions: boolean;
+}) => {
+ const sortedColumn = useMemo(
+ () => sorting?.columns.find((col) => col.id === id),
+ [sorting, id]
+ );
+ const isColumnSorted = !!sortedColumn;
+ const hasOnlyOneSort = sorting?.columns?.length === 1;
+
+ /**
+ * Arrow icon
+ */
+ const sortingArrow = isColumnSorted ? (
+
+ ) : null;
+
+ /**
+ * aria-sort attribute - should only be used when a single column is being sorted
+ * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-sort
+ * @see https://www.w3.org/WAI/ARIA/apg/example-index/table/sortable-table.html
+ * @see https://github.com/w3c/aria/issues/283 for potential future multi-column usage
+ */
+ const ariaSort: AriaAttributes['aria-sort'] =
+ // eslint-disable-next-line no-nested-ternary
+ isColumnSorted && hasOnlyOneSort
+ ? sorting.columns[0].direction === 'asc'
+ ? 'ascending'
+ : 'descending'
+ : undefined;
+
+ /**
+ * Sorting status - screen reader text
+ */
+ const sortingScreenReaderText = useMemo(() => {
+ if (!isColumnSorted) return null;
+ if (!showColumnActions && hasOnlyOneSort) return null; // in this scenario, the `aria-sort` attribute will be used by screen readers
+ return (
+ <>
+ {sorting?.columns?.map(({ id: columnId, direction }, index) => {
+ if (hasOnlyOneSort) {
+ if (direction === 'asc') {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ } else if (index === 0) {
+ if (direction === 'asc') {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ } else {
+ if (direction === 'asc') {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ }
+ })}
+ .
+ >
+ );
+ }, [isColumnSorted, showColumnActions, hasOnlyOneSort, sorting]);
+
+ return { sortingArrow, ariaSort, sortingScreenReaderText };
+};
+
/**
* Add keyboard arrow navigation to the cell actions popover
* to match the UX of the rest of EuiDataGrid
diff --git a/src/components/datagrid/data_grid.spec.tsx b/src/components/datagrid/data_grid.spec.tsx
index 5197ba4fc03a..c6e3949f9215 100644
--- a/src/components/datagrid/data_grid.spec.tsx
+++ b/src/components/datagrid/data_grid.spec.tsx
@@ -621,14 +621,17 @@ function getGridData() {
const rows = cy.get('[role=row]');
return rows.then((rows) => {
const headers: string[] = [];
- const data = [];
+ const data: Array<{ [key: string]: string }> = [];
// process header
const headerRow = rows[0];
const headerCells = headerRow.querySelectorAll('[role=columnheader]');
for (let i = 0; i < headerCells.length; i++) {
const headerCell = headerCells[i];
- headers.push(headerCell.textContent ?? '');
+ const headerContent = headerCell.querySelector(
+ '.euiDataGridHeaderCell__content'
+ )?.textContent;
+ headers.push(headerContent ?? '');
}
// process data rows
diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx
index 15177057ceee..81ded173355b 100644
--- a/src/components/datagrid/data_grid.test.tsx
+++ b/src/components/datagrid/data_grid.test.tsx
@@ -645,64 +645,6 @@ describe('EuiDataGrid', () => {
`);
});
- it('renders correct aria attributes on column headers', () => {
- const component = mount(
- {},
- }}
- rowCount={1}
- renderCellValue={() => 'value'}
- />
- );
-
- // no columns are sorted, expect no aria-sort or aria-describedby attributes
- expect(component.find('[role="columnheader"][aria-sort]').length).toBe(0);
- expect(
- component.find('[role="columnheader"][aria-describedby]').length
- ).toBe(0);
-
- // sort on one column
- component.setProps({
- sorting: { columns: [{ id: 'A', direction: 'asc' }], onSort: () => {} },
- });
-
- // expect A column to have aria-sort, expect no aria-describedby
- expect(component.find('[role="columnheader"][aria-sort]').length).toBe(1);
- expect(
- component.find(
- '[role="columnheader"][aria-sort="ascending"][data-test-subj="dataGridHeaderCell-A"]'
- ).length
- ).toBe(1);
- expect(
- component.find('[role="columnheader"][aria-describedby]').length
- ).toBe(0);
-
- // sort on both columns
- component.setProps({
- sorting: {
- columns: [
- { id: 'A', direction: 'asc' },
- { id: 'B', direction: 'desc' },
- ],
- onSort: () => {},
- },
- });
-
- // expect no aria-sort, both columns have aria-describedby
- expect(component.find('[role="columnheader"][aria-sort]').length).toBe(0);
- expect(
- component.find('[role="columnheader"][aria-describedby]').length
- ).toBe(2);
- expect(
- component.find('[role="columnheader"][aria-describedby="generated-id"]')
- .length
- ).toBe(2);
- });
-
it('renders additional toolbar controls', () => {
const component = render(