Skip to content

Commit

Permalink
chore: add decorator in dataTable (#18114)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
preetibansalui and tay1orjones authored Jan 2, 2025
1 parent 7b6d921 commit 69087c4
Show file tree
Hide file tree
Showing 20 changed files with 380 additions and 95 deletions.
22 changes: 22 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,17 @@ Map {
},
},
},
"TableDecoratorRow": Object {
"displayName": "TableDecoratorRow",
"propTypes": Object {
"className": Object {
"type": "string",
},
"decorator": Object {
"type": "node",
},
},
},
"TableExpandHeader": Object {
"propTypes": Object {
"aria-controls": Object {
Expand Down Expand Up @@ -8058,6 +8069,17 @@ Map {
},
},
},
"TableDecoratorRow" => Object {
"displayName": "TableDecoratorRow",
"propTypes": Object {
"className": Object {
"type": "string",
},
"decorator": Object {
"type": "node",
},
},
},
"TableExpandHeader" => Object {
"propTypes": Object {
"aria-controls": Object {
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ describe('Carbon Components React', () => {
"TableBody",
"TableCell",
"TableContainer",
"TableDecoratorRow",
"TableExpandHeader",
"TableExpandRow",
"TableExpandedRow",
Expand Down
11 changes: 9 additions & 2 deletions packages/react/src/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import TableBatchActions from './TableBatchActions';
import TableBody from './TableBody';
import TableCell from './TableCell';
import TableContainer from './TableContainer';
import TableDecoratorRow from './TableDecoratorRow';
import TableExpandHeader from './TableExpandHeader';
import TableExpandRow from './TableExpandRow';
import TableExpandedRow from './TableExpandedRow';
Expand Down Expand Up @@ -99,6 +100,7 @@ export interface DataTableHeader {
key: string;
header: React.ReactNode;
slug?: React.ReactElement;
decorator?: React.ReactElement;
}

export interface DataTableRenderProps<RowType, ColTypes extends any[]> {
Expand Down Expand Up @@ -203,7 +205,8 @@ export interface DataTableRenderProps<RowType, ColTypes extends any[]> {
};
getCellProps: (getCellPropsArgs: { cell: DataTableCell<ColTypes> }) => {
[key: string]: unknown;
hasSlugHeader?: boolean;
hasAILabelHeader?: boolean;
hasDecoratorHeader?: boolean;
};

// Custom event handlers
Expand Down Expand Up @@ -390,6 +393,7 @@ class DataTable<RowType, ColTypes extends any[]> extends React.Component<
static TableBody: typeof TableBody;
static TableCell: typeof TableCell;
static TableContainer: typeof TableContainer;
static TableDecoratorRow: typeof TableDecoratorRow;
static TableExpandHeader: typeof TableExpandHeader;
static TableExpandRow: typeof TableExpandRow;
static TableExpandedRow: typeof TableExpandedRow;
Expand Down Expand Up @@ -473,6 +477,7 @@ class DataTable<RowType, ColTypes extends any[]> extends React.Component<
isSortable,
isSortHeader: sortHeaderKey === header.key,
slug: header.slug,
decorator: header.decorator,
onClick: (event) => {
const nextSortState = getNextSortState(this.props, this.state, {
key: header.key,
Expand Down Expand Up @@ -747,7 +752,8 @@ class DataTable<RowType, ColTypes extends any[]> extends React.Component<
getCellProps = ({ cell, ...rest }) => {
return {
...rest,
hasSlugHeader: cell.hasSlugHeader,
hasAILabelHeader: cell.hasAILabelHeader,
hasDecoratorHeader: cell.hasDecoratorHeader,
};
};

Expand Down Expand Up @@ -1035,6 +1041,7 @@ DataTable.TableBatchActions = TableBatchActions;
DataTable.TableBody = TableBody;
DataTable.TableCell = TableCell;
DataTable.TableContainer = TableContainer;
DataTable.TableDecoratorRow = TableDecoratorRow;
DataTable.TableExpandHeader = TableExpandHeader;
DataTable.TableExpandRow = TableExpandRow;
DataTable.TableExpandedRow = TableExpandedRow;
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/components/DataTable/TableCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface TableCellProps extends ReactAttr<HTMLTableCellElement> {
/**
* Specify if the table cell is in an AI column
*/
hasSlugHeader?: boolean;
hasAILabelHeader?: boolean;

/**
* 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
Expand All @@ -38,11 +38,11 @@ interface TableCellProps extends ReactAttr<HTMLTableCellElement> {
}

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

const tableCellClassNames = classNames(className, {
[`${prefix}--table-cell--column-slug`]: hasSlugHeader,
[`${prefix}--table-cell--column-slug`]: hasAILabelHeader,
});
return (
<td
Expand Down
68 changes: 68 additions & 0 deletions packages/react/src/components/DataTable/TableDecoratorRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import PropTypes from 'prop-types';
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import { usePrefix } from '../../internal/usePrefix';
import deprecate from '../../prop-types/deprecate';

export interface TableDecoratorRowProps {
/**
* The CSS class names of the cell that wraps the underlying input control
*/
className?: string;

/**
* **Experimental**: Provide a `decorator` component to be rendered inside the `TableDecoratorRow` component
*/
decorator?: ReactNode;
}

const TableDecoratorRow = ({
className,
decorator,
}: TableDecoratorRowProps) => {
const prefix = usePrefix();
const TableDecoratorRowClasses = classNames({
...(className && { [className]: true }),
[`${prefix}--table-column-decorator`]: true,
[`${prefix}--table-column-decorator--active`]: decorator,
});

let normalizedDecorator = React.isValidElement(decorator)
? (decorator as ReactNode)
: null;
if (
normalizedDecorator &&
normalizedDecorator['type']?.displayName === 'AILabel'
) {
normalizedDecorator = React.cloneElement(
normalizedDecorator as React.ReactElement<any>,
{
size: 'mini',
}
);
}

return <td className={TableDecoratorRowClasses}>{normalizedDecorator}</td>;
};

TableDecoratorRow.displayName = 'TableDecoratorRow';
TableDecoratorRow.propTypes = {
/**
* The CSS class names of the cell that wraps the underlying input control
*/
className: PropTypes.string,

/**
* **Experimental**: Provide a `decorator` component to be rendered inside the `TableDecoratorRow` component
*/
decorator: PropTypes.node,
};

export default TableDecoratorRow;
28 changes: 19 additions & 9 deletions packages/react/src/components/DataTable/TableExpandRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,18 @@ const TableExpandRow = React.forwardRef(
) => {
const prefix = usePrefix();

// We need to put the slug before the expansion arrow and all other table cells after the arrow.
let rowHasSlug;
const slug = React.Children.toArray(children).map((child: any) => {
if (child.type?.displayName === 'TableSlugRow') {
if (child.props.slug) {
rowHasSlug = true;
// We need to put the AILabel and Decorator before the expansion arrow and all other table cells after the arrow.
let rowHasAILabel;
const decorator = React.Children.toArray(children).map((child: any) => {
if (
child.type?.displayName === 'TableSlugRow' ||
child.type?.displayName === 'TableDecoratorRow'
) {
if (
child.props.slug ||
child.props.decorator?.type.displayName === 'AILabel'
) {
rowHasAILabel = true;
}

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

const normalizedChildren = React.Children.toArray(children).map(
(child: any) => {
if (child.type?.displayName !== 'TableSlugRow') {
if (
child.type?.displayName !== 'TableSlugRow' &&
child.type?.displayName !== 'TableDecoratorRow'
) {
return child;
}
}
Expand All @@ -98,15 +107,16 @@ const TableExpandRow = React.forwardRef(
[`${prefix}--parent-row`]: true,
[`${prefix}--expandable-row`]: isExpanded,
[`${prefix}--data-table--selected`]: isSelected,
[`${prefix}--data-table--slug-row`]: rowHasSlug,
[`${prefix}--data-table--slug-row ${prefix}--data-table--ai-label-row`]:
rowHasAILabel,
},
rowClassName
);
const previousValue = isExpanded ? 'collapsed' : undefined;

return (
<tr {...rest} ref={ref as never} className={className} data-parent-row>
{slug}
{decorator}
<TableCell
className={`${prefix}--table-expand`}
data-previous-value={previousValue}
Expand Down
57 changes: 43 additions & 14 deletions packages/react/src/components/DataTable/TableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,16 @@ interface TableHeaderProps
scope?: string;

/**
* **Experimental**: Provide a `Slug` component to be rendered inside the `TableSlugRow` component
* @deprecated please use decorator instead.
* Provide a `Slug` component to be rendered inside the `TableSlugRow` component
*/
slug?: ReactNode;

/**
* **Experimental**: Provide a `decorator` component to be rendered inside the `TableDecoratorRow` component
*/
decorator?: ReactNode;

/**
* Specify which direction we are currently sorting by, should be one of DESC,
* NONE, or ASC.
Expand All @@ -130,6 +136,7 @@ const TableHeader = React.forwardRef(function TableHeader(
className: headerClassName,
children,
colSpan,
decorator,
isSortable = false,
isSortHeader,
onClick,
Expand All @@ -145,19 +152,32 @@ const TableHeader = React.forwardRef(function TableHeader(
const prefix = usePrefix();
const uniqueId = useId('table-sort');

// Slug is always size `mini`
const slugRef = useRef<HTMLInputElement>(null);
let normalizedSlug;
if (slug) {
normalizedSlug = React.cloneElement(slug as React.ReactElement<any>, {
size: 'mini',
ref: slugRef,
});
// AILabel is always size `mini`
const AILableRef = useRef<HTMLInputElement>(null);

let colHasAILabel;
let normalizedDecorator = React.isValidElement(slug ?? decorator)
? (slug ?? decorator)
: null;
if (
normalizedDecorator &&
normalizedDecorator['type']?.displayName === 'AILabel'
) {
colHasAILabel = true;
normalizedDecorator = React.cloneElement(
normalizedDecorator as React.ReactElement<any>,
{
size: 'mini',
ref: AILableRef,
}
);
}

const headerLabelClassNames = classNames({
[`${prefix}--table-header-label`]: true,
[`${prefix}--table-header-label--slug`]: slug,
[`${prefix}--table-header-label--slug ${prefix}--table-header-label--ai-label`]:
colHasAILabel,
[`${prefix}--table-header-label--decorator`]: decorator,
});

if (!isSortable) {
Expand All @@ -172,7 +192,9 @@ const TableHeader = React.forwardRef(function TableHeader(
{children ? (
<div className={headerLabelClassNames}>
{children}
{normalizedSlug}
<div className={`${prefix}--table-header-label--decorator-inner`}>
{normalizedDecorator}
</div>
</div>
) : null}
</th>
Expand All @@ -198,11 +220,16 @@ const TableHeader = React.forwardRef(function TableHeader(
});

const headerClasses = cx(headerClassName, `${prefix}--table-sort__header`, {
[`${prefix}--table-sort__header--slug`]: slug,
[`${prefix}--table-sort__header--ai-label`]: colHasAILabel,
[`${prefix}--table-sort__header--decorator`]: decorator,
});

const handleClick = (evt) => {
if (slug && slugRef.current && slugRef.current.contains(evt.target)) {
if (
colHasAILabel &&
AILableRef.current &&
AILableRef.current.contains(evt.target)
) {
return;
} else if (onClick) {
return onClick(evt);
Expand Down Expand Up @@ -233,7 +260,9 @@ const TableHeader = React.forwardRef(function TableHeader(
size={20}
className={`${prefix}--table-sort__icon-unsorted`}
/>
{normalizedSlug}
<div className={`${prefix}--table-header-label--decorator-inner`}>
{normalizedDecorator}
</div>
</span>
</button>
</th>
Expand Down
17 changes: 12 additions & 5 deletions packages/react/src/components/DataTable/TableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@ export interface TableRowProps extends ReactAttr<HTMLTableRowElement> {
const TableRow = (props: TableRowProps) => {
const prefix = usePrefix();

let rowHasSlug;
let rowHasAILabel;
if (props?.children) {
React.Children.toArray(props.children).map((child: any) => {
if (child.type?.displayName === 'TableSlugRow') {
if (child.props.slug) {
rowHasSlug = true;
if (
child.type?.displayName === 'TableSlugRow' ||
child.type?.displayName === 'TableDecoratorRow'
) {
if (
child.props.slug ||
child.props.decorator?.type.displayName === 'AILabel'
) {
rowHasAILabel = true;
}
}
});
Expand All @@ -39,7 +45,8 @@ const TableRow = (props: TableRowProps) => {
// only useful in `TableExpandRow`
const className = cx(props.className, {
[`${prefix}--data-table--selected`]: props.isSelected,
[`${prefix}--data-table--slug-row`]: rowHasSlug,
[`${prefix}--data-table--slug-row ${prefix}--data-table--ai-label-row`]:
rowHasAILabel,
});

const {
Expand Down
Loading

0 comments on commit 69087c4

Please sign in to comment.