Skip to content

Commit dc23fff

Browse files
authored
feat(AnalyticalTable): add headerLabel column property (#5011)
This PR adds the `headerLabel` column offering a way to define a label for custom headers. Fixes #3121 Fixes #3189
1 parent fb2078a commit dc23fff

File tree

4 files changed

+44
-16
lines changed

4 files changed

+44
-16
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ const columns = [
5353
},
5454
{
5555
Header: () => <span>Friend Age</span>, // Custom header components!
56-
accessor: 'friend.age'
56+
accessor: 'friend.age',
57+
headerLabel: 'Custom Label'
5758
}
5859
];
5960
const data = [
@@ -1258,8 +1259,9 @@ describe('AnalyticalTable', () => {
12581259
});
12591260
});
12601261
it('columns drag & drop', () => {
1261-
columns.pop();
1262-
const updatedCols = [...columns, { accessor: 'friend.age', Header: 'Friend Age', disableDragAndDrop: true }];
1262+
const localCols = [...columns];
1263+
localCols.pop();
1264+
const updatedCols = [...localCols, { accessor: 'friend.age', Header: 'Friend Age', disableDragAndDrop: true }];
12631265
const reorder = cy.spy().as('reorder');
12641266
['ltr', 'rtl'].forEach((dir) => {
12651267
cy.mount(<AnalyticalTable dir={dir} data={data} columns={updatedCols} onColumnsReorder={reorder} />);
@@ -1742,7 +1744,7 @@ describe('AnalyticalTable', () => {
17421744
cy.findByText('Selected: {"0":true,"1":true,"2":true,"3":true}').should('be.visible');
17431745
});
17441746

1745-
it('a11y: grouped, filtered, sorted', () => {
1747+
it('a11y: grouped, filtered, sorted, headerLabel', () => {
17461748
cy.mount(<AnalyticalTable columns={columns} data={data} groupable filterable sortable />);
17471749
cy.findByText('Name').click();
17481750
cy.findByText('Sort Ascending').shadow().findByRole('listitem').click({ force: true });
@@ -1767,24 +1769,32 @@ describe('AnalyticalTable', () => {
17671769
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should(
17681770
'have.attr',
17691771
'aria-label',
1770-
'Grouped, To expand the row, press the spacebar'
1772+
'Name Grouped, To expand the row, press the spacebar'
17711773
);
17721774
cy.get('[name="navigation-right-arrow"]').click();
17731775
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should(
17741776
'have.attr',
17751777
'aria-label',
1776-
'Grouped, To collapse the row, press the spacebar'
1778+
'Name Grouped, To collapse the row, press the spacebar'
17771779
);
17781780
cy.findByText('Name').click();
17791781
cy.findByText('Ungroup').shadow().findByRole('listitem').click({ force: true });
1780-
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should('not.have.attr', 'aria-label');
1782+
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should('have.attr', 'aria-label', 'Name ');
17811783
cy.get('[data-column-id="name"]')
17821784
.should('have.attr', 'aria-sort', 'descending')
17831785
.and('have.attr', 'aria-label', 'Filtered');
17841786

17851787
cy.findByText('Name').click();
17861788
cy.findByText('Sort Ascending').shadow().get('[ui5-input]').typeIntoUi5Input('{selectall}{backspace}{enter}');
1787-
cy.get('[data-column-id="name"]').should('have.attr', 'aria-sort', 'descending').and('not.have.attr', 'aria-label');
1789+
cy.get('[data-column-id="name"]').should('have.attr', 'aria-sort', 'descending').and('have.attr', 'aria-label', '');
1790+
1791+
cy.get('[data-column-id="friend.age"]').should('have.attr', 'aria-label', 'Custom Label ');
1792+
cy.realPress('ArrowDown');
1793+
cy.get('[data-visible-row-index="1"][data-visible-column-index="3"]').should(
1794+
'have.attr',
1795+
'aria-label',
1796+
'Custom Label '
1797+
);
17881798
});
17891799

17901800
it("Expandable: don't scroll when expanded/collapsed", () => {
@@ -2234,7 +2244,7 @@ describe('AnalyticalTable', () => {
22342244
cy.focused().should('have.attr', 'data-row-index', '0').should('have.attr', 'data-column-index', '0');
22352245

22362246
cy.realPress('End');
2237-
cy.focused().should('have.attr', 'data-row-index', '0').should('have.attr', 'data-column-index', '2');
2247+
cy.focused().should('have.attr', 'data-row-index', '0').should('have.attr', 'data-column-index', '3');
22382248
cy.realPress('Home');
22392249
cy.focused().should('have.attr', 'data-row-index', '0').should('have.attr', 'data-column-index', '0');
22402250

packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const meta = {
6363
},
6464
{
6565
Header: () => <span>Friend Age</span>,
66+
headerLabel: 'Friend Age',
6667
accessor: 'friend.age',
6768
hAlign: TextAlign.End,
6869
filter: (rows, accessor, filterValue) => {

packages/main/src/components/AnalyticalTable/hooks/useA11y.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ interface UpdatedCellProptypes {
88
'aria-colindex'?: number;
99
}
1010

11-
const setCellProps = (cellProps, { cell: { column, row, value }, instance }) => {
11+
const setCellProps = (cellProps, { cell, instance }) => {
12+
const { column, row, value } = cell;
1213
const columnIndex = instance.visibleColumns.findIndex(({ id }) => id === column.id);
1314
const { alwaysShowSubComponent, renderRowSubComponent, translatableTexts, selectionMode, selectionBehavior } =
1415
instance.webComponentsReactProperties;
@@ -26,6 +27,10 @@ const setCellProps = (cellProps, { cell: { column, row, value }, instance }) =>
2627

2728
const isFirstUserCol = userCols[0]?.id === column.id || userCols[0]?.accessor === column.accessor;
2829
updatedCellProps['data-is-first-column'] = isFirstUserCol;
30+
updatedCellProps['aria-label'] = column.headerLabel || (typeof column.Header === 'string' ? column.Header : '');
31+
if (updatedCellProps['aria-label']) {
32+
updatedCellProps['aria-label'] += ' ';
33+
}
2934

3035
if ((isFirstUserCol && rowIsExpandable) || (row.isGrouped && row.canExpand)) {
3136
updatedCellProps.onKeyDown = row.getToggleRowExpandedProps?.()?.onKeyDown;
@@ -40,7 +45,7 @@ const setCellProps = (cellProps, { cell: { column, row, value }, instance }) =>
4045
updatedCellProps['aria-expanded'] = 'false';
4146
ariaLabel += ` ${translatableTexts.expandA11yText}`;
4247
}
43-
updatedCellProps['aria-label'] = ariaLabel;
48+
updatedCellProps['aria-label'] += ariaLabel;
4449
} else if (
4550
(selectionMode !== AnalyticalTableSelectionMode.None &&
4651
selectionBehavior !== AnalyticalTableSelectionBehavior.RowSelector &&
@@ -49,13 +54,12 @@ const setCellProps = (cellProps, { cell: { column, row, value }, instance }) =>
4954
) {
5055
if (row.isSelected) {
5156
updatedCellProps['aria-selected'] = 'true';
52-
updatedCellProps['aria-label'] = `${value ?? ''} ${translatableTexts.unselectA11yText}`;
57+
updatedCellProps['aria-label'] += `${value ?? ''} ${translatableTexts.unselectA11yText}`;
5358
} else {
5459
updatedCellProps['aria-selected'] = 'false';
55-
updatedCellProps['aria-label'] = `${value ?? ''} ${translatableTexts.selectA11yText}`;
60+
updatedCellProps['aria-label'] += `${value ?? ''} ${translatableTexts.selectA11yText}`;
5661
}
5762
}
58-
5963
return [cellProps, updatedCellProps];
6064
};
6165

@@ -68,17 +72,22 @@ const setHeaderProps = (headerProps, { column, instance }) => {
6872
const isFiltered = column?.filterValue && column?.filterValue.length > 0;
6973

7074
const updatedProps = {};
75+
updatedProps['aria-label'] = column.headerLabel ??= '';
76+
if (updatedProps['aria-label']) {
77+
updatedProps['aria-label'] += ' ';
78+
}
79+
7180
if (column.isSorted) {
7281
updatedProps['aria-sort'] = column.isSortedDesc ? 'descending' : 'ascending';
7382
}
7483
if (isFiltered) {
75-
updatedProps['aria-label'] = translatableTexts.filteredA11yText;
84+
updatedProps['aria-label'] += translatableTexts.filteredA11yText;
7685
}
7786
if (column.isGrouped) {
7887
if (updatedProps['aria-label']) {
7988
updatedProps['aria-label'] += ` ${translatableTexts.groupedA11yText}`;
8089
} else {
81-
updatedProps['aria-label'] = translatableTexts.groupedA11yText;
90+
updatedProps['aria-label'] += translatableTexts.groupedA11yText;
8291
}
8392
}
8493

packages/main/src/components/AnalyticalTable/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ export interface AnalyticalTableColumnDefinition {
104104
* Can either be string or a React component that will be rendered as column header
105105
*/
106106
Header?: string | ComponentType<any> | ((props?: any) => ReactNode);
107+
/**
108+
* Defines the `aria-label` for the whole column read by screen readers.
109+
*
110+
* __Note:__ This prop is mandatory if you pass anything else than `string` to the `Header` property. In this case the `headerLabel` should at least contain the column name.
111+
*
112+
* __Note:__ If `headerLabel` is defined, screen readers will always read the passed string, even when `Header` is a `string`.
113+
*/
114+
headerLabel?: string;
107115
/**
108116
* Tooltip for the column header. If not set, the display text will be the same as the Header if it is a `string`.
109117
*/

0 commit comments

Comments
 (0)