Skip to content

Commit

Permalink
Improve accessibility for sortable table columns (#5780)
Browse files Browse the repository at this point in the history
  • Loading branch information
deleonio authored Dec 22, 2023
2 parents 8d83104 + f0eba2f commit ed61ed3
Show file tree
Hide file tree
Showing 25 changed files with 144 additions and 149 deletions.
8 changes: 8 additions & 0 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ The component InputColor is a wrapper for the native HTML element `<input type="
For full accessibility, consider using predefined colors lists, e.g. using KolSelect or KolCheckbox.

[🐞 GitHub issue #5549](https://github.com/public-ui/kolibri/issues/5549)

## `aria-sort` changes sometimes not announced in NVDA

When a table column changes its sort order (i.e. when its `aria-sort` attribut changes), screen readers announce this change automatically.
For unknown reasons, this sometime does not happen in NVDA.

[🐞 GitHub issue (PR) #5780](https://github.com/public-ui/kolibri/pull/5780)
[🐞 NVDA issue #10890](https://github.com/nvaccess/nvda/issues/10890)
[🐞 NVDA issue #8132](https://github.com/nvaccess/nvda/issues/8132)

## input-number and input-date 'readonly' not announced in NVDA
Expand Down
67 changes: 14 additions & 53 deletions packages/components/src/components/table/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export class KolTable implements API {
* @deprecated only for backward compatibility
*/
private sortedColumnHead: KoliBriTableSelectedHead = { label: '', key: '', sortDirection: 'NOS' };
private ariaLive = '';

/**
* Defines whether to allow multi sort.
Expand Down Expand Up @@ -610,44 +609,21 @@ export class KolTable implements API {
}
return 0;
});

if (this.sortData.length === 1) {
this.ariaLive = translate(`kol-sort-${this.sortData[0].direction === 'ASC' ? 'ascending' : 'descending'}`, {
placeholders: { column: this.sortData[0].label, multi: '' },
});
} else {
let sortText = '';
for (let index = 1; index < this.sortData.length - 1; index++) {
const data = this.sortData[index];
sortText += translate(`kol-sort-then-${data.direction === 'ASC' ? 'ascending' : 'descending'}`, { placeholders: { column: data.label } });
}
sortText += translate(`kol-sort-then-last-${this.sortData[this.sortData.length - 1].direction === 'ASC' ? 'ascending' : 'descending'}`, {
placeholders: { column: this.sortData[this.sortData.length - 1].label },
});
this.ariaLive = translate(`kol-sort-${this.sortData[0].direction === 'ASC' ? 'ascending' : 'descending'}`, {
placeholders: { column: this.sortData[0].label, multi: sortText },
});
}
} else if (typeof this.sortFunction === 'function') {
switch (this.sortDirections.get(this.sortFunction)) {
case 'ASC':
sortedData = this.sortFunction([...this.state._data]);
this.ariaLive = translate('kol-sort-ascending', { placeholders: { column: cell.label } });
this.sortedColumnHead = { label: cell.label, key: cell.key, sortDirection: 'ASC' };
break;
case 'DESC':
sortedData = this.sortFunction([...this.state._data]).reverse();
this.ariaLive = translate('kol-sort-descending', { placeholders: { column: cell.label } });
this.sortedColumnHead = { label: cell.label, key: cell.key, sortDirection: 'DESC' };
break;
case 'NOS':
default:
sortedData = [...this.state._data];
this.sortedColumnHead = { label: '', key: '', sortDirection: 'NOS' };
this.ariaLive = translate('kol-sort-none', { placeholders: { column: cell.label } });
}
} else {
this.ariaLive = translate('kol-table-sort-none');
}
setState(this, '_sortedData', sortedData);
};
Expand Down Expand Up @@ -773,9 +749,6 @@ export class KolTable implements API {

return (
<Host>
<div style={{ height: '0', width: '0', overflow: 'hidden' }} aria-live="assertive">
{this.ariaLive}
</div>
{this.pageEndSlice > 0 && this.showPagination && (
<div class="pagination">
<span>
Expand Down Expand Up @@ -869,43 +842,31 @@ export class KolTable implements API {
}
}
return (
<th // role="columnheader"
<th
class={col.textAlign ? `align-${col.textAlign}` : undefined}
key={`thead-${rowIndex}-${colIndex}-${headerCell.label}`}
scope={typeof headerCell.colSpan === 'number' && headerCell.colSpan > 1 ? 'colgroup' : 'col'}
colSpan={headerCell.colSpan}
rowSpan={headerCell.rowSpan}
style={{
textAlign: col.textAlign,
width: col.width,
}}
aria-sort={sortDirection}
data-sort={`sort-${shortSortDirection}`}
>
<div class="w-full flex gap-1 items-center">
<div
class={{
'w-full': true,
[col.textAlign as string]: typeof col.textAlign === 'string' && col.textAlign.length > 0,
}}
style={{
textAlign: col.textAlign,
{!this.disableSort && (typeof headerCell.compareFn === 'function' || typeof headerCell.sort === 'function') ? (
<kol-button-wc
class="table-sort-button"
exportparts="icon"
_icons={{ right: sortButtonIcon }}
_label={col.label}
_on={{
onClick: () => this.changeCellSort(headerCell),
}}
>
{col.label}
</div>
{!this.disableSort && (typeof headerCell.compareFn === 'function' || typeof headerCell.sort === 'function') && (
<kol-button
exportparts="icon"
_icons={sortButtonIcon}
_hideLabel
_label={translate('kol-change-order', { placeholders: { colLabel: col.label } })}
_on={{
onClick: () => this.changeCellSort(headerCell),
}}
_variant="ghost"
></kol-button>
)}
</div>
></kol-button-wc>
) : (
col.label
)}
</th>
);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/components/table/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,15 @@ Warum die Tabelle einen **Tabindex** hat, wird auf der folgenden Webseite beschr

- [kol-button](../button)
- [kol-pagination](../pagination)
- kol-button-wc

### Graph

```mermaid
graph TD;
kol-table --> kol-button
kol-table --> kol-pagination
kol-table --> kol-button-wc
kol-button --> kol-button-wc
kol-button-wc --> kol-span-wc
kol-button-wc --> kol-tooltip-wc
Expand Down
25 changes: 21 additions & 4 deletions packages/components/src/components/table/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,27 @@
width: 100%;
}

th > div {
display: grid;
grid-template-columns: 1fr auto;
place-items: center;
.table-sort-button .button {
color: inherit;
}

th.align-left {
text-align: left;
& .table-sort-button .button-inner {
justify-items: start;
}
}
th.align-center {
text-align: center;
& .table-sort-button .button-inner {
justify-items: center;
}
}
th.align-right {
text-align: right;
& .table-sort-button .button-inner {
justify-items: end;
}
}

div.pagination {
Expand Down
6 changes: 0 additions & 6 deletions packages/components/src/dev/theme-registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1161,12 +1161,6 @@ padding: 0.25em 0.5em;
th {
background-color: #eee;
}
th > div {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 0.25em;
}
:host > div.pagination {
padding: 0.5em;
}
Expand Down
8 changes: 0 additions & 8 deletions packages/components/src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ export default {
'logo-description': 'Logo {{orgShort}}. Bundesadler mit Flaggenstab und Schriftzug {{orgLong}}',
'open-link-in-tab': 'Öffnet in neuem Tab.',
'kolibri-logo': 'Logo von KoliBri',
'sort-ascending': 'Die Spalte {{column}} ist aufsteigend{{multi}} sortiert.',
'sort-descending': 'Die Spalte {{column}} ist absteigend{{multi}} sortiert.',
'sort-then-ascending': ', dann die Spalte {{column}} aufsteigend',
'sort-then-descending': ', dann die Spalte {{column}} absteigend',
'sort-then-last-ascending': ' und dann die Spalte {{column}} aufsteigend',
'sort-then-last-descending': ' und dann die Spalte {{column}} absteigend',
'sort-none': 'Spalte {{column}} nicht sortiert',
'table-sort-none': 'Keine Spalte ist sortiert.',
'table-pagination-label': 'Paginierung für die Tabelle {{label}}',
'avatar-alt': 'Avatar von {{name}}',
'toast-close-all': 'Alle schließen',
Expand Down
8 changes: 0 additions & 8 deletions packages/components/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ export default {
'logo-description': 'Logo {{orgShort}}. Federal eagle with flag staff and lettering {{orgLong}}',
'open-link-in-tab': 'Opens in new tab.',
'kolibri-logo': 'KoliBri logo',
'sort-descending': 'Sorted column {{column}} descending{{multi}}',
'sort-ascending': 'Sorted column {{column}} ascending{{multi}}',
'sort-then-ascending': ', then column {{column}} acending',
'sort-then-descending': ', then column {{column}} descending',
'sort-then-last-ascending': ' and then column {{column}} acending',
'sort-then-last-descending': ' and then column {{column}} descending',
'sort-none': 'column {{column}} not sorted',
'table-sort-none': 'No column is sorted.',
'table-pagination-label': 'Pagination for table {{label}}',
'avatar-alt': 'Avatar of {{name}}',
'toast-close-all': 'Close all',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const SampleDescription: FC<PropsWithChildren> = (props) => {
const hideMenus = useContext(HideMenusContext);

return hideMenus ? null : (
<div className="flex">
<div className="flex mb-sm">
<KolIndentedText>{props.children}</KolIndentedText>
<KolLink
_hideLabel
Expand Down
84 changes: 84 additions & 0 deletions packages/samples/react/src/components/table/column-alignment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { FC } from 'react';

import { KolTable, KolHeading } from '@public-ui/react';
import { SampleDescription } from '../SampleDescription';

const DATA = [{ left: 'Left Example', center: 'Center Example', right: 'Right Example' }];
type Data = (typeof DATA)[0];
const genericNonSorter = (data: Data[]): Data[] => data;

export const TableColumnAlignment: FC = () => (
<>
<SampleDescription>Table with columns headers and data in different text alignments.</SampleDescription>

<KolHeading _label="Simple table" _level={3}></KolHeading>
<KolTable
_label="Table for demonstration purposes with different text align properties"
_headers={{
horizontal: [
[
{ label: 'left', key: 'left', textAlign: 'left' },
{ label: 'center', key: 'center', textAlign: 'center' },
{ label: 'right', key: 'right', textAlign: 'right' },
],
],
}}
_data={DATA}
className="block"
style={{ maxWidth: '600px' }}
/>

<KolHeading _label="Table with sortable columns" _level={3} className="block mt-6"></KolHeading>
<KolTable
_label="Table for demonstration purposes with sortable columns"
_headers={{
horizontal: [
[
{ label: 'left', key: 'left', textAlign: 'left', sort: genericNonSorter },
{ label: 'center', key: 'center', textAlign: 'center', sort: genericNonSorter },
{ label: 'right', key: 'right', textAlign: 'right', sort: genericNonSorter },
],
],
}}
_data={DATA}
className="block"
style={{ maxWidth: '600px' }}
/>

<KolHeading _label="Table some sortable columns" _level={3} className="block mt-6"></KolHeading>
<KolTable
_label="Table for demonstration purposes with some but not all columns sortable"
_headers={{
horizontal: [
[
{ label: 'left', key: 'left', textAlign: 'left', sort: genericNonSorter },
{ label: 'center', key: 'center', textAlign: 'center', sort: genericNonSorter },
{ label: 'right', key: 'right', textAlign: 'right' },
],
],
}}
_data={DATA}
className="block"
style={{ maxWidth: '600px' }}
/>

<KolHeading _label="Table with vertical heading" _level={3} className="block mt-6"></KolHeading>
<KolTable
_label="Table for demonstration purposes with vertical heading"
_headers={{
horizontal: [
[
{ label: '', asTd: true },
{ label: 'left', key: 'left', textAlign: 'left' },
{ label: 'center', key: 'center', textAlign: 'center' },
{ label: 'right', key: 'right', textAlign: 'right' },
],
],
vertical: [[{ label: 'Vertical' }]],
}}
_data={DATA}
className="block"
style={{ maxWidth: '600px' }}
/>
</>
);
8 changes: 4 additions & 4 deletions packages/samples/react/src/components/table/routes.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Routes } from '../../shares/types';

import { TableBadgeSize } from './badge-size';

import { TableColumnAlignment } from './column-alignment';
import { TableRenderCell } from './render-cell';

import { TableSortTable } from './sort-date';
import { TableSortData } from './sort-data';
import { TableWithPagination } from './with-pagination';

export const TABLE_ROUTES: Routes = {
table: {
'badge-size': TableBadgeSize,
'column-alignment': TableColumnAlignment,
'render-cell': TableRenderCell,
'sort-data': TableSortTable,
'sort-data': TableSortData,
'with-pagination': TableWithPagination,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ const HEADERS: KoliBriTableHeaders = {
],
};

export const TableSortTable: FC = () => <KolTable _label="Sort a date column" _data={DATA} _headers={HEADERS} className="block" />;
export const TableSortData: FC = () => <KolTable _label="Sort a date column" _data={DATA} _headers={HEADERS} className="block" />;
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 2 additions & 17 deletions packages/themes/bmf/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1594,15 +1594,8 @@ export const BMF = KoliBri.createTheme('bmf', {
grid-template-columns: 1fr auto;
align-items: center;
}
th div.center {
justify-content: center;
}
th div.right {
justify-content: end;
}
th,
td {
vertical-align: top;
border-bottom: 1px solid var(--color-granite);
height: 1.25rem;
}
Expand All @@ -1612,22 +1605,14 @@ export const BMF = KoliBri.createTheme('bmf', {
tbody th,
td {
padding: 1em 0;
vertical-align: top;
}
th kol-button,
td kol-button {
margin-top: -0.75rem;
margin-bottom: -0.75rem;
}
td.center > div {
display: flex;
justify-content: center;
}
td.right > div {
display: flex;
justify-content: end;
}
th[aria-sort='ascending'],
th[aria-sort='descending'] {
.table-sort-button .button {
font-weight: 700;
}
:host > div:last-child,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ed61ed3

Please sign in to comment.