Skip to content

Commit 69087c4

Browse files
chore: add decorator in dataTable (#18114)
* chore: add decorator in dataTable * fix: replace slug with ai-lable * fix: test cases * fix: test case * fix: test cases * fix: changed const name as per PR suggestions * fix: css issues * fix: css * fix: test * fix: removed decorator stories * fix: test * fix: test --------- Co-authored-by: Taylor Jones <tay1orjones@users.noreply.github.com>
1 parent 7b6d921 commit 69087c4

File tree

20 files changed

+380
-95
lines changed

20 files changed

+380
-95
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,6 +1973,17 @@ Map {
19731973
},
19741974
},
19751975
},
1976+
"TableDecoratorRow": Object {
1977+
"displayName": "TableDecoratorRow",
1978+
"propTypes": Object {
1979+
"className": Object {
1980+
"type": "string",
1981+
},
1982+
"decorator": Object {
1983+
"type": "node",
1984+
},
1985+
},
1986+
},
19761987
"TableExpandHeader": Object {
19771988
"propTypes": Object {
19781989
"aria-controls": Object {
@@ -8058,6 +8069,17 @@ Map {
80588069
},
80598070
},
80608071
},
8072+
"TableDecoratorRow" => Object {
8073+
"displayName": "TableDecoratorRow",
8074+
"propTypes": Object {
8075+
"className": Object {
8076+
"type": "string",
8077+
},
8078+
"decorator": Object {
8079+
"type": "node",
8080+
},
8081+
},
8082+
},
80618083
"TableExpandHeader" => Object {
80628084
"propTypes": Object {
80638085
"aria-controls": Object {

packages/react/src/__tests__/index-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ describe('Carbon Components React', () => {
205205
"TableBody",
206206
"TableCell",
207207
"TableContainer",
208+
"TableDecoratorRow",
208209
"TableExpandHeader",
209210
"TableExpandRow",
210211
"TableExpandedRow",

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import TableBatchActions from './TableBatchActions';
2424
import TableBody from './TableBody';
2525
import TableCell from './TableCell';
2626
import TableContainer from './TableContainer';
27+
import TableDecoratorRow from './TableDecoratorRow';
2728
import TableExpandHeader from './TableExpandHeader';
2829
import TableExpandRow from './TableExpandRow';
2930
import TableExpandedRow from './TableExpandedRow';
@@ -99,6 +100,7 @@ export interface DataTableHeader {
99100
key: string;
100101
header: React.ReactNode;
101102
slug?: React.ReactElement;
103+
decorator?: React.ReactElement;
102104
}
103105

104106
export interface DataTableRenderProps<RowType, ColTypes extends any[]> {
@@ -203,7 +205,8 @@ export interface DataTableRenderProps<RowType, ColTypes extends any[]> {
203205
};
204206
getCellProps: (getCellPropsArgs: { cell: DataTableCell<ColTypes> }) => {
205207
[key: string]: unknown;
206-
hasSlugHeader?: boolean;
208+
hasAILabelHeader?: boolean;
209+
hasDecoratorHeader?: boolean;
207210
};
208211

209212
// Custom event handlers
@@ -390,6 +393,7 @@ class DataTable<RowType, ColTypes extends any[]> extends React.Component<
390393
static TableBody: typeof TableBody;
391394
static TableCell: typeof TableCell;
392395
static TableContainer: typeof TableContainer;
396+
static TableDecoratorRow: typeof TableDecoratorRow;
393397
static TableExpandHeader: typeof TableExpandHeader;
394398
static TableExpandRow: typeof TableExpandRow;
395399
static TableExpandedRow: typeof TableExpandedRow;
@@ -473,6 +477,7 @@ class DataTable<RowType, ColTypes extends any[]> extends React.Component<
473477
isSortable,
474478
isSortHeader: sortHeaderKey === header.key,
475479
slug: header.slug,
480+
decorator: header.decorator,
476481
onClick: (event) => {
477482
const nextSortState = getNextSortState(this.props, this.state, {
478483
key: header.key,
@@ -747,7 +752,8 @@ class DataTable<RowType, ColTypes extends any[]> extends React.Component<
747752
getCellProps = ({ cell, ...rest }) => {
748753
return {
749754
...rest,
750-
hasSlugHeader: cell.hasSlugHeader,
755+
hasAILabelHeader: cell.hasAILabelHeader,
756+
hasDecoratorHeader: cell.hasDecoratorHeader,
751757
};
752758
};
753759

@@ -1035,6 +1041,7 @@ DataTable.TableBatchActions = TableBatchActions;
10351041
DataTable.TableBody = TableBody;
10361042
DataTable.TableCell = TableCell;
10371043
DataTable.TableContainer = TableContainer;
1044+
DataTable.TableDecoratorRow = TableDecoratorRow;
10381045
DataTable.TableExpandHeader = TableExpandHeader;
10391046
DataTable.TableExpandRow = TableExpandRow;
10401047
DataTable.TableExpandedRow = TableExpandedRow;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface TableCellProps extends ReactAttr<HTMLTableCellElement> {
2929
/**
3030
* Specify if the table cell is in an AI column
3131
*/
32-
hasSlugHeader?: boolean;
32+
hasAILabelHeader?: boolean;
3333

3434
/**
3535
* 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
@@ -38,11 +38,11 @@ interface TableCellProps extends ReactAttr<HTMLTableCellElement> {
3838
}
3939

4040
const TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps>(
41-
({ children, className, hasSlugHeader, colSpan, ...rest }, ref) => {
41+
({ children, className, hasAILabelHeader, colSpan, ...rest }, ref) => {
4242
const prefix = usePrefix();
4343

4444
const tableCellClassNames = classNames(className, {
45-
[`${prefix}--table-cell--column-slug`]: hasSlugHeader,
45+
[`${prefix}--table-cell--column-slug`]: hasAILabelHeader,
4646
});
4747
return (
4848
<td
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2023
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import PropTypes from 'prop-types';
9+
import React, { ReactNode } from 'react';
10+
import classNames from 'classnames';
11+
import { usePrefix } from '../../internal/usePrefix';
12+
import deprecate from '../../prop-types/deprecate';
13+
14+
export interface TableDecoratorRowProps {
15+
/**
16+
* The CSS class names of the cell that wraps the underlying input control
17+
*/
18+
className?: string;
19+
20+
/**
21+
* **Experimental**: Provide a `decorator` component to be rendered inside the `TableDecoratorRow` component
22+
*/
23+
decorator?: ReactNode;
24+
}
25+
26+
const TableDecoratorRow = ({
27+
className,
28+
decorator,
29+
}: TableDecoratorRowProps) => {
30+
const prefix = usePrefix();
31+
const TableDecoratorRowClasses = classNames({
32+
...(className && { [className]: true }),
33+
[`${prefix}--table-column-decorator`]: true,
34+
[`${prefix}--table-column-decorator--active`]: decorator,
35+
});
36+
37+
let normalizedDecorator = React.isValidElement(decorator)
38+
? (decorator as ReactNode)
39+
: null;
40+
if (
41+
normalizedDecorator &&
42+
normalizedDecorator['type']?.displayName === 'AILabel'
43+
) {
44+
normalizedDecorator = React.cloneElement(
45+
normalizedDecorator as React.ReactElement<any>,
46+
{
47+
size: 'mini',
48+
}
49+
);
50+
}
51+
52+
return <td className={TableDecoratorRowClasses}>{normalizedDecorator}</td>;
53+
};
54+
55+
TableDecoratorRow.displayName = 'TableDecoratorRow';
56+
TableDecoratorRow.propTypes = {
57+
/**
58+
* The CSS class names of the cell that wraps the underlying input control
59+
*/
60+
className: PropTypes.string,
61+
62+
/**
63+
* **Experimental**: Provide a `decorator` component to be rendered inside the `TableDecoratorRow` component
64+
*/
65+
decorator: PropTypes.node,
66+
};
67+
68+
export default TableDecoratorRow;

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,18 @@ const TableExpandRow = React.forwardRef(
7373
) => {
7474
const prefix = usePrefix();
7575

76-
// We need to put the slug before the expansion arrow and all other table cells after the arrow.
77-
let rowHasSlug;
78-
const slug = React.Children.toArray(children).map((child: any) => {
79-
if (child.type?.displayName === 'TableSlugRow') {
80-
if (child.props.slug) {
81-
rowHasSlug = true;
76+
// We need to put the AILabel and Decorator before the expansion arrow and all other table cells after the arrow.
77+
let rowHasAILabel;
78+
const decorator = React.Children.toArray(children).map((child: any) => {
79+
if (
80+
child.type?.displayName === 'TableSlugRow' ||
81+
child.type?.displayName === 'TableDecoratorRow'
82+
) {
83+
if (
84+
child.props.slug ||
85+
child.props.decorator?.type.displayName === 'AILabel'
86+
) {
87+
rowHasAILabel = true;
8288
}
8389

8490
return child;
@@ -87,7 +93,10 @@ const TableExpandRow = React.forwardRef(
8793

8894
const normalizedChildren = React.Children.toArray(children).map(
8995
(child: any) => {
90-
if (child.type?.displayName !== 'TableSlugRow') {
96+
if (
97+
child.type?.displayName !== 'TableSlugRow' &&
98+
child.type?.displayName !== 'TableDecoratorRow'
99+
) {
91100
return child;
92101
}
93102
}
@@ -98,15 +107,16 @@ const TableExpandRow = React.forwardRef(
98107
[`${prefix}--parent-row`]: true,
99108
[`${prefix}--expandable-row`]: isExpanded,
100109
[`${prefix}--data-table--selected`]: isSelected,
101-
[`${prefix}--data-table--slug-row`]: rowHasSlug,
110+
[`${prefix}--data-table--slug-row ${prefix}--data-table--ai-label-row`]:
111+
rowHasAILabel,
102112
},
103113
rowClassName
104114
);
105115
const previousValue = isExpanded ? 'collapsed' : undefined;
106116

107117
return (
108118
<tr {...rest} ref={ref as never} className={className} data-parent-row>
109-
{slug}
119+
{decorator}
110120
<TableCell
111121
className={`${prefix}--table-expand`}
112122
data-previous-value={previousValue}

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

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,16 @@ interface TableHeaderProps
114114
scope?: string;
115115

116116
/**
117-
* **Experimental**: Provide a `Slug` component to be rendered inside the `TableSlugRow` component
117+
* @deprecated please use decorator instead.
118+
* Provide a `Slug` component to be rendered inside the `TableSlugRow` component
118119
*/
119120
slug?: ReactNode;
120121

122+
/**
123+
* **Experimental**: Provide a `decorator` component to be rendered inside the `TableDecoratorRow` component
124+
*/
125+
decorator?: ReactNode;
126+
121127
/**
122128
* Specify which direction we are currently sorting by, should be one of DESC,
123129
* NONE, or ASC.
@@ -130,6 +136,7 @@ const TableHeader = React.forwardRef(function TableHeader(
130136
className: headerClassName,
131137
children,
132138
colSpan,
139+
decorator,
133140
isSortable = false,
134141
isSortHeader,
135142
onClick,
@@ -145,19 +152,32 @@ const TableHeader = React.forwardRef(function TableHeader(
145152
const prefix = usePrefix();
146153
const uniqueId = useId('table-sort');
147154

148-
// Slug is always size `mini`
149-
const slugRef = useRef<HTMLInputElement>(null);
150-
let normalizedSlug;
151-
if (slug) {
152-
normalizedSlug = React.cloneElement(slug as React.ReactElement<any>, {
153-
size: 'mini',
154-
ref: slugRef,
155-
});
155+
// AILabel is always size `mini`
156+
const AILableRef = useRef<HTMLInputElement>(null);
157+
158+
let colHasAILabel;
159+
let normalizedDecorator = React.isValidElement(slug ?? decorator)
160+
? (slug ?? decorator)
161+
: null;
162+
if (
163+
normalizedDecorator &&
164+
normalizedDecorator['type']?.displayName === 'AILabel'
165+
) {
166+
colHasAILabel = true;
167+
normalizedDecorator = React.cloneElement(
168+
normalizedDecorator as React.ReactElement<any>,
169+
{
170+
size: 'mini',
171+
ref: AILableRef,
172+
}
173+
);
156174
}
157175

158176
const headerLabelClassNames = classNames({
159177
[`${prefix}--table-header-label`]: true,
160-
[`${prefix}--table-header-label--slug`]: slug,
178+
[`${prefix}--table-header-label--slug ${prefix}--table-header-label--ai-label`]:
179+
colHasAILabel,
180+
[`${prefix}--table-header-label--decorator`]: decorator,
161181
});
162182

163183
if (!isSortable) {
@@ -172,7 +192,9 @@ const TableHeader = React.forwardRef(function TableHeader(
172192
{children ? (
173193
<div className={headerLabelClassNames}>
174194
{children}
175-
{normalizedSlug}
195+
<div className={`${prefix}--table-header-label--decorator-inner`}>
196+
{normalizedDecorator}
197+
</div>
176198
</div>
177199
) : null}
178200
</th>
@@ -198,11 +220,16 @@ const TableHeader = React.forwardRef(function TableHeader(
198220
});
199221

200222
const headerClasses = cx(headerClassName, `${prefix}--table-sort__header`, {
201-
[`${prefix}--table-sort__header--slug`]: slug,
223+
[`${prefix}--table-sort__header--ai-label`]: colHasAILabel,
224+
[`${prefix}--table-sort__header--decorator`]: decorator,
202225
});
203226

204227
const handleClick = (evt) => {
205-
if (slug && slugRef.current && slugRef.current.contains(evt.target)) {
228+
if (
229+
colHasAILabel &&
230+
AILableRef.current &&
231+
AILableRef.current.contains(evt.target)
232+
) {
206233
return;
207234
} else if (onClick) {
208235
return onClick(evt);
@@ -233,7 +260,9 @@ const TableHeader = React.forwardRef(function TableHeader(
233260
size={20}
234261
className={`${prefix}--table-sort__icon-unsorted`}
235262
/>
236-
{normalizedSlug}
263+
<div className={`${prefix}--table-header-label--decorator-inner`}>
264+
{normalizedDecorator}
265+
</div>
237266
</span>
238267
</button>
239268
</th>

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@ export interface TableRowProps extends ReactAttr<HTMLTableRowElement> {
2525
const TableRow = (props: TableRowProps) => {
2626
const prefix = usePrefix();
2727

28-
let rowHasSlug;
28+
let rowHasAILabel;
2929
if (props?.children) {
3030
React.Children.toArray(props.children).map((child: any) => {
31-
if (child.type?.displayName === 'TableSlugRow') {
32-
if (child.props.slug) {
33-
rowHasSlug = true;
31+
if (
32+
child.type?.displayName === 'TableSlugRow' ||
33+
child.type?.displayName === 'TableDecoratorRow'
34+
) {
35+
if (
36+
child.props.slug ||
37+
child.props.decorator?.type.displayName === 'AILabel'
38+
) {
39+
rowHasAILabel = true;
3440
}
3541
}
3642
});
@@ -39,7 +45,8 @@ const TableRow = (props: TableRowProps) => {
3945
// only useful in `TableExpandRow`
4046
const className = cx(props.className, {
4147
[`${prefix}--data-table--selected`]: props.isSelected,
42-
[`${prefix}--data-table--slug-row`]: rowHasSlug,
48+
[`${prefix}--data-table--slug-row ${prefix}--data-table--ai-label-row`]:
49+
rowHasAILabel,
4350
});
4451

4552
const {

0 commit comments

Comments
 (0)