Skip to content

Commit d38c621

Browse files
authored
fix(TableRow): correct prop types and ai label detection (#20247)
1 parent 18999f1 commit d38c621

File tree

3 files changed

+129
-53
lines changed

3 files changed

+129
-53
lines changed

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2303,12 +2303,27 @@ Map {
23032303
"TableRow": {
23042304
"$$typeof": Symbol(react.forward_ref),
23052305
"propTypes": {
2306+
"aria-controls": {
2307+
"type": "string",
2308+
},
2309+
"aria-label": {
2310+
"type": "string",
2311+
},
2312+
"ariaLabel": {
2313+
"type": "string",
2314+
},
23062315
"className": {
23072316
"type": "string",
23082317
},
2318+
"isExpanded": {
2319+
"type": "bool",
2320+
},
23092321
"isSelected": {
23102322
"type": "bool",
23112323
},
2324+
"onExpand": {
2325+
"type": "func",
2326+
},
23122327
},
23132328
"render": [Function],
23142329
},
@@ -8708,12 +8723,27 @@ Map {
87088723
"TableRow" => {
87098724
"$$typeof": Symbol(react.forward_ref),
87108725
"propTypes": {
8726+
"aria-controls": {
8727+
"type": "string",
8728+
},
8729+
"aria-label": {
8730+
"type": "string",
8731+
},
8732+
"ariaLabel": {
8733+
"type": "string",
8734+
},
87118735
"className": {
87128736
"type": "string",
87138737
},
8738+
"isExpanded": {
8739+
"type": "bool",
8740+
},
87148741
"isSelected": {
87158742
"type": "bool",
87168743
},
8744+
"onExpand": {
8745+
"type": "func",
8746+
},
87178747
},
87188748
"render": [Function],
87198749
},

packages/react/src/components/DataTable/TableExpandRow.tsx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,57 @@ import PropTypes from 'prop-types';
1010
import React, {
1111
Children,
1212
isValidElement,
13+
type HTMLAttributes,
1314
type MouseEventHandler,
1415
type PropsWithChildren,
1516
} from 'react';
1617
import { ChevronRight } from '@carbon/icons-react';
1718
import TableCell from './TableCell';
1819
import { usePrefix } from '../../internal/usePrefix';
19-
import { TableRowProps } from './TableRow';
2020
import TableSlugRow from './TableSlugRow';
2121
import TableDecoratorRow from './TableDecoratorRow';
2222
import { AILabel } from '../AILabel';
2323
import { isComponentElement } from '../../internal';
2424

25-
export interface TableExpandRowProps extends PropsWithChildren<TableRowProps> {
25+
/** Props shared between `TableRow` and `TableExpandRow`. */
26+
export interface TableRowExpandInteropProps {
27+
/**
28+
* @deprecated Use `aria-label` instead.
29+
*/
30+
ariaLabel?: string;
31+
/**
32+
* Specify the string read by a voice reader when the expand trigger is
33+
* focused
34+
*/
35+
'aria-label'?: string;
36+
/**
37+
* Space separated list of one or more ID values referencing the TableExpandedRow(s) being controlled by the TableExpandRow
38+
*/
39+
'aria-controls'?: string;
40+
/**
41+
* Specify whether this row is expanded or not. This helps coordinate data
42+
* attributes so that `TableExpandRow` and `TableExpandedRow` work together
43+
*/
44+
isExpanded?: boolean;
45+
/**
46+
* Hook for when a listener initiates a request to expand the given row
47+
*/
48+
onExpand?: MouseEventHandler<HTMLButtonElement>;
49+
/**
50+
* Specify if the row is selected.
51+
*/
52+
isSelected?: boolean;
53+
}
54+
55+
export interface TableExpandRowProps
56+
extends PropsWithChildren<
57+
Omit<HTMLAttributes<HTMLTableRowElement>, 'onClick'>
58+
>,
59+
Omit<TableRowExpandInteropProps, 'aria-label' | 'onExpand'> {
2660
/**
2761
* Space separated list of one or more ID values referencing the TableExpandedRow(s) being controlled by the TableExpandRow
2862
*/
29-
['aria-controls']?: string;
63+
'aria-controls'?: string;
3064

3165
/**
3266
* @deprecated This prop has been deprecated and will be
@@ -39,7 +73,7 @@ export interface TableExpandRowProps extends PropsWithChildren<TableRowProps> {
3973
* Specify the string read by a voice reader when the expand trigger is
4074
* focused
4175
*/
42-
['aria-label']: string;
76+
'aria-label': string;
4377

4478
/**
4579
* The id of the matching th node in the table head. Addresses a11y concerns outlined here: https://www.ibm.com/able/guidelines/ci162/info_and_relationships.html and https://www.w3.org/TR/WCAG20-TECHS/H43
@@ -154,14 +188,14 @@ TableExpandRow.propTypes = {
154188
* Space separated list of one or more ID values referencing the TableExpandedRow(s) being controlled by the TableExpandRow
155189
* TODO: make this required in v12
156190
*/
157-
['aria-controls']: PropTypes.string,
191+
'aria-controls': PropTypes.string,
158192

159193
/**
160194
* Specify the string read by a voice reader when the expand trigger is
161195
* focused
162196
*/
163197
/**@ts-ignore*/
164-
['aria-label']: PropTypes.string,
198+
'aria-label': PropTypes.string,
165199

166200
/**
167201
* Deprecated, please use `aria-label` instead.

packages/react/src/components/DataTable/TableRow.tsx

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,83 +5,95 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import React, { Children, type HTMLAttributes } from 'react';
8+
import React, { Children, forwardRef, type HTMLAttributes } from 'react';
99
import PropTypes from 'prop-types';
1010
import cx from 'classnames';
1111
import { usePrefix } from '../../internal/usePrefix';
1212
import TableSlugRow from './TableSlugRow';
1313
import TableDecoratorRow from './TableDecoratorRow';
1414
import { AILabel } from '../AILabel';
1515
import { isComponentElement } from '../../internal';
16+
import type { TableRowExpandInteropProps } from './TableExpandRow';
1617

17-
export interface TableRowProps extends HTMLAttributes<HTMLTableRowElement> {
18+
export interface TableRowProps
19+
extends HTMLAttributes<HTMLTableRowElement>,
20+
TableRowExpandInteropProps {
1821
/**
1922
* Specify an optional className to be applied to the container node
2023
*/
2124
className?: string;
22-
/**
23-
* Specify if the row is selected
24-
*/
25-
isSelected?: boolean;
2625
}
2726

28-
const TableRow = React.forwardRef<HTMLTableCellElement, TableRowProps>(
29-
(props, ref) => {
30-
const prefix = usePrefix();
27+
const frFn = forwardRef<HTMLTableRowElement, TableRowProps>;
3128

32-
let rowHasAILabel;
33-
if (props?.children) {
34-
// TODO: Why is this loop a `map`? It's not returning anything. Ideally,
35-
// it seems that it should be a `some`. Maybe I'm missing something?
36-
Children.toArray(props.children).map((child) => {
37-
if (isComponentElement(child, TableSlugRow)) {
38-
if (child.props.slug) {
39-
rowHasAILabel = true;
40-
}
41-
} else if (
42-
isComponentElement(child, TableDecoratorRow) &&
43-
isComponentElement(child.props.decorator, AILabel)
44-
) {
45-
rowHasAILabel = true;
46-
}
47-
});
48-
}
49-
// Remove unnecessary props if provided to this component, these are
50-
// only useful in `TableExpandRow`
51-
const className = cx(props.className, {
52-
[`${prefix}--data-table--selected`]: props.isSelected,
53-
[`${prefix}--data-table--slug-row ${prefix}--data-table--ai-label-row`]:
54-
rowHasAILabel,
55-
});
29+
const TableRow = frFn((props, ref) => {
30+
// Remove unnecessary props if provided to this component, these are
31+
// only useful in `TableExpandRow`
32+
const {
33+
ariaLabel,
34+
'aria-label': ariaLabelAlt,
35+
'aria-controls': ariaControls,
36+
onExpand,
37+
isExpanded,
38+
isSelected,
39+
...cleanProps
40+
} = props;
5641

57-
const {
58-
ariaLabel,
59-
'aria-label': ariaLabelAlt,
60-
'aria-controls': ariaControls,
61-
onExpand,
62-
isExpanded,
63-
isSelected,
64-
...cleanProps
65-
} = props as any;
42+
const prefix = usePrefix();
6643

67-
if (className) {
68-
cleanProps.className = className;
44+
const rowHasAILabel = Children.toArray(props.children).some((child) => {
45+
if (isComponentElement(child, TableSlugRow)) {
46+
return !!child.props.slug;
6947
}
7048

71-
return <tr ref={ref} {...cleanProps} />;
49+
return (
50+
isComponentElement(child, TableDecoratorRow) &&
51+
isComponentElement(child.props.decorator, AILabel)
52+
);
53+
});
54+
55+
const className = cx(props.className, {
56+
[`${prefix}--data-table--selected`]: isSelected,
57+
[`${prefix}--data-table--slug-row ${prefix}--data-table--ai-label-row`]:
58+
rowHasAILabel,
59+
});
60+
61+
if (className) {
62+
cleanProps.className = className;
7263
}
73-
);
64+
65+
return <tr ref={ref} {...cleanProps} />;
66+
});
7467

7568
TableRow.propTypes = {
7669
/**
7770
* Specify an optional className to be applied to the container node
7871
*/
7972
className: PropTypes.string,
80-
8173
/**
8274
* Specify if the row is selected
8375
*/
8476
isSelected: PropTypes.bool,
77+
/**
78+
* Non-standard alias for `aria-label`.
79+
*/
80+
ariaLabel: PropTypes.string,
81+
/**
82+
* Accessible label for the row element.
83+
*/
84+
'aria-label': PropTypes.string,
85+
/**
86+
* Associates this row with the id of the corresponding expanded row content.
87+
*/
88+
'aria-controls': PropTypes.string,
89+
/**
90+
* Handler called when the row’s expand toggle is clicked.
91+
*/
92+
onExpand: PropTypes.func,
93+
/**
94+
* Flag indicating whether the row is currently expanded.
95+
*/
96+
isExpanded: PropTypes.bool,
8597
};
8698

8799
export default TableRow;

0 commit comments

Comments
 (0)