/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. * * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import React, { Component, Fragment, HTMLAttributes, ReactNode, ReactElement, } from 'react'; import classNames from 'classnames'; import moment from 'moment'; import { Direction, formatAuto, formatBoolean, formatDate, formatNumber, formatText, LEFT_ALIGNMENT, RIGHT_ALIGNMENT, SortDirection, } from '../../services'; import { CommonProps } from '../common'; import { isFunction } from '../../services/predicate'; import { get } from '../../services/objects'; import { OuiFlexGroup, OuiFlexItem } from '../flex'; import { OuiCheckbox } from '../form'; import { OuiTable, OuiTableProps, OuiTableBody, OuiTableFooter, OuiTableFooterCell, OuiTableHeader, OuiTableHeaderCell, OuiTableHeaderCellCheckbox, OuiTableHeaderMobile, OuiTableRow, OuiTableRowCell, OuiTableRowCellCheckbox, OuiTableSortMobile, } from '../table'; import { CollapsedItemActions } from './collapsed_item_actions'; import { ExpandedItemActions } from './expanded_item_actions'; import { Pagination, PaginationBar } from './pagination_bar'; import { OuiIcon } from '../icon'; import { OuiKeyboardAccessible, OuiScreenReaderOnly } from '../accessibility'; import { OuiI18n } from '../i18n'; import { OuiDelayRender } from '../delay_render'; import { htmlIdGenerator } from '../../services/accessibility'; import { Action } from './action_types'; import { OuiTableActionsColumnType, OuiTableComputedColumnType, OuiTableDataType, OuiTableFieldDataColumnType, OuiTableFooterProps, ItemId, OuiTableSelectionType, OuiTableSortingType, ItemIdResolved, } from './table_types'; import { OuiTableSortMobileProps } from '../table/mobile/table_sort_mobile'; type DataTypeProfiles = Record< OuiTableDataType, { align: typeof LEFT_ALIGNMENT | typeof RIGHT_ALIGNMENT; render: (value: any) => string; } >; const dataTypesProfiles: DataTypeProfiles = { auto: { align: LEFT_ALIGNMENT, render: (value: any) => formatAuto(value), }, string: { align: LEFT_ALIGNMENT, render: (value: any) => formatText(value), }, number: { align: RIGHT_ALIGNMENT, render: (value: number | null) => formatNumber(value), }, boolean: { align: LEFT_ALIGNMENT, render: (value: boolean) => formatBoolean(value), }, date: { align: LEFT_ALIGNMENT, render: (value: moment.MomentInput) => formatDate(value), }, }; const DATA_TYPES = Object.keys(dataTypesProfiles); interface ItemIdToExpandedRowMap { [id: string]: ReactNode; } export function getItemId(item: T, itemId?: ItemId) { if (itemId) { if (isFunction(itemId)) { return itemId(item); } // @ts-ignore never mind about the index signature return item[itemId]; } } function getRowProps(item: T, rowProps: RowPropsCallback) { if (rowProps) { if (isFunction(rowProps)) { return rowProps(item); } return rowProps; } return {}; } function getCellProps( item: T, column: OuiBasicTableColumn, cellProps: CellPropsCallback ) { if (cellProps) { if (isFunction(cellProps)) { return cellProps(item, column); } return cellProps; } return {}; } function getColumnFooter( column: OuiBasicTableColumn, { items, pagination }: OuiTableFooterProps ) { const { footer } = column as OuiTableFieldDataColumnType; if (footer) { if (isFunction(footer)) { return footer({ items, pagination }); } return footer; } return undefined; } export type OuiBasicTableColumn = | OuiTableFieldDataColumnType | OuiTableComputedColumnType | OuiTableActionsColumnType; export interface Criteria { /** * If the shown items represents a page (slice) into a bigger set, this describes this page */ page?: { index: number; size: number; }; /** * If the shown items are sorted, this describes the sort criteria */ sort?: { field: keyof T; direction: Direction; }; } export interface CriteriaWithPagination extends Criteria { /** * If the shown items represents a page (slice) into a bigger set, this describes this page */ page: { index: number; size: number; }; } type CellPropsCallback = (item: T, column: OuiBasicTableColumn) => object; type RowPropsCallback = (item: T) => object; interface BasicTableProps extends Omit { /** * Describes how to extract a unique ID from each item, used for selections & expanded rows */ itemId?: ItemId; /** * Row expansion uses the itemId prop to identify each row */ itemIdToExpandedRowMap?: ItemIdToExpandedRowMap; /** * A list of objects to who in the table - an item per row */ items: T[]; /** * Applied to `OuiTableRowCell` */ cellProps?: object | CellPropsCallback; /** * An array of one of the objects: #OuiTableFieldDataColumnType, #OuiTableComputedColumnType or #OuiTableActionsColumnType. */ columns: Array>; /** * Error message to display */ error?: string; /** * Describes the content of the table. If not specified, the caption will be "This table contains {itemCount} rows." */ tableCaption?: string; /** * Indicates which column should be used as the identifying cell in each row. Should match a "field" prop in FieldDataColumn */ rowHeader?: string; hasActions?: boolean; isExpandable?: boolean; isSelectable?: boolean; /** * Provides an infinite loading indicator */ loading?: boolean; /** * Message to display if table is empty */ noItemsMessage?: ReactNode; /** * Called whenever pagination or sorting changes (this property is required when either pagination or sorting is configured). See #Criteria or #CriteriaWithPagination */ onChange?: (criteria: Criteria) => void; /** * Configures #Pagination */ pagination?: undefined; /** * If true, will convert table to cards in mobile view */ responsive?: boolean; /** * Applied to `OuiTableRow` */ rowProps?: object | RowPropsCallback; /** * Configures #OuiTableSelectionType */ selection?: OuiTableSelectionType; /** * Configures #OuiTableSortingType */ sorting?: OuiTableSortingType; /** * Sets the table-layout CSS property. Note that auto tableLayout prevents truncateText from working properly. */ tableLayout?: 'fixed' | 'auto'; /** * Applied to table cells => Any cell using render function will set this to be false, leading to unnecessary word breaks. Apply textOnly: true in order to ensure it breaks properly */ textOnly?: boolean; } type BasicTableWithPaginationProps = Omit< BasicTableProps, 'pagination' | 'onChange' > & { pagination: Pagination; onChange?: (criteria: CriteriaWithPagination) => void; }; export type OuiBasicTableProps = CommonProps & Omit, 'onChange'> & (BasicTableProps | BasicTableWithPaginationProps); interface State { initialSelectionRendered: boolean; selection: T[]; } interface SortOptions { isSorted?: boolean; isSortAscending?: boolean; onSort?: () => void; allowNeutralSort?: boolean; readOnly?: boolean; } function hasPagination( x: OuiBasicTableProps ): x is BasicTableWithPaginationProps { return x.hasOwnProperty('pagination') && !!x.pagination; } export class OuiBasicTable extends Component< OuiBasicTableProps, State > { static defaultProps = { responsive: true, tableLayout: 'fixed', noItemsMessage: 'No items found', }; static getDerivedStateFromProps( nextProps: OuiBasicTableProps, prevState: State ) { if (!nextProps.selection) { // next props doesn't have a selection, reset our state return { selection: [] }; } const { itemId } = nextProps; const selection = prevState.selection.filter( (selectedItem: T) => nextProps.items.findIndex( (item: T) => getItemId(item, itemId) === getItemId(selectedItem, itemId) ) !== -1 ); if (selection.length !== prevState.selection.length) { if (nextProps.selection.onSelectionChange) { nextProps.selection.onSelectionChange(selection); } return { selection }; } return null; } // used for moving in & out of `loading` state private cleanups: Array<() => void> = []; private tbody: HTMLTableSectionElement | null = null; constructor(props: OuiBasicTableProps) { super(props); this.state = { // used for checking if initial selection is rendered initialSelectionRendered: false, selection: [], }; } componentDidMount() { if (this.props.loading && this.tbody) this.addLoadingListeners(this.tbody); this.getInitialSelection(); } componentDidUpdate(prevProps: OuiBasicTableProps) { if (prevProps.loading !== this.props.loading) { if (this.props.loading && this.tbody) { this.addLoadingListeners(this.tbody); } else { this.removeLoadingListeners(); } } this.getInitialSelection(); } componentWillUnmount() { this.removeLoadingListeners(); } getInitialSelection() { if ( this.props.selection && this.props.selection.initialSelected && !this.state.initialSelectionRendered && this.props.items.length > 0 ) { this.setState({ selection: this.props.selection.initialSelected }); this.setState({ initialSelectionRendered: true }); } } setSelection(newSelection: T[]) { this.changeSelection(newSelection); } private setTbody = (tbody: HTMLTableSectionElement | null) => { // remove listeners from an existing element this.removeLoadingListeners(); // update the ref this.tbody = tbody; // if loading, add listeners if (this.props.loading === true && tbody) { this.addLoadingListeners(tbody); } }; private addLoadingListeners = (tbody: HTMLTableSectionElement) => { const listener = (event: Event) => { event.stopPropagation(); event.preventDefault(); }; [ 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'click', 'dblclick', 'keydown', 'keyup', 'keypress', ].forEach((event) => { tbody.addEventListener(event, listener, true); this.cleanups.push(() => { tbody.removeEventListener(event, listener, true); }); }); }; private removeLoadingListeners = () => { this.cleanups.forEach((cleanup) => cleanup()); this.cleanups.length = 0; }; buildCriteria(props: OuiBasicTableProps): Criteria { const criteria: Criteria = {}; if (hasPagination(props)) { criteria.page = { index: props.pagination.pageIndex, size: props.pagination.pageSize, }; } if (props.sorting) { criteria.sort = props.sorting.sort; } return criteria; } changeSelection(selection: T[]) { if (!this.props.selection) { return; } this.setState({ selection }); if (this.props.selection.onSelectionChange) { this.props.selection.onSelectionChange(selection); } } clearSelection() { this.changeSelection([]); } onPageSizeChange(size: number) { this.clearSelection(); const currentCriteria = this.buildCriteria(this.props); const criteria: CriteriaWithPagination = { ...currentCriteria, page: { index: 0, // when page size changes, we take the user back to the first page size, }, }; if (this.props.onChange) { this.props.onChange(criteria); } } onPageChange(index: number) { this.clearSelection(); const currentCriteria = this.buildCriteria(this.props); const criteria: CriteriaWithPagination = { ...currentCriteria, page: { ...currentCriteria.page!, index, }, }; if (this.props.onChange) { this.props.onChange(criteria); } } onColumnSortChange(column: OuiBasicTableColumn) { this.clearSelection(); const currentCriteria = this.buildCriteria(this.props); let direction: Direction = SortDirection.ASC; if ( currentCriteria && currentCriteria.sort && (currentCriteria.sort.field === (column as OuiTableFieldDataColumnType).field || currentCriteria.sort.field === column.name) ) { direction = SortDirection.reverse(currentCriteria.sort.direction); } const criteria: Criteria = { ...currentCriteria, // resetting the page if the criteria has one page: !currentCriteria.page ? undefined : { index: 0, size: currentCriteria.page.size, }, sort: { field: ((column as OuiTableFieldDataColumnType).field || column.name) as keyof T, direction, }, }; if (this.props.onChange) { // @ts-ignore complex relationship between pagination's existence and criteria, the code logic ensures this is correctly maintained this.props.onChange(criteria); } } tableId = htmlIdGenerator('__table')(); render() { const { className, loading, items, itemId, columns, pagination, sorting, selection, onChange, error, noItemsMessage, compressed, itemIdToExpandedRowMap, responsive, isSelectable, isExpandable, hasActions, rowProps, cellProps, tableCaption, rowHeader, tableLayout, ...rest } = this.props; const classes = classNames( 'ouiBasicTable', { 'ouiBasicTable-loading': loading, }, className ); const table = this.renderTable(); const paginationBar = this.renderPaginationBar(); return (
{table} {paginationBar}
); } renderTable() { const { compressed, responsive, tableLayout } = this.props; const mobileHeader = responsive ? ( {this.renderSelectAll(true)} {this.renderTableMobileSort()} ) : undefined; const caption = this.renderTableCaption(); const head = this.renderTableHead(); const body = this.renderTableBody(); const footer = this.renderTableFooter(); return (
{mobileHeader} {caption} {head} {body} {footer}
); } renderTableMobileSort() { const { columns, sorting } = this.props; const items: OuiTableSortMobileProps['items'] = []; if (!sorting) { return null; } columns.forEach((column: OuiBasicTableColumn, index: number) => { if ( (column as OuiTableFieldDataColumnType).field && sorting.sort && !!sorting.enableAllColumns && (column as OuiTableFieldDataColumnType).sortable == null ) { column = { ...(column as OuiTableFieldDataColumnType), sortable: true, }; } if ( !(column as OuiTableFieldDataColumnType).sortable || (column as OuiTableFieldDataColumnType).hideForMobile ) { return; } const sortDirection = this.resolveColumnSortDirection(column); items.push({ name: column.name, key: `_data_s_${ (column as OuiTableFieldDataColumnType).field }_${index}`, onSort: this.resolveColumnOnSort(column), isSorted: !!sortDirection, isSortAscending: sortDirection ? SortDirection.isAsc(sortDirection) : undefined, }); }); return items.length ? : null; } renderTableCaption() { const { items, pagination, tableCaption } = this.props; let captionElement; if (tableCaption) { if (pagination) { captionElement = ( ); } else { captionElement = tableCaption; } } else { if (pagination) { if (pagination.totalItemCount > 0) { captionElement = ( ); } else { captionElement = ( ); } } else { captionElement = ( ); } } return ( {captionElement} ); } renderSelectAll = (isMobile: boolean) => { const { items, selection } = this.props; if (!selection) { return; } const selectableItems = items.filter( (item: T) => !selection.selectable || selection.selectable(item) ); const checked = this.state.selection && selectableItems.length > 0 && this.state.selection.length === selectableItems.length; const disabled = selectableItems.length === 0; const onChange = (event: React.ChangeEvent) => { if (event.target.checked) { this.changeSelection(selectableItems); } else { this.changeSelection([]); } }; return ( {(selectAllRows: string) => ( )} ); }; renderTableHead() { const { columns, selection } = this.props; const headers: ReactNode[] = []; if (selection) { headers.push( {this.renderSelectAll(false)} ); } columns.forEach((column: OuiBasicTableColumn, index: number) => { const { field, width, name, align, dataType, sortable, mobileOptions, isMobileHeader, hideForMobile, readOnly, description, } = column as OuiTableFieldDataColumnType; const columnAlign = align || this.getAlignForDataType(dataType); // actions column if ((column as OuiTableActionsColumnType).actions) { headers.push( {name} ); return; } // computed column if (!(column as OuiTableFieldDataColumnType).field) { const sorting: SortOptions = {}; // computed columns are only sortable if their `sortable` is a function if (this.props.sorting && typeof sortable === 'function') { const sortDirection = this.resolveColumnSortDirection(column); sorting.isSorted = !!sortDirection; sorting.isSortAscending = sortDirection ? SortDirection.isAsc(sortDirection) : undefined; sorting.onSort = this.resolveColumnOnSort(column); sorting.readOnly = this.props.sorting.readOnly || readOnly; } headers.push( {name} ); return; } // field data column const sorting: SortOptions = {}; if (this.props.sorting) { if ( this.props.sorting.sort && !!this.props.sorting.enableAllColumns && (column as OuiTableFieldDataColumnType).sortable == null ) { column = { ...(column as OuiTableFieldDataColumnType), sortable: true, }; } const { sortable } = column as OuiTableFieldDataColumnType; if (sortable) { const sortDirection = this.resolveColumnSortDirection(column); sorting.isSorted = !!sortDirection; sorting.isSortAscending = sortDirection ? SortDirection.isAsc(sortDirection) : undefined; sorting.onSort = this.resolveColumnOnSort(column); sorting.readOnly = this.props.sorting.readOnly || readOnly; } } headers.push( {name} ); }); return {headers}; } renderTableFooter() { const { items, columns, pagination, selection } = this.props; const footers = []; let hasDefinedFooter = false; if (selection) { // Create an empty cell to compensate for additional selection column footers.push( {undefined} ); } columns.forEach((column: OuiBasicTableColumn) => { const footer = getColumnFooter(column, { items, pagination }); const { mobileOptions, isMobileHeader, field, align, } = column as OuiTableFieldDataColumnType; if ((mobileOptions && mobileOptions!.only) || isMobileHeader) { return; // exclude columns that only exist for mobile headers } if (footer) { footers.push( {footer} ); hasDefinedFooter = true; } else { // Footer is undefined, so create an empty cell to preserve layout footers.push( {undefined} ); } }); return footers.length && hasDefinedFooter ? ( {footers} ) : null; } renderTableBody() { if (this.props.error) { return this.renderErrorBody(this.props.error); } const { items } = this.props; if (items.length === 0) { return this.renderEmptyBody(); } const rows = items.map((item: T, index: number) => { // if there's pagination the item's index must be adjusted to the where it is in the whole dataset const tableItemIndex = hasPagination(this.props) ? this.props.pagination.pageIndex * this.props.pagination.pageSize + index : index; return this.renderItemRow(item, tableItemIndex); }); return {rows}; } renderErrorBody(error: string) { const colSpan = this.props.columns.length + (this.props.selection ? 1 : 0); return ( {error} ); } renderEmptyBody() { const { columns, selection, noItemsMessage } = this.props; const colSpan = columns.length + (selection ? 1 : 0); return ( {noItemsMessage} ); } renderItemRow(item: T, rowIndex: number) { const { columns, selection, isSelectable, hasActions, rowHeader, itemIdToExpandedRowMap = {}, isExpandable, } = this.props; const cells = []; const { itemId: itemIdCallback } = this.props; const itemId: ItemIdResolved = getItemId(item, itemIdCallback) != null ? getItemId(item, itemIdCallback) : rowIndex; const selected = !selection ? false : this.state.selection && !!this.state.selection.find( (selectedItem: T) => getItemId(selectedItem, itemIdCallback) === itemId ); let calculatedHasSelection; if (selection) { cells.push(this.renderItemSelectionCell(itemId, item, selected)); calculatedHasSelection = true; } let calculatedHasActions; columns.forEach((column: OuiBasicTableColumn, columnIndex: number) => { if ((column as OuiTableActionsColumnType).actions) { cells.push( this.renderItemActionsCell( itemId, item, column as OuiTableActionsColumnType, columnIndex ) ); calculatedHasActions = true; } else if ((column as OuiTableFieldDataColumnType).field) { const fieldDataColumn = column as OuiTableFieldDataColumnType; cells.push( this.renderItemFieldDataCell( itemId, item, column as OuiTableFieldDataColumnType, columnIndex, fieldDataColumn.field === rowHeader ) ); } else { cells.push( this.renderItemComputedCell( itemId, item, column as OuiTableComputedColumnType, columnIndex ) ); } }); // Occupy full width of table, taking checkbox & mobile only columns into account. let expandedRowColSpan = selection ? columns.length + 1 : columns.length; const mobileOnlyCols = columns.reduce((num, column) => { if ( (column as OuiTableFieldDataColumnType).mobileOptions && (column as OuiTableFieldDataColumnType).mobileOptions!.only ) { return num + 1; } return (column as OuiTableFieldDataColumnType).isMobileHeader ? num + 1 : num + 0; // BWC only }, 0); expandedRowColSpan = expandedRowColSpan - mobileOnlyCols; // We'll use the ID to associate the expanded row with the original. const hasExpandedRow = itemIdToExpandedRowMap.hasOwnProperty(itemId); const expandedRowId = hasExpandedRow ? `row_${itemId}_expansion` : undefined; const expandedRow = hasExpandedRow ? ( {itemIdToExpandedRowMap[itemId]} ) : undefined; const { rowProps: rowPropsCallback } = this.props; const rowProps = getRowProps(item, rowPropsCallback as RowPropsCallback); const row = ( {cells} ); return ( {(rowProps as any).onClick ? ( {row} ) : ( row )} {expandedRow} ); } renderItemSelectionCell(itemId: ItemId, item: T, selected: boolean) { const { selection } = this.props; const key = `_selection_column_${itemId}`; const checked = selected; const disabled = selection!.selectable && !selection!.selectable(item); const title = selection!.selectableMessage && selection!.selectableMessage(!disabled, item); const onChange = (event: React.ChangeEvent) => { if (event.target.checked) { this.changeSelection([...this.state.selection, item]); } else { const { itemId: itemIdCallback } = this.props; this.changeSelection( this.state.selection.reduce((selection: T[], selectedItem: T) => { if (getItemId(selectedItem, itemIdCallback) !== itemId) { selection.push(selectedItem); } return selection; }, []) ); } }; return ( {(selectThisRow: string) => ( )} ); } renderItemActionsCell( itemId: ItemIdResolved, item: T, column: OuiTableActionsColumnType, columnIndex: number ) { const actionEnabled = (action: Action) => this.state.selection.length === 0 && (!action.enabled || action.enabled(item)); let actualActions = column.actions.filter( (action: Action) => !action.available || action.available(item) ); if (actualActions.length > 2) { // if any of the actions `isPrimary`, add them inline as well, but only the first 2 const primaryActions = actualActions.filter((o) => o.isPrimary); actualActions = primaryActions.slice(0, 2); // if we have more than 1 action, we don't show them all in the cell, instead we // put them all in a popover tool. This effectively means we can only have a maximum // of one tool per row (it's either and normal action, or it's a popover that shows multiple actions) // // here we create a single custom action that triggers the popover with all the configured actions actualActions.push({ name: 'All actions', render: (item: T) => { return ( ); }, }); } const tools = ( ); const key = `record_actions_${itemId}_${columnIndex}`; return ( {tools} ); } renderItemFieldDataCell( itemId: ItemId, item: T, column: OuiTableFieldDataColumnType, columnIndex: number, setScopeRow: boolean ) { const { field, render, dataType } = column; const key = `_data_column_${field}_${itemId}_${columnIndex}`; const contentRenderer = render || this.getRendererForDataType(dataType); const value = get(item, field as string); const content = contentRenderer(value, item); return this.renderItemCell(item, column, key, content, setScopeRow); } renderItemComputedCell( itemId: ItemId, item: T, column: OuiTableComputedColumnType, columnIndex: number ) { const { render } = column; const key = `_computed_column_${itemId}_${columnIndex}`; const contentRenderer = render || this.getRendererForDataType(); const content = contentRenderer(item); return this.renderItemCell(item, column, key, content, false); } renderItemCell( item: T, column: OuiBasicTableColumn, key: string | number, content: ReactNode, setScopeRow: boolean ) { const { align, render, dataType, isExpander, textOnly, name, field, description, sortable, footer, mobileOptions, ...rest } = column as OuiTableFieldDataColumnType; const columnAlign = align || this.getAlignForDataType(dataType); const { cellProps: cellPropsCallback } = this.props; const cellProps = getCellProps( item, column, cellPropsCallback as CellPropsCallback ); return ( {content} ); } resolveColumnSortDirection = (column: OuiBasicTableColumn) => { const { sorting } = this.props; const { sortable, field, name } = column as OuiTableFieldDataColumnType; if (!sorting || !sorting.sort || !sortable) { return; } if (sorting.sort.field === field || sorting.sort.field === name) { return sorting.sort.direction; } }; resolveColumnOnSort = (column: OuiBasicTableColumn) => { const { sorting } = this.props; const { sortable, name } = column as OuiTableFieldDataColumnType; if (!sorting || !sortable) { return; } if (!this.props.onChange) { throw new Error(`BasicTable is configured to be sortable on column [${name}] but [onChange] is not configured. This callback must be implemented to handle the sort requests`); } return () => this.onColumnSortChange(column); }; getRendererForDataType(dataType: OuiTableDataType = 'auto') { const profile = dataTypesProfiles[dataType]; if (!profile) { throw new Error( `Unknown dataType [${dataType}]. The supported data types are [${DATA_TYPES.join( ', ' )}]` ); } return profile.render; } getAlignForDataType(dataType: OuiTableDataType = 'auto') { const profile = dataTypesProfiles[dataType]; if (!profile) { throw new Error( `Unknown dataType [${dataType}]. The supported data types are [${DATA_TYPES.join( ', ' )}]` ); } return profile.align; } renderPaginationBar() { const { error, pagination, tableCaption, onChange } = this.props; if (!error && pagination && pagination.totalItemCount > 0) { if (!onChange) { throw new Error(`The Basic Table is configured with pagination but [onChange] is not configured. This callback must be implemented to handle pagination changes`); } let ariaLabel: ReactElement | undefined = undefined; if (tableCaption) { ariaLabel = ( ); } return ( ); } } }