diff --git a/packages/patternfly-4/react-table/src/components/Table/Table.md b/packages/patternfly-4/react-table/src/components/Table/Table.md
index 030c9ddca5a..bbaa4a18f5c 100644
--- a/packages/patternfly-4/react-table/src/components/Table/Table.md
+++ b/packages/patternfly-4/react-table/src/components/Table/Table.md
@@ -13,6 +13,7 @@ import {
TableHeader,
TableBody,
sortable,
+ SortHelpers,
SortByDirection,
headerCol,
TableVariant,
@@ -22,13 +23,16 @@ import {
textCenter,
wrappable,
classNames,
- Visibility
+ Visibility,
+ useSortableRows,
+ useSelectableRows
} from '@patternfly/react-table';
-
import {
CodeBranchIcon,
CodeIcon,
- CubeIcon
+ CubeIcon,
+ CheckIcon,
+ TimesIcon
} from '@patternfly/react-icons';
import DemoSortableTable from './demo/DemoSortableTable';
@@ -41,74 +45,99 @@ import {
Table,
TableHeader,
TableBody,
- sortable,
- SortByDirection,
- headerCol,
- TableVariant,
- expandable,
- cellWidth,
textCenter,
} from '@patternfly/react-table';
-class SimpleTable extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- columns: [
- { title: 'Repositories' },
- 'Branches',
- { title: 'Pull requests' },
- 'Workspaces',
- {
- title: 'Last Commit',
- transforms: [textCenter],
- cellTransforms: [textCenter]
- }
- ],
- rows: [
- {
- cells: ['one', 'two', 'three', 'four', 'five']
- },
- {
- cells: [
- {
- title:
one - 2
,
- props: { title: 'hover title', colSpan: 3 }
- },
- 'four - 2',
- 'five - 2'
- ]
- },
- {
- cells: [
- 'one - 3',
- 'two - 3',
- 'three - 3',
- 'four - 3',
- {
- title: 'five - 3 (not centered)',
- props: { textCenter: false }
- }
- ]
- }
- ]
- };
- }
+function SimpleTable() {
+ const cells = [
+ 'Repositories',
+ 'Branches',
+ 'Pull requests',
+ 'Workspaces',
+ 'Last Commit'
+ ];
+ const rows = [
+ ['Foo', 2, 0, 6, 1533635470],
+ ['Bar', 1, 11, 2, 1564566670],
+ ['Baz', 6, 4, 99, 1565167870],
+ ];
+
+ return (
+
+ );
+}
+```
- render() {
- const { columns, rows } = this.state;
+## Cell/row options
- return (
-
- );
- }
+```js
+import React from 'react';
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ cellWidth,
+ textCenter,
+} from '@patternfly/react-table';
+
+function SimpleCustomTable() {
+ const cells = [
+ // Equivalent to just passing the string
+ { title: 'Repositories' },
+ 'Branches',
+ { title: 'Pull requests' },
+ 'Workspaces',
+ // Will run the `transform` functions on the header, and the
+ // `cellTransforms` on the cells. Transformers are used to inject
+ // extra props to the the cell component. In this case we use the
+ // builtin `textCenter` transformer to instruct the cell to apply
+ // the required stylings to center the cell content.
+ {
+ title: 'Last Commit',
+ transforms: [textCenter],
+ cellTransforms: [textCenter]
+ }
+ ];
+ const rows = [
+ ['Foo', 2, 0, 6, 1533635470],
+ [
+ // Cell content can also be defined as a JSX element.
+ // Extra props can be passed to the cell (the `td` element).
+ {
+ title: Bar 👾
,
+ props: { title: 'hover title', colSpan: 3 }
+ },
+ 2,
+ {
+ title: 1564566670
+ }
+ ],
+ [
+ 'Baz',
+ 6,
+ 4,
+ 99,
+ // In this example, we disable the default cellTransform for this specific cell, setting the `textCenter` prop to `false`.
+ {
+ title: '1565167870 (not centered)',
+ props: { textCenter: false }
+ }
+ ]
+ ];
+
+ return (
+
+ );
}
```
-## Sortable table
+## Cell formatters
```js
import React from 'react';
@@ -116,52 +145,121 @@ import {
Table,
TableHeader,
TableBody,
- sortable,
- SortByDirection,
- headerCol,
- TableVariant,
- expandable,
- cellWidth
+ textCenter
} from '@patternfly/react-table';
+import {
+ CheckIcon,
+ TimesIcon
+} from '@patternfly/react-icons';
-class SortableTable extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- columns: [
- { title: 'Repositories', transforms: [sortable] },
- 'Branches',
- { title: 'Pull requests', transforms: [sortable] },
- 'Workspaces',
- 'Last Commit'
- ],
- rows: [['one', 'two', 'a', 'four', 'five'], ['a', 'two', 'k', 'four', 'five'], ['p', 'two', 'b', 'four', 'five']],
- sortBy: {}
+function CellFormatters() {
+ const rows = [
+ ['Foo', 1533635470, 1],
+ ['Bar', 1564566670, 0],
+ ['Baz', 1565167870, 1],
+ ];
+
+ // A component that displays a CI build status.
+ // *Heads-up* - this component definition should *not* stay inside
+ // the main component function in a real world application; it's
+ // done this way in this example as a work-around against the
+ // limitation of one component per example of the documentation
+ // system in use.
+ const CIStatusIcon = ({ passing }) => {
+ const styles = {
+ color: passing
+ ? 'var(--pf-global--success-color--200)'
+ : 'var(--pf-global--danger-color--100)'
};
- this.onSort = this.onSort.bind(this);
+ const icon = passing ? : ;
+ const text = passing ? 'Passing' : 'Failing';
+ return (
+
+ {icon} {text}
+
+ );
+ };
+
+ // Last commit comes as a unix timestamp (in seconds). We specify
+ // a formatter that convert it to something readable by humans.
+ const lastCommitTimestampToLocalHuman = (value, extraProps) => {
+ return new Date(value * 1000).toLocaleString();
}
- onSort(_event, index, direction) {
- const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0));
- this.setState({
- sortBy: {
- index,
- direction
- },
- rows: direction === SortByDirection.asc ? sortedRows : sortedRows.reverse()
- });
+ // CI Status comes as a boolean value. We specify a formatter that
+ // passes the value to the `CIStatusIcon` we wrote.
+ const statusToIcon = (value, extraProps) => {
+ return ;
}
- render() {
- const { columns, rows, sortBy } = this.state;
+ const cells = [
+ 'Repositories',
+ {
+ title: 'Last Commit',
+ cellFormatters: [lastCommitTimestampToLocalHuman]
+ },
+ {
+ title: 'CI Status',
+ cellFormatters: [statusToIcon],
+ // We apply some transforms to both the header and the cell, to make
+ // it centered.
+ transforms: [textCenter],
+ cellTransforms: [textCenter]
+ }
+ ];
+
+ return (
+
+ );
+}
+```
- return (
-
- );
- }
+## Sortable table
+
+```js
+import React from 'react';
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ sortable,
+ SortHelpers,
+ useSortableRows
+} from '@patternfly/react-table';
+
+function SortableTable () {
+ const rows = [
+ ['Foo', 2, 0, 6, 1533635470],
+ ['Bar', 1, 11, 2, { title: ⏰ 1564566670
, value: 1564566670 }],
+ ['Baz', 6, 4, 99, 1565167870],
+ ['Qux', undefined, 4, undefined, 1565167870],
+ ];
+
+ const sortLastCommit = (a, b, aObj, bObj) => {
+ a = aObj.value || a;
+ b = bObj.value || b;
+ return SortHelpers.numbers(a, b);
+ };
+
+ const cells = [
+ { title: 'Repositories', transforms: [sortable] },
+ { title: 'Branches', transforms: [sortable.numbers] },
+ { title: 'Pull requests', transforms: [sortable.numbers] },
+ { title: 'Workspaces', transforms: [sortable.numbers] },
+ { title: 'Last Commit', transforms: [sortable.custom(sortLastCommit)] }
+ ];
+
+ const [sortedRows, onSort, sortBy] = useSortableRows(rows);
+
+ return (
+
+ );
}
```
@@ -173,66 +271,78 @@ import {
Table,
TableHeader,
TableBody,
- sortable,
- SortByDirection,
headerCol,
- TableVariant,
- expandable,
- cellWidth
+ useSelectableRows
} from '@patternfly/react-table';
-class SelectableTable extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- columns: [
- { title: 'Repositories', cellTransforms: [headerCol()] },
- 'Branches',
- { title: 'Pull requests' },
- 'Workspaces',
- 'Last Commit'
- ],
- rows: [
- {
- cells: ['one', 'two', 'a', 'four', 'five']
- },
- {
- cells: ['a', 'two', 'k', 'four', 'five']
- },
- {
- cells: ['p', 'two', 'b', 'four', 'five']
- }
- ]
- };
- this.onSelect = this.onSelect.bind(this);
- }
+function SelectableTable () {
+ const rows = [
+ ['Foo', 2, 0, 6, 1533635470],
+ ['Bar', 1, 11, 2, 1564566670],
+ ['Baz', 6, 4, 99, 1565167870],
+ ];
+
+ const cells = [
+ { title: 'Repositories', cellTransforms: [headerCol()] },
+ 'Branches',
+ 'Pull requests',
+ 'Workspaces',
+ 'Last Commit',
+ ];
+
+ const [selectedRows, onSelect] = useSelectableRows(rows);
+
+ return (
+
+ );
+}
+```
- onSelect(event, isSelected, rowId) {
- let rows;
- if (rowId === -1) {
- rows = this.state.rows.map(oneRow => {
- oneRow.selected = isSelected;
- return oneRow;
- });
- } else {
- rows = [...this.state.rows];
- rows[rowId].selected = isSelected;
- }
- this.setState({
- rows
- });
- }
+## Sortable and selectable table
- render() {
- const { columns, rows } = this.state;
+```js
+import React from 'react';
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ headerCol,
+ sortable,
+ useSelectableRows
+} from '@patternfly/react-table';
- return (
-
- );
- }
+function SortableAndSelectableTable () {
+ const rows = [
+ ['Foo', 2, 0, 6, 1533635470],
+ ['Bar', 1, 11, 2, 1564566670],
+ ['Baz', 6, 4, 99, 1565167870],
+ ];
+
+ const cells = [
+ { title: 'Repositories', transforms: [sortable], cellTransforms: [headerCol()] },
+ { title: 'Branches', transforms: [sortable.numbers] },
+ { title: 'Pull requests', transforms: [sortable.numbers] },
+ { title: 'Workspaces', transforms: [sortable.numbers] },
+ { title: 'Last Commit', transforms: [sortable.numbers] }
+ ];
+
+ const [sortedRows, onSort, sortBy] = useSortableRows(rows, cells);
+
+ // we need to specify the getRowKey callback to use unique ids to identify the
+ // selected rows. We use the repository name in this example.
+ const [sortedAndSelectedRows, onSelect] = useSelectableRows(sortedRows, {
+ getRowKey: (rowData, rowIndex) => rowData[0]
+ });
+
+ return (
+
+ );
}
```
@@ -298,7 +408,7 @@ class SimpleActionsTable extends React.Component {
render() {
const { columns, rows, actions } = this.state;
return (
-
+
diff --git a/packages/patternfly-4/react-table/src/components/Table/Table.tsx b/packages/patternfly-4/react-table/src/components/Table/Table.tsx
index f22fa20fea9..681c0b5ff05 100644
--- a/packages/patternfly-4/react-table/src/components/Table/Table.tsx
+++ b/packages/patternfly-4/react-table/src/components/Table/Table.tsx
@@ -8,7 +8,7 @@ import { BodyCell } from './BodyCell';
import { HeaderCell } from './HeaderCell';
import { RowWrapper } from './RowWrapper';
import { BodyWrapper } from './BodyWrapper';
-import { calculateColumns } from './utils/headerUtils';
+import { calculateColumns } from './utils';
import { formatterValueType, ColumnType, RowType, RowKeyType, ColumnsType } from './base';
export enum TableGridBreakpoint {
@@ -23,8 +23,8 @@ export enum TableGridBreakpoint {
export enum TableVariant {
compact = 'compact'
}
-
-export type OnSort = (event: React.MouseEvent, columnIndex: number, sortByDirection: SortByDirection, extraData: IExtraColumnData) => void;
+export type OnSortCallback = (aValue: any, bValue: any, aObject: IRow | string, bObject: IRow | string) => number;
+export type OnSort = (event: React.MouseEvent, columnIndex: number, sortByDirection: SortByDirection, extraData: IExtraColumnData, sortCallback: OnSortCallback) => void;
export type OnCollapse = (event: React.MouseEvent, rowIndex: number, isOpen: boolean, rowData: IRowData, extraData: IExtraData) => void;
export type OnExpand = (event: React.MouseEvent, rowIndex: number, colIndex: number, isOpen: boolean, rowData: IRowData, extraData: IExtraData) => void;
export type OnSelect = (event: React.MouseEvent, isSelected: boolean, rowIndex: number, rowData: IRowData, extraData: IExtraData) => void;
@@ -45,6 +45,7 @@ export interface IColumn {
extraParams: {
sortBy?: ISortBy;
onSort?: OnSort;
+ sortCallback?: OnSortCallback;
onCollapse?: OnCollapse;
onExpand?: OnExpand;
onSelect?: OnSelect;
@@ -54,6 +55,7 @@ export interface IColumn {
dropdownPosition?: DropdownPosition;
dropdownDirection?: DropdownDirection;
allRowsSelected?: boolean;
+ firstUserColumnIndex?: number;
};
}
@@ -106,6 +108,8 @@ export interface IDecorator extends React.HTMLProps {
children?: React.ReactNode;
}
+export type ICells = (ICell | string)[];
+
export interface ICell {
title?: string;
transforms?: ((...args: any) => any)[];
@@ -124,6 +128,8 @@ export interface IRowCell {
props?: any;
}
+export type IRows = (((string | number | IRowCell)[]) | IRow)[];
+
export interface IRow extends RowType {
cells?: (React.ReactNode | IRowCell)[];
isOpen?: boolean;
@@ -161,8 +167,8 @@ export interface TableProps {
contentId?: string;
dropdownPosition?: 'right' | 'left';
dropdownDirection?: 'up' | 'down';
- rows: (IRow | string[])[];
- cells: (ICell | string)[];
+ rows: IRows;
+ cells: ICells;
bodyWrapper?: Function;
rowWrapper?: Function;
role?: string;
diff --git a/packages/patternfly-4/react-table/src/components/Table/hooks/index.ts b/packages/patternfly-4/react-table/src/components/Table/hooks/index.ts
new file mode 100644
index 00000000000..cd628f7d4a8
--- /dev/null
+++ b/packages/patternfly-4/react-table/src/components/Table/hooks/index.ts
@@ -0,0 +1,2 @@
+export * from './useSelectableRows';
+export * from './useSortableRows';
diff --git a/packages/patternfly-4/react-table/src/components/Table/hooks/useSelectableRows.ts b/packages/patternfly-4/react-table/src/components/Table/hooks/useSelectableRows.ts
new file mode 100644
index 00000000000..c97f80f815c
--- /dev/null
+++ b/packages/patternfly-4/react-table/src/components/Table/hooks/useSelectableRows.ts
@@ -0,0 +1,84 @@
+import { useCallback, useMemo, useRef, useState } from 'react';
+import { IRowData, IRows, OnSelect } from '../Table';
+
+const defaultOptions = {
+ getRowKey: (rowData: IRowData, rowIndex: number) => rowIndex
+};
+
+/**
+ * Returns the onSelect callback required by the Table component to allow for
+ * selecting rows, and the updated `rows` with the right internal flags to tell
+ * the Table component which rows are selected and which are not.
+ *
+ * @example
+ * const [selectedRows, selectedRows] = useSelectableRows(rows);
+ *
+ * @param rows
+ * @param getRowKey - optional, a function to return an unique key for a row. By
+ * default the row's index is used. For the table to be also sortable while being
+ * selectable, a key that uniquely identify the row among its siblings is required.
+ */
+export function useSelectableRows(rows: IRows, { getRowKey } = defaultOptions) {
+ // Selected rows's keys will be saved in the component's state
+ const [selectedKeys, setSelectedKeys] = useState[]>([]);
+
+ // When selecting/deselecting all lines, or when transitioning from an all rows
+ // selected state, we need to compute the new keys based on the full list of keys
+ // available in the original rows array.
+ // Since that array can be composed of many entries, we cache the value so we don't
+ // pay the cost of the map when the user is not changing the original data.
+ const allKeys = useMemo(() => rows.map((r, idx) => getRowKey(r, idx)), [rows, getRowKey]);
+
+ // Since the Table component will not re-render rows if unchanged (because of the
+ // BodyRow:shouldComponentUpdate method), we need to have a reference to an alway
+ // up to date value of the selected keys in the callback we pass to the selectable
+ // cell. This way we can ensure that that callback will not run against stale state
+ // data.
+ const latestSelectedKeys = useRef(selectedKeys);
+
+ // The callback that should be passed to the Table's onSelect property.
+ // It will update the list of selected keys based on the user action.
+ // Note that the user could be interacting with the select/deselect all button
+ // in the header: that case is identified by the rowIndex value passed as -1
+ // by the Table component.
+ const onSelect = useCallback(
+ (event, isSelected, rowIndex, rowData, extraData) => {
+ const latestIndexes = latestSelectedKeys.current;
+ let updatedIndexes = selectedKeys;
+ // A rowIndex -1 indicates that the user clicked on the select all checkbox.
+ if (rowIndex === -1) {
+ updatedIndexes = isSelected ? allKeys : [];
+ } else {
+ // A specific row has been selected/deselected
+ const rowKey = getRowKey(rowData, rowIndex);
+ updatedIndexes = isSelected
+ ? Array.from(new Set([...latestIndexes, rowKey]))
+ : latestIndexes.filter(index => index !== rowKey);
+ }
+ // Here we make sure that other onSelect callbacks will work against the latest
+ // set of selected keys.
+ latestSelectedKeys.current = updatedIndexes;
+ // We still have to save the selected keys in the state, to trigger a re-render
+ // of the component so that selected rows will actually be displayed as
+ // selected.
+ setSelectedKeys(updatedIndexes);
+ },
+ [setSelectedKeys, latestSelectedKeys, allKeys, getRowKey]
+ );
+
+ const selectedRows = rows.map((row, index) => {
+ const isRowSelected = selectedKeys.includes(getRowKey(row, index));
+ if (Array.isArray(row)) {
+ const updatedRow = [...row] as typeof row;
+ // cast required to work with primitive types
+ (updatedRow as any).selected = isRowSelected;
+ return updatedRow;
+ } else {
+ const updatedRow = {...row};
+ updatedRow.selected = isRowSelected;
+ return updatedRow;
+ }
+ });
+
+ return [selectedRows, onSelect];
+}
diff --git a/packages/patternfly-4/react-table/src/components/Table/hooks/useSortableRows.ts b/packages/patternfly-4/react-table/src/components/Table/hooks/useSortableRows.ts
new file mode 100644
index 00000000000..e1021f40ce2
--- /dev/null
+++ b/packages/patternfly-4/react-table/src/components/Table/hooks/useSortableRows.ts
@@ -0,0 +1,30 @@
+import { useMemo, useState } from 'react';
+import { IExtraColumnData, IRows, OnSort, OnSortCallback, SortByDirection } from '../Table';
+
+export function useSortableRows(rows: IRows) {
+ const [sortBy, setSortBy] = useState<
+ | { index: number; direction: SortByDirection; columnData: IExtraColumnData; sortCallback: OnSortCallback }
+ | undefined
+ >();
+
+ const onSort: OnSort = (_event, index, direction, columnData, sortCallback) => {
+ setSortBy({
+ index,
+ direction,
+ columnData,
+ sortCallback
+ });
+ };
+
+ const sortCb = (rowA: any, rowB: any) => {
+ const [a, b] =
+ sortBy.direction === 'desc' ? [rowA[sortBy.index], rowB[sortBy.index]] : [rowB[sortBy.index], rowA[sortBy.index]];
+ const aValue = typeof a === 'object' && a.title ? a.title : a;
+ const bValue = typeof b === 'object' && b.title ? b.title : b;
+ return sortBy.sortCallback(aValue, bValue, a, b);
+ };
+
+ const sortedRows = useMemo(() => (!sortBy ? rows : rows.sort(sortCb)), [sortBy, rows, sortCb]);
+
+ return [sortedRows, onSort, sortBy];
+}
diff --git a/packages/patternfly-4/react-table/src/components/Table/index.ts b/packages/patternfly-4/react-table/src/components/Table/index.ts
index e8197c40dce..4f6069fe9b4 100644
--- a/packages/patternfly-4/react-table/src/components/Table/index.ts
+++ b/packages/patternfly-4/react-table/src/components/Table/index.ts
@@ -11,3 +11,4 @@ export * from './RowWrapper';
export * from './SelectColumn';
export * from './SortColumn';
export * from './utils';
+export * from './hooks';
diff --git a/packages/patternfly-4/react-table/src/components/Table/utils/decorators/sortable.tsx b/packages/patternfly-4/react-table/src/components/Table/utils/decorators/sortable.tsx
index abcc2f7ad4b..f08a36eaea7 100644
--- a/packages/patternfly-4/react-table/src/components/Table/utils/decorators/sortable.tsx
+++ b/packages/patternfly-4/react-table/src/components/Table/utils/decorators/sortable.tsx
@@ -2,20 +2,25 @@ import * as React from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Table/table';
import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
-import { SortByDirection, IExtra, IFormatterValueType } from '../../Table';
+import { SortByDirection, IExtra, IFormatterValueType, OnSortCallback } from '../../Table';
import { SortColumn } from '../../SortColumn';
-export const sortable = (label: IFormatterValueType, { columnIndex, column, property }: IExtra) => {
+const sortableFn = (sortCallback: OnSortCallback, label: IFormatterValueType, { columnIndex, column, property }: IExtra) => {
const {
- extraParams: { sortBy, onSort }
+ extraParams: { sortBy, onSort, firstUserColumnIndex = 0 }
} = column;
+
+ // correct the column index based on the presence of extra columns added on the
+ // left of the user provided ones
+ const correctedColumnIndex = columnIndex - firstUserColumnIndex;
+
const extraData = {
- columnIndex,
+ columnIndex: correctedColumnIndex,
column,
property
};
- const isSortedBy = sortBy && columnIndex === sortBy.index;
+ const isSortedBy = sortBy && correctedColumnIndex === sortBy.index;
function sortClicked(event: React.MouseEvent) {
let reversedDirection;
if (!isSortedBy) {
@@ -24,7 +29,7 @@ export const sortable = (label: IFormatterValueType, { columnIndex, column, prop
reversedDirection = sortBy.direction === SortByDirection.asc ? SortByDirection.desc : SortByDirection.asc;
}
// tslint:disable-next-line:no-unused-expression
- onSort && onSort(event, columnIndex, reversedDirection, extraData);
+ onSort && onSort(event, correctedColumnIndex, reversedDirection, extraData, sortCallback);
}
return {
@@ -42,3 +47,36 @@ export const sortable = (label: IFormatterValueType, { columnIndex, column, prop
)
};
};
+
+const SortHelpers = {
+ numbers(a: number, b: number) {
+ if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ }
+ return 0;
+ },
+
+ booleans(a: boolean, b: boolean) {
+ const toNumber = (v: boolean) => (v ? 1 : 0);
+ return SortHelpers.numbers(toNumber(a), toNumber(b));
+ },
+
+ strings(a: string, b: string) {
+ return a.localeCompare(b);
+ }
+};
+
+const partialOnSort = (fn: OnSortCallback) => sortableFn.bind(null, fn);
+const defaultSortable = partialOnSort(SortHelpers.strings);
+const sortableFunctions = {
+ custom: partialOnSort,
+ numbers: partialOnSort(SortHelpers.numbers),
+ booleans: partialOnSort(SortHelpers.booleans),
+ strings: partialOnSort(SortHelpers.strings),
+};
+
+const sortable = Object.assign(defaultSortable, sortableFunctions);
+
+export { sortable, SortHelpers };
diff --git a/packages/patternfly-4/react-table/src/components/Table/utils/transformers.tsx b/packages/patternfly-4/react-table/src/components/Table/utils/transformers.tsx
index 3e1c2ba28de..a5b12cc5612 100644
--- a/packages/patternfly-4/react-table/src/components/Table/utils/transformers.tsx
+++ b/packages/patternfly-4/react-table/src/components/Table/utils/transformers.tsx
@@ -1,5 +1,5 @@
export { selectable } from './decorators/selectable';
-export { sortable } from './decorators/sortable';
+export { sortable, SortHelpers } from './decorators/sortable';
export { cellActions } from './decorators/cellActions';
export { cellWidth } from './decorators/cellWidth';
export { wrappable } from './decorators/wrappable';