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

[Fix] Programmatic determination of table column sort #12453

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ const ResponsiveTable = <TData extends object, TFilters = object>({
};
}

const canSort = table
.getFlatHeaders()
.some((header) => header.column.getCanSort());

return (
<>
<Table.Controls add={add}>
Expand Down Expand Up @@ -353,7 +357,7 @@ const ResponsiveTable = <TData extends object, TFilters = object>({
{table.getHeaderGroups().map((headerGroup) => (
<Table.HeadRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Table.HeadCell key={header.id} header={header} />
<Table.HeadCell key={header.id} id={id} header={header} />
))}
</Table.HeadRow>
))}
Expand Down Expand Up @@ -397,6 +401,15 @@ const ResponsiveTable = <TData extends object, TFilters = object>({
)}
</>
)}
{canSort && (
<span id={`sortHint-${id}`} data-h2-display="base(none)">
{intl.formatMessage({
defaultMessage: "Sort",
id: "LwruRb",
description: "Hint to let users know a table column can be sorted",
})}
</span>
)}
</>
);
};
Expand Down
22 changes: 16 additions & 6 deletions apps/web/src/components/Table/ResponsiveTable/SortButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ 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";

interface SortButtonProps<T> {
column: Column<T, unknown>;
children: ReactNode;
locked?: boolean;
tableId: string;
}

const SortButton = <T,>({ column, locked, children }: SortButtonProps<T>) => {
const SortButton = <T,>({
column,
locked,
tableId,
children,
}: SortButtonProps<T>) => {
const intl = useIntl();
const id = useId();
const ariaId = `${column.id}-${id}`;

if (!column.getCanSort()) {
// eslint-disable-next-line react/jsx-no-useless-fragment
Expand All @@ -30,6 +38,8 @@ const SortButton = <T,>({ column, locked, children }: SortButtonProps<T>) => {

return (
<Button
id={ariaId}
aria-labelledby={`${ariaId} sortHint-${tableId}`}
mode="inline"
color="whiteFixed"
fontSize="caption"
Expand All @@ -48,14 +58,14 @@ const SortButton = <T,>({ column, locked, children }: SortButtonProps<T>) => {
<span data-h2-visually-hidden="base(invisible)">
{sortDirection === "asc"
? intl.formatMessage({
defaultMessage: " (Ascending)",
id: "90E1fF",
defaultMessage: "Ascending",
id: "RGHXLW",
description:
"Message added to indicate a table column is sorted in ascending order",
})
: intl.formatMessage({
defaultMessage: " (Descending)",
id: "uKD+km",
defaultMessage: "Descending",
id: "zIv0jm",
description:
"Message added to indicate a table column is sorted in descending order",
})}
Expand Down
16 changes: 14 additions & 2 deletions apps/web/src/components/Table/ResponsiveTable/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,28 @@ const Row = (props: RowProps) => (
/>
);

type AriaSort = "ascending" | "descending" | "none" | undefined;

type CellHTMLProps = DetailedHTMLProps<
HTMLAttributes<HTMLTableCellElement>,
HTMLTableCellElement
>;

type HeadCellProps<T> = {
header: Header<T, unknown>;
id: string;
} & CellHTMLProps;

const HeadCell = <T,>({ header, ...rest }: HeadCellProps<T>) => {
const HeadCell = <T,>({ header, id, ...rest }: HeadCellProps<T>) => {
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 = undefined;
if (sortDirection) {
ariaSort = sortDirection === "asc" ? "ascending" : "descending";
}

return (
<th
role="columnheader"
Expand All @@ -132,6 +141,9 @@ const HeadCell = <T,>({ header, ...rest }: HeadCellProps<T>) => {
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)",
Expand All @@ -140,7 +152,7 @@ const HeadCell = <T,>({ header, ...rest }: HeadCellProps<T>) => {
{...rest}
>
{header.isPlaceholder ? null : (
<SortButton column={header.column} locked={sortingLocked}>
<SortButton tableId={id} column={header.column} locked={sortingLocked}>
{flexRender(header.column.columnDef.header, header.getContext())}
</SortButton>
)}
Expand Down
20 changes: 12 additions & 8 deletions apps/web/src/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -2147,10 +2147,6 @@
"defaultMessage": "Conditions favorables",
"description": "Heading for the directives digital enabling conditions component"
},
"90E1fF": {
"defaultMessage": "(Ordre croissant)",
"description": "Message added to indicate a table column is sorted in ascending order"
},
"91b+W0": {
"defaultMessage": "Niveau exigé",
"description": "Required skill level column header for tables"
Expand Down Expand Up @@ -4711,6 +4707,10 @@
"defaultMessage": "Expérience minimale ou combinaison équivalente d'études",
"description": "Title for Minimum experience or equivalent education"
},
"LwruRb": {
"defaultMessage": "Trier",
"description": "Hint to let users know a table column can be sorted"
},
"LxRoWB": {
"defaultMessage": "Création de la collectivité réussie!",
"description": "Message displayed after a community is created"
Expand Down Expand Up @@ -5775,6 +5775,10 @@
"defaultMessage": "Date d’attribution",
"description": "Label displayed on award form for date awarded input"
},
"RGHXLW": {
"defaultMessage": "Ordre croissant",
"description": "Message added to indicate a table column is sorted in ascending order"
},
"RGl+Dr": {
"defaultMessage": "Cette possibilité d'emploi s'adresse aux employés internes",
"description": "Title of a note describing that a pool is only open to employees"
Expand Down Expand Up @@ -11474,10 +11478,6 @@
"defaultMessage": "Le contrat comporte-t-il des exigences particulières en matière de personnel?",
"description": "Label for _contract amendable_ fieldset in the _digital services contracting questionnaire_"
},
"uKD+km": {
"defaultMessage": "(Ordre décroissant)",
"description": "Message added to indicate a table column is sorted in descending order"
},
"uLncuU": {
"defaultMessage": "Nom du (de la) candidat(e)",
"description": "Title displayed on the User table Candidate name column."
Expand Down Expand Up @@ -12342,6 +12342,10 @@
"defaultMessage": "Veuillez répondre à ces questions au mieux de vos capacités afin d'aider les gestionnaires d'embauche à mieux comprendre pourquoi vous correspondez à l'occasion d'emploi.",
"description": "Reminder what general questions are used for."
},
"zIv0jm": {
"defaultMessage": "Ordre décroissant",
"description": "Message added to indicate a table column is sorted in descending order"
},
"zJKngJ": {
"defaultMessage": "Gérer votre expérience et les processus de recrutement pour lesquels vous êtes qualifié(e).",
"description": "Description for the Career timeline and recruitment page in applicant profile."
Expand Down
Loading