diff --git a/.all-contributorsrc b/.all-contributorsrc
index 3e3668a03cd1..872429a9c3cf 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -165,15 +165,15 @@
"a11y"
]
},
- {
- "login": "erifsx",
- "name": "Eric Marcoux",
- "avatar_url": "https://avatars3.githubusercontent.com/u/997572?v=4",
- "profile": "https://github.com/erifsx",
- "contributions": [
- "code"
- ]
- },
+ {
+ "login": "erifsx",
+ "name": "Eric Marcoux",
+ "avatar_url": "https://avatars3.githubusercontent.com/u/997572?v=4",
+ "profile": "https://github.com/erifsx",
+ "contributions": [
+ "code"
+ ]
+ },
{
"login": "vpicone",
"name": "Vince Picone",
@@ -1223,6 +1223,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "djragsdale",
+ "name": "David Ragsdale",
+ "avatar_url": "https://avatars.githubusercontent.com/u/4396766?v=4",
+ "profile": "https://github.com/djragsdale",
+ "contributions": [
+ "code"
+ ]
}
],
"commitConvention": "none"
diff --git a/README.md b/README.md
index 237a14006242..e32c8674b9bf 100644
--- a/README.md
+++ b/README.md
@@ -248,6 +248,9 @@ check out our [Contributing Guide](/.github/CONTRIBUTING.md) and our
Tony ZL 💻 |
Petr Kadlec 💻 |
+
+ David Ragsdale 💻 |
+
diff --git a/packages/react/src/components/DataTable/DataTable.js b/packages/react/src/components/DataTable/DataTable.tsx
similarity index 75%
rename from packages/react/src/components/DataTable/DataTable.js
rename to packages/react/src/components/DataTable/DataTable.tsx
index 104baf46ccd4..b4d40a0e8291 100644
--- a/packages/react/src/components/DataTable/DataTable.js
+++ b/packages/react/src/components/DataTable/DataTable.tsx
@@ -10,6 +10,7 @@ import React from 'react';
import isEqual from 'lodash.isequal';
import getDerivedStateFromProps from './state/getDerivedStateFromProps';
import { getNextSortState } from './state/sorting';
+import type { DataTableSortState } from './state/sortStates';
import { getCellId } from './tools/cells';
import denormalize from './tools/denormalize';
import { composeEventHandlers } from '../../tools/events';
@@ -63,6 +64,185 @@ const defaultTranslations = {
const translateWithId = (id) => defaultTranslations[id];
+const dataTableDefaultProps = {
+ filterRows: defaultFilterRows,
+ locale: 'en',
+ overflowMenuOnHover: true,
+ size: 'lg',
+ sortRow: defaultSortRow,
+ translateWithId,
+};
+
+export type DataTableSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+
+export interface DataTableCell {
+ id: string;
+ value: unknown;
+ isEditable: boolean;
+ isEditing: boolean;
+ isValid: boolean;
+ errors: null | Array;
+ info: {
+ header: string;
+ };
+}
+
+export interface DataTableRow {
+ id: string;
+ cells: Array;
+ disabled?: boolean;
+ isExpanded?: boolean;
+ isSelected?: boolean;
+}
+
+export interface DataTableHeader {
+ key: string;
+ header: React.ReactNode;
+}
+
+export interface DataTableRenderProps {
+ headers: Array;
+ rows: Array;
+ selectedRows: Array;
+
+ // Prop accessors/getters
+ getHeaderProps: (getHeaderPropsArgs: {
+ header: DataTableHeader;
+ isSortable?: boolean;
+ onClick?: (
+ e: MouseEvent,
+ sortState: { sortHeaderKey: string; sortDirection: DataTableSortState }
+ ) => void;
+ [key: string]: unknown;
+ }) => {
+ isSortable: boolean | undefined;
+ isSortHeader: boolean;
+ key: string;
+ onClick: (e: MouseEvent) => void;
+ sortDirection: DataTableSortState;
+ [key: string]: unknown;
+ };
+ getExpandHeaderProps: (getExpandHeaderPropsArgs?: {
+ onClick?: (e: MouseEvent, expandState: { isExpanded?: boolean }) => void;
+ onExpand?: (e: MouseEvent) => void;
+ [key: string]: unknown;
+ }) => {
+ ariaLabel: string;
+ isExpanded: boolean;
+ onExpand: (e: MouseEvent) => void;
+ [key: string]: unknown;
+ };
+ getRowProps: (getRowPropsArgs: {
+ onClick?: (e: MouseEvent) => void;
+ row: DataTableRow;
+ [key: string]: unknown;
+ }) => {
+ ariaLabel: string;
+ disabled: boolean | undefined;
+ isExpanded?: boolean;
+ isSelected?: boolean;
+ key: string;
+ onExpand: (e: MouseEvent) => void;
+ [key: string]: unknown;
+ };
+ getSelectionProps: (getSelectionPropsArgs: {
+ onClick?: (e: MouseEvent) => void;
+ row: DataTableRow;
+ [key: string]: unknown;
+ }) => {
+ ariaLabel: string;
+ checked: boolean | undefined;
+ disabled?: boolean | undefined;
+ id: string;
+ indeterminate?: boolean;
+ name: string;
+ onSelect: (e: MouseEvent) => void;
+ radio?: boolean | null;
+ [key: string]: unknown;
+ };
+ getToolbarProps: (getToolbarPropsArgs?: { [key: string]: unknown }) => {
+ size: 'sm' | undefined;
+ [key: string]: unknown;
+ };
+ getBatchActionProps: (getBatchActionPropsArgs?: {
+ [key: string]: unknown;
+ }) => {
+ onCancel: () => void;
+ shouldShowBatchActions: boolean;
+ totalSelected: number;
+ [key: string]: unknown;
+ };
+ getTableProps: () => {
+ experimentalAutoAlign?: boolean;
+ isSortable?: boolean;
+ overflowMenuOnHover: boolean;
+ size: DataTableSize;
+ stickyHeader?: boolean;
+ useStaticWidth?: boolean;
+ useZebraStyles?: boolean;
+ };
+ getTableContainerProps: () => {
+ stickyHeader?: boolean;
+ useStaticWidth?: boolean;
+ };
+
+ // Custom event handlers
+ onInputChange: (e: Event, defaultValue?: string) => void;
+
+ // Expose internal state change actions
+ sortBy: (headerKey: string) => void;
+ selectAll: () => void;
+ selectRow: (rowId: string) => void;
+ expandRow: (rowId: string) => void;
+ expandAll: () => void;
+ radio: boolean | undefined;
+}
+
+export interface DataTableProps {
+ children?: (renderProps: DataTableRenderProps) => React.ReactElement;
+ experimentalAutoAlign?: boolean;
+ filterRows?: (filterRowsArgs: {
+ cellsById: Record;
+ getCellId: (rowId: string, header: string) => string;
+ headers: Array;
+ inputValue: string;
+ rowIds: Array;
+ }) => Array;
+ headers: Array;
+ isSortable?: boolean;
+ locale?: string;
+ overflowMenuOnHover?: boolean;
+ radio?: boolean;
+ render?: (renderProps: DataTableRenderProps) => React.ReactElement;
+ rows: Array>;
+ size?: DataTableSize;
+ sortRow?: (
+ cellA: DataTableCell,
+ cellB: DataTableCell,
+ sortRowOptions: {
+ sortDirection: DataTableSortState;
+ sortStates: Record;
+ locale: string;
+ }
+ ) => number;
+ stickyHeader?: boolean;
+ translateWithId?: (id: string) => string;
+ useStaticWidth?: boolean;
+ useZebraStyles?: boolean;
+}
+
+interface DataTableState {
+ cellsById: Record;
+ filterInputValue: string | null;
+ initialRowOrder: Array;
+ isExpandedAll: boolean;
+ rowIds: Array;
+ rowsById: Record;
+ shouldShowBatchActions: boolean;
+ sortDirection: DataTableSortState;
+ sortHeaderKey: string | null;
+}
+
/**
* Data Tables are used to represent a collection of resources, displaying a
* subset of their fields in columns, or headers. We prioritize direct updates
@@ -73,7 +253,13 @@ const translateWithId = (id) => defaultTranslations[id];
* and updating the state of the single entity will cascade updates to the
* consumer.
*/
-class DataTable extends React.Component {
+class DataTable extends React.Component<
+ DataTableProps & typeof dataTableDefaultProps,
+ DataTableState
+> {
+ instanceId: number;
+
+ static defaultProps = dataTableDefaultProps;
static propTypes = {
/**
* Experimental property. Allows table to align cell contents to the top if there is text wrapping in the content. Might have performance issues, intended for smaller tables
@@ -168,17 +354,30 @@ class DataTable extends React.Component {
useZebraStyles: PropTypes.bool,
};
- static defaultProps = {
- sortRow: defaultSortRow,
- filterRows: defaultFilterRows,
- locale: 'en',
- size: 'lg',
- overflowMenuOnHover: true,
- translateWithId,
- };
-
static translationKeys = Object.values(translationKeys);
+ // Static properties for sub-components
+ static Table: typeof Table;
+ static TableActionList: typeof TableActionList;
+ static TableBatchAction: typeof TableBatchAction;
+ static TableBatchActions: typeof TableBatchActions;
+ static TableBody: typeof TableBody;
+ static TableCell: typeof TableCell;
+ static TableContainer: typeof TableContainer;
+ static TableExpandHeader: typeof TableExpandHeader;
+ static TableExpandRow: typeof TableExpandRow;
+ static TableExpandedRow: typeof TableExpandedRow;
+ static TableHead: typeof TableHead;
+ static TableHeader: typeof TableHeader;
+ static TableRow: typeof TableRow;
+ static TableSelectAll: typeof TableSelectAll;
+ static TableSelectRow: typeof TableSelectRow;
+ static TableToolbar: typeof TableToolbar;
+ static TableToolbarAction: typeof TableToolbarAction;
+ static TableToolbarContent: typeof TableToolbarContent;
+ static TableToolbarSearch: typeof TableToolbarSearch;
+ static TableToolbarMenu: typeof TableToolbarMenu;
+
constructor(props) {
super(props);
this.state = {
@@ -230,6 +429,14 @@ class DataTable extends React.Component {
onClick,
isSortable = this.props.isSortable,
...rest
+ }: {
+ header: DataTableHeader;
+ onClick?: (
+ e: MouseEvent,
+ sortState: { sortHeaderKey: string; sortDirection: DataTableSortState }
+ ) => void;
+ isSortable?: boolean;
+ [key: string]: unknown;
}) => {
const { sortDirection, sortHeaderKey } = this.state;
return {
@@ -261,7 +468,13 @@ class DataTable extends React.Component {
* @param {Function} config.onExpand a custom click handler called when header is expanded
* @returns {object}
*/
- getExpandHeaderProps = ({ onClick, onExpand, ...rest } = {}) => {
+ getExpandHeaderProps = (
+ { onClick, onExpand, ...rest } = {} as {
+ onClick?: (e: MouseEvent, expandState: { isExpanded: boolean }) => void;
+ onExpand?: (e: MouseEvent) => void;
+ [key: string]: unknown;
+ }
+ ) => {
const { translateWithId: t } = this.props;
const { isExpandedAll, rowIds, rowsById } = this.state;
const isExpanded =
@@ -278,11 +491,10 @@ class DataTable extends React.Component {
onExpand: composeEventHandlers([
this.handleOnExpandAll,
onExpand,
- onClick
- ? this.handleOnExpandHeaderClick(onClick, {
- isExpanded,
- })
- : null,
+ onClick &&
+ this.handleOnExpandHeaderClick(onClick, {
+ isExpanded,
+ }),
]),
};
};
@@ -317,7 +529,15 @@ class DataTable extends React.Component {
* @param {Function} config.onClick a custom click handler for the header
* @returns {object}
*/
- getRowProps = ({ row, onClick, ...rest }) => {
+ getRowProps = ({
+ row,
+ onClick,
+ ...rest
+ }: {
+ onClick?: (e: MouseEvent) => void;
+ row: DataTableRow;
+ [key: string]: unknown;
+ }) => {
const { translateWithId: t } = this.props;
const translationKey = row.isExpanded
? translationKeys.collapseRow
@@ -345,7 +565,13 @@ class DataTable extends React.Component {
* @param {object} row.row
* @returns {object}
*/
- getSelectionProps = ({ onClick, row, ...rest } = {}) => {
+ getSelectionProps = (
+ { onClick, row, ...rest } = {} as {
+ onClick?: (e: MouseEvent) => void;
+ row: DataTableRow;
+ [key: string]: unknown;
+ }
+ ) => {
const { translateWithId: t } = this.props;
// If we're given a row, return the selection state values for that row
@@ -391,9 +617,14 @@ class DataTable extends React.Component {
};
};
- getToolbarProps = (props = {}) => {
+ getToolbarProps = (
+ props = {}
+ ): {
+ size: 'sm' | undefined;
+ [key: string]: unknown;
+ } => {
const { size } = this.props;
- let isSmall = size === 'xs' || size === 'sm';
+ const isSmall = size === 'xs' || size === 'sm';
return {
...props,
size: isSmall ? 'sm' : undefined,
@@ -676,7 +907,7 @@ class DataTable extends React.Component {
getCellId,
})
: rowIds;
- const renderProps = {
+ const renderProps: DataTableRenderProps = {
// Data derived from state
rows: denormalize(filteredRowIds, rowsById, cellsById),
headers: this.props.headers,
diff --git a/packages/react/src/components/DataTable/__tests__/exports-test.js b/packages/react/src/components/DataTable/__tests__/exports-test.js
index 5b6567125dbd..9db710f77936 100644
--- a/packages/react/src/components/DataTable/__tests__/exports-test.js
+++ b/packages/react/src/components/DataTable/__tests__/exports-test.js
@@ -21,7 +21,7 @@ const blocklist = new Set([
'DataTable-story.js',
'__tests__',
'__mocks__',
- 'DataTable.js',
+ 'DataTable.tsx',
'DataTable.mdx',
'stories',
'next',
diff --git a/packages/react/src/components/DataTable/index.ts b/packages/react/src/components/DataTable/index.ts
index 77ae0a9d2ed9..335a1b5126d2 100644
--- a/packages/react/src/components/DataTable/index.ts
+++ b/packages/react/src/components/DataTable/index.ts
@@ -5,7 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
-import DataTable from './DataTable';
+import DataTable, {
+ type DataTableCell,
+ type DataTableHeader,
+ type DataTableRow,
+ type DataTableProps,
+ type DataTableRenderProps,
+ type DataTableSize,
+} from './DataTable';
import Table from './Table';
import TableActionList from './TableActionList';
import TableBatchAction from './TableBatchAction';
@@ -50,6 +57,12 @@ DataTable.TableToolbarMenu = TableToolbarMenu;
export {
DataTable,
+ type DataTableCell,
+ type DataTableHeader,
+ type DataTableProps,
+ type DataTableRenderProps,
+ type DataTableRow,
+ type DataTableSize,
Table,
TableActionList,
TableBatchAction,
diff --git a/packages/react/src/components/DataTable/state/sortStates.js b/packages/react/src/components/DataTable/state/sortStates.ts
similarity index 76%
rename from packages/react/src/components/DataTable/state/sortStates.js
rename to packages/react/src/components/DataTable/state/sortStates.ts
index c82e2fc8d7af..bf2f0ba15d7c 100644
--- a/packages/react/src/components/DataTable/state/sortStates.js
+++ b/packages/react/src/components/DataTable/state/sortStates.ts
@@ -5,12 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
+export type DataTableSortState = 'NONE' | 'DESC' | 'ASC';
+
/**
* We currently support the following sorting states for DataTable headers,
* namely: `NONE` for no sorting being applied, and then `DESC` and `ASC` for
* the corresponding direction of the sorting order.
*/
-export const sortStates = {
+export const sortStates: Record = {
NONE: 'NONE',
DESC: 'DESC',
ASC: 'ASC',