diff --git a/src/DataTable/README.md b/src/DataTable/README.md index fd3eb261acb..0028b2ad478 100644 --- a/src/DataTable/README.md +++ b/src/DataTable/README.md @@ -1569,4 +1569,102 @@ You can create your own cell content by passing the `Cell` property to a specifi ); } + ``` +## maxSelectedRows and onMaxSelectedRows props + +These props will allow us to handle the maximum number of selectable rows, which is necessary for validation. Using the `maxSelectedRows` prop, the implementation process can be simplified. You only need to pass the maximum number of rows you want to have selected. + +After selecting the maximum possible number of rows, you can display an error message or perform other actions. This is achieved through the `onMaxSelectedRows` callback. In the example below, the callback will be executed when the table has 3 rows selected. + +```jsx live +() => { + return ( + console.log('this is the last row allowed')} + itemCount={7} + data={[ + { + name: 'Lil Bub', + color: 'brown tabby', + famous_for: 'weird tongue', + }, + { + name: 'Grumpy Cat', + color: 'siamese', + famous_for: 'serving moods', + }, + { + name: 'Smoothie', + color: 'orange tabby', + famous_for: 'modeling', + }, + { + name: 'Maru', + color: 'brown tabby', + famous_for: 'being a lovable oaf', + }, + { + name: 'Keyboard Cat', + color: 'orange tabby', + famous_for: 'piano virtuoso', + }, + { + name: 'Long Cat', + color: 'russian white', + famous_for: 'being looooooooooooooooooooooooooong', + }, + { + name: 'Zeno', + color: 'brown tabby', + famous_for: 'getting halfway there', + }, + ]} + columns={[ + { + Header: 'Name', + accessor: 'name', + }, + { + Header: 'Famous For', + accessor: 'famous_for', + }, + { + Header: 'Coat Color', + accessor: 'color', + filter: 'includesValue', + filterChoices: [ + { + name: 'russian white', + number: 1, + value: 'russian white', + }, + { + name: 'orange tabby', + number: 2, + value: 'orange tabby', + }, + { + name: 'brown tabby', + number: 3, + value: 'brown tabby', + }, + { + name: 'siamese', + number: 1, + value: 'siamese', + }, + ], + }, + ]} + > + + + + + + ); +} diff --git a/src/DataTable/index.jsx b/src/DataTable/index.jsx index 3024ce7b3df..d28f20e816d 100644 --- a/src/DataTable/index.jsx +++ b/src/DataTable/index.jsx @@ -52,6 +52,8 @@ function DataTable({ isLoading, children, onSelectedRowsChanged, + maxSelectedRows, + onMaxSelectedRows, ...props }) { const defaultColumn = useMemo( @@ -60,7 +62,7 @@ function DataTable({ ); const tableOptions = useMemo(() => { const updatedTableOptions = { - stateReducer: (newState, action) => { + stateReducer: (newState, action, previousState) => { switch (action.type) { // Note: we override the `toggleAllRowsSelected` action // from react-table because it only clears the selections on the @@ -77,6 +79,28 @@ function DataTable({ selectedRowIds: {}, }; } + /* Note: We override the `toggleRowSelected` action from react-table + because we need to preserve the order of the selected rows. + While `selectedRowIds` is an object that contains the selected rows as key-value pairs, + it does not maintain the order of selection. Therefore, we have added the `selectedRowsOrdered` property + to keep track of the order in which the rows were selected. + */ + case 'toggleRowSelected': { + const rowIndex = parseInt(action.id, 10); + const { selectedRowsOrdered = [] } = previousState; + + let newSelectedRowsOrdered; + if (action.value) { + newSelectedRowsOrdered = [...selectedRowsOrdered, rowIndex]; + } else { + newSelectedRowsOrdered = selectedRowsOrdered.filter((item) => item !== rowIndex); + } + + return { + ...newState, + selectedRowsOrdered: newSelectedRowsOrdered, + }; + } default: return newState; } @@ -93,7 +117,7 @@ function DataTable({ initialState, ...updatedTableOptions, }; - }, [columns, data, defaultColumn, manualFilters, manualPagination, initialState, initialTableOptions, manualSortBy]); + }, [initialTableOptions, columns, data, defaultColumn, manualFilters, manualPagination, manualSortBy, initialState]); const [selections, selectionsDispatch] = useReducer(selectionsReducer, initialSelectionsState); @@ -176,6 +200,8 @@ function DataTable({ isSelectable, isPaginated, manualSelectColumn, + maxSelectedRows, + onMaxSelectedRows, ...selectionProps, ...selectionActions, ...props, @@ -236,6 +262,8 @@ DataTable.defaultProps = { isExpandable: false, isLoading: false, onSelectedRowsChanged: undefined, + maxSelectedRows: undefined, + onMaxSelectedRows: undefined, }; DataTable.propTypes = { @@ -308,6 +336,7 @@ DataTable.propTypes = { filters: requiredWhen(PropTypes.arrayOf(PropTypes.shape()), 'manualFilters'), sortBy: requiredWhen(PropTypes.arrayOf(PropTypes.shape()), 'manualSortBy'), selectedRowIds: PropTypes.shape(), + selectedRowsOrdered: PropTypes.arrayOf(PropTypes.number), }), /** Table options passed to react-table's useTable hook. Will override some options passed in to DataTable, such as: data, columns, defaultColumn, manualFilters, manualPagination, manualSortBy, and initialState */ @@ -405,6 +434,10 @@ DataTable.propTypes = { isLoading: PropTypes.bool, /** Callback function called when row selections change. */ onSelectedRowsChanged: PropTypes.func, + /** Indicates the max of rows selectable in the table. Requires isSelectable prop */ + maxSelectedRows: PropTypes.number, + /** Callback after selected max rows. Requires isSelectable and maxSelectedRows props */ + onMaxSelectedRows: PropTypes.func, }; DataTable.BulkActions = BulkActions; diff --git a/src/DataTable/selection/BaseSelectionStatus.jsx b/src/DataTable/selection/BaseSelectionStatus.jsx index e5e1c8c51a9..e4d9d551ea5 100644 --- a/src/DataTable/selection/BaseSelectionStatus.jsx +++ b/src/DataTable/selection/BaseSelectionStatus.jsx @@ -22,10 +22,12 @@ function BaseSelectionStatus({ }) { const { itemCount, filteredRows, isPaginated, state, + isSelectable, maxSelectedRows, } = useContext(DataTableContext); const hasAppliedFilters = state?.filters?.length > 0; const isAllRowsSelected = numSelectedRows === itemCount; const filteredItems = filteredRows?.length || itemCount; + const hasMaxSelectedRows = isSelectable && maxSelectedRows; const intlAllSelectedText = allSelectedText || ( {isAllRowsSelected ? intlAllSelectedText : intlSelectedText} - {!isAllRowsSelected && ( + {!isAllRowsSelected && !hasMaxSelectedRows && (