From de0ce6159c3d4920a56393dc387f58c92886b8af Mon Sep 17 00:00:00 2001 From: Eric Sizer Date: Thu, 9 Jan 2025 14:11:28 -0500 Subject: [PATCH 1/5] add aria-sort to table headings --- .../src/components/Table/ResponsiveTable/Table.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/web/src/components/Table/ResponsiveTable/Table.tsx b/apps/web/src/components/Table/ResponsiveTable/Table.tsx index ccb623f1f3f..ad5ffd0965c 100644 --- a/apps/web/src/components/Table/ResponsiveTable/Table.tsx +++ b/apps/web/src/components/Table/ResponsiveTable/Table.tsx @@ -110,6 +110,8 @@ const Row = (props: RowProps) => ( /> ); +type AriaSort = "ascending" | "descending" | "none" | undefined; + type CellHTMLProps = DetailedHTMLProps< HTMLAttributes, HTMLTableCellElement @@ -123,6 +125,12 @@ const HeadCell = ({ header, ...rest }: HeadCellProps) => { const isRowSelect = header.column.columnDef.meta?.isRowSelect; const shouldShrink = header.column.columnDef.meta?.shrink; const sortingLocked = header.column.columnDef.meta?.sortingLocked; + const sortDirection = header.column.getIsSorted(); + let ariaSort: AriaSort = "none"; + if (sortDirection) { + ariaSort = sortDirection === "asc" ? "ascending" : "descending"; + } + return ( ({ header, ...rest }: HeadCellProps) => { data-h2-font-size="base(caption)" data-h2-vertical-align="base(middle)" data-h2-font-weight="base(400)" + {...(header.column.getCanSort() && { + "aria-sort": ariaSort, + })} {...(!isRowSelect && !shouldShrink && { "data-h2-min-width": "base(x8)", From 2372f18ab3fd7e712f83f05df2341677af1777bb Mon Sep 17 00:00:00 2001 From: Eric Sizer Date: Fri, 10 Jan 2025 08:29:24 -0500 Subject: [PATCH 2/5] improve sort with hints --- .../Table/ResponsiveTable/ResponsiveTable.tsx | 15 ++++++++++++++- .../Table/ResponsiveTable/SortButton.tsx | 14 ++++++++++++-- .../components/Table/ResponsiveTable/Table.tsx | 7 ++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/Table/ResponsiveTable/ResponsiveTable.tsx b/apps/web/src/components/Table/ResponsiveTable/ResponsiveTable.tsx index 432ae27a183..bf998aaed45 100644 --- a/apps/web/src/components/Table/ResponsiveTable/ResponsiveTable.tsx +++ b/apps/web/src/components/Table/ResponsiveTable/ResponsiveTable.tsx @@ -320,6 +320,10 @@ const ResponsiveTable = ({ }; } + const canSort = table + .getFlatHeaders() + .some((header) => header.column.getCanSort()); + return ( <> @@ -353,7 +357,7 @@ const ResponsiveTable = ({ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( - + ))} ))} @@ -397,6 +401,15 @@ const ResponsiveTable = ({ )} )} + {canSort && ( + + {intl.formatMessage({ + defaultMessage: "Sort", + id: "LwruRb", + description: "Hint to let users know a table column can be sorted", + })} + + )} ); }; diff --git a/apps/web/src/components/Table/ResponsiveTable/SortButton.tsx b/apps/web/src/components/Table/ResponsiveTable/SortButton.tsx index 40afcc3ab66..6fe25b525e7 100644 --- a/apps/web/src/components/Table/ResponsiveTable/SortButton.tsx +++ b/apps/web/src/components/Table/ResponsiveTable/SortButton.tsx @@ -4,7 +4,7 @@ import ArrowDownIcon from "@heroicons/react/20/solid/ArrowDownIcon"; import ArrowUpIcon from "@heroicons/react/20/solid/ArrowUpIcon"; import ArrowsUpDownIcon from "@heroicons/react/20/solid/ArrowsUpDownIcon"; import LockClosedIcon from "@heroicons/react/24/solid/LockClosedIcon"; -import { ReactNode } from "react"; +import { ReactNode, useId } from "react"; import { Button } from "@gc-digital-talent/ui"; @@ -12,10 +12,18 @@ interface SortButtonProps { column: Column; children: ReactNode; locked?: boolean; + tableId: string; } -const SortButton = ({ column, locked, children }: SortButtonProps) => { +const SortButton = ({ + column, + locked, + tableId, + children, +}: SortButtonProps) => { const intl = useIntl(); + const id = useId(); + const ariaId = `${column.id}-${id}`; if (!column.getCanSort()) { // eslint-disable-next-line react/jsx-no-useless-fragment @@ -30,6 +38,8 @@ const SortButton = ({ column, locked, children }: SortButtonProps) => { return (