mirror of
https://github.com/strapi/strapi.git
synced 2025-08-11 18:27:22 +00:00
Merge pull request #16515 from strapi/bulk-publish/actions-bar
[Bulk Publish] Add new publish buttons to the content manager tables
This commit is contained in:
commit
b83c8d9320
@ -0,0 +1,85 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button } from '@strapi/design-system';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useTracking } from '@strapi/helper-plugin';
|
||||||
|
import ConfirmDialogDeleteAll from '../ConfirmDialogDeleteAll';
|
||||||
|
|
||||||
|
const BulkActionsBar = ({
|
||||||
|
showPublish,
|
||||||
|
showDelete,
|
||||||
|
onConfirmDeleteAll,
|
||||||
|
selectedEntries,
|
||||||
|
clearSelectedEntries,
|
||||||
|
}) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { trackUsage } = useTracking();
|
||||||
|
|
||||||
|
const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
|
||||||
|
const [showConfirmDeleteAll, setShowConfirmDeleteAll] = useState(false);
|
||||||
|
|
||||||
|
const handleToggleShowDeleteAllModal = () => {
|
||||||
|
if (!showConfirmDeleteAll) {
|
||||||
|
trackUsage('willBulkDeleteEntries');
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowConfirmDeleteAll((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDeleteAll = async () => {
|
||||||
|
try {
|
||||||
|
setIsConfirmButtonLoading(true);
|
||||||
|
await onConfirmDeleteAll(selectedEntries);
|
||||||
|
handleToggleShowDeleteAllModal();
|
||||||
|
clearSelectedEntries();
|
||||||
|
setIsConfirmButtonLoading(false);
|
||||||
|
} catch (err) {
|
||||||
|
setIsConfirmButtonLoading(false);
|
||||||
|
handleToggleShowDeleteAllModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showPublish && (
|
||||||
|
<>
|
||||||
|
<Button variant="tertiary">
|
||||||
|
{formatMessage({ id: 'app.utils.publish', defaultMessage: 'Publish' })}
|
||||||
|
</Button>
|
||||||
|
<Button variant="tertiary">
|
||||||
|
{formatMessage({ id: 'app.utils.unpublish', defaultMessage: 'Unpublish' })}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{showDelete && (
|
||||||
|
<>
|
||||||
|
<Button variant="danger-light" onClick={handleToggleShowDeleteAllModal}>
|
||||||
|
{formatMessage({ id: 'global.delete', defaultMessage: 'Delete' })}
|
||||||
|
</Button>
|
||||||
|
<ConfirmDialogDeleteAll
|
||||||
|
isOpen={showConfirmDeleteAll}
|
||||||
|
onToggleDialog={handleToggleShowDeleteAllModal}
|
||||||
|
isConfirmButtonLoading={isConfirmButtonLoading}
|
||||||
|
onConfirm={handleConfirmDeleteAll}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
BulkActionsBar.defaultProps = {
|
||||||
|
showPublish: false,
|
||||||
|
showDelete: false,
|
||||||
|
onConfirmDeleteAll() {},
|
||||||
|
};
|
||||||
|
|
||||||
|
BulkActionsBar.propTypes = {
|
||||||
|
showPublish: PropTypes.bool,
|
||||||
|
showDelete: PropTypes.bool,
|
||||||
|
onConfirmDeleteAll: PropTypes.func,
|
||||||
|
selectedEntries: PropTypes.array.isRequired,
|
||||||
|
clearSelectedEntries: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BulkActionsBar;
|
@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import BulkActionsBar from '../index';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
|
useTracking: () => ({
|
||||||
|
trackUsage: jest.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../../../shared/hooks', () => ({
|
||||||
|
...jest.requireActual('../../../../../shared/hooks'),
|
||||||
|
useInjectionZone: () => [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('BulkActionsBar', () => {
|
||||||
|
const requiredProps = {
|
||||||
|
selectedEntries: [],
|
||||||
|
clearSelectedEntries: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestComponent = (props) => (
|
||||||
|
<ThemeProvider theme={lightTheme}>
|
||||||
|
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
||||||
|
<BulkActionsBar {...requiredProps} {...props} />
|
||||||
|
</IntlProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const setup = (props) => render(<TestComponent {...props} />);
|
||||||
|
|
||||||
|
it('should render publish buttons if showPublish is true', () => {
|
||||||
|
setup({ showPublish: true });
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: /\bPublish\b/ })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /\bUnpublish\b/ })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render publish buttons if showPublish is false', () => {
|
||||||
|
setup({ showPublish: false });
|
||||||
|
|
||||||
|
expect(screen.queryByRole('button', { name: /\bPublish\b/ })).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('button', { name: /\bUnpublish\b/ })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render delete button if showDelete is true', () => {
|
||||||
|
setup({ showDelete: true });
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: /\bDelete\b/ })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render delete button if showDelete is false', () => {
|
||||||
|
setup({ showDelete: false });
|
||||||
|
|
||||||
|
expect(screen.queryByRole('button', { name: /\bDelete\b/ })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show delete modal if delete button is clicked', () => {
|
||||||
|
setup({ showDelete: true });
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /\bDelete\b/ }));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('Confirmation')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call confirm delete all if confirmation button is clicked', async () => {
|
||||||
|
const mockConfirmDeleteAll = jest.fn();
|
||||||
|
|
||||||
|
setup({
|
||||||
|
showDelete: true,
|
||||||
|
onConfirmDeleteAll: mockConfirmDeleteAll,
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await fireEvent.click(screen.getByRole('button', { name: /\bDelete\b/ }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /confirm/i }));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockConfirmDeleteAll).toHaveBeenCalledWith([]);
|
||||||
|
});
|
||||||
|
});
|
@ -9,13 +9,14 @@ import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
|
|||||||
import { selectDisplayedHeaders } from '../../pages/ListView/selectors';
|
import { selectDisplayedHeaders } from '../../pages/ListView/selectors';
|
||||||
import { getTrad } from '../../utils';
|
import { getTrad } from '../../utils';
|
||||||
import TableRows from './TableRows';
|
import TableRows from './TableRows';
|
||||||
import ConfirmDialogDeleteAll from './ConfirmDialogDeleteAll';
|
|
||||||
import ConfirmDialogDelete from './ConfirmDialogDelete';
|
import ConfirmDialogDelete from './ConfirmDialogDelete';
|
||||||
import { PublicationState } from './CellContent/PublicationState/PublicationState';
|
import { PublicationState } from './CellContent/PublicationState/PublicationState';
|
||||||
|
import BulkActionsBar from './BulkActionsBar';
|
||||||
|
|
||||||
const DynamicTable = ({
|
const DynamicTable = ({
|
||||||
canCreate,
|
canCreate,
|
||||||
canDelete,
|
canDelete,
|
||||||
|
canPublish,
|
||||||
contentTypeName,
|
contentTypeName,
|
||||||
action,
|
action,
|
||||||
isBulkable,
|
isBulkable,
|
||||||
@ -89,17 +90,25 @@ const DynamicTable = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
components={{ ConfirmDialogDelete, ConfirmDialogDeleteAll }}
|
components={{ ConfirmDialogDelete }}
|
||||||
contentType={contentTypeName}
|
contentType={contentTypeName}
|
||||||
action={action}
|
action={action}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
headers={tableHeaders}
|
headers={tableHeaders}
|
||||||
onConfirmDelete={onConfirmDelete}
|
onConfirmDelete={onConfirmDelete}
|
||||||
onConfirmDeleteAll={onConfirmDeleteAll}
|
|
||||||
onOpenDeleteAllModalTrackedEvent="willBulkDeleteEntries"
|
onOpenDeleteAllModalTrackedEvent="willBulkDeleteEntries"
|
||||||
rows={rows}
|
rows={rows}
|
||||||
withBulkActions
|
withBulkActions
|
||||||
withMainAction={canDelete && isBulkable}
|
withMainAction={(canDelete || canPublish) && isBulkable}
|
||||||
|
renderBulkActionsBar={({ selectedEntries, clearSelectedEntries }) => (
|
||||||
|
<BulkActionsBar
|
||||||
|
showPublish={canPublish && hasDraftAndPublish}
|
||||||
|
showDelete={canDelete}
|
||||||
|
onConfirmDeleteAll={onConfirmDeleteAll}
|
||||||
|
selectedEntries={selectedEntries}
|
||||||
|
clearSelectedEntries={clearSelectedEntries}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<TableRows
|
<TableRows
|
||||||
canCreate={canCreate}
|
canCreate={canCreate}
|
||||||
@ -121,6 +130,7 @@ DynamicTable.defaultProps = {
|
|||||||
DynamicTable.propTypes = {
|
DynamicTable.propTypes = {
|
||||||
canCreate: PropTypes.bool.isRequired,
|
canCreate: PropTypes.bool.isRequired,
|
||||||
canDelete: PropTypes.bool.isRequired,
|
canDelete: PropTypes.bool.isRequired,
|
||||||
|
canPublish: PropTypes.bool.isRequired,
|
||||||
contentTypeName: PropTypes.string.isRequired,
|
contentTypeName: PropTypes.string.isRequired,
|
||||||
action: PropTypes.node,
|
action: PropTypes.node,
|
||||||
isBulkable: PropTypes.bool.isRequired,
|
isBulkable: PropTypes.bool.isRequired,
|
||||||
|
@ -64,6 +64,7 @@ function ListView({
|
|||||||
canCreate,
|
canCreate,
|
||||||
canDelete,
|
canDelete,
|
||||||
canRead,
|
canRead,
|
||||||
|
canPublish,
|
||||||
data,
|
data,
|
||||||
getData,
|
getData,
|
||||||
getDataSucceeded,
|
getDataSucceeded,
|
||||||
@ -331,6 +332,7 @@ function ListView({
|
|||||||
<DynamicTable
|
<DynamicTable
|
||||||
canCreate={canCreate}
|
canCreate={canCreate}
|
||||||
canDelete={canDelete}
|
canDelete={canDelete}
|
||||||
|
canPublish={canPublish}
|
||||||
contentTypeName={headerLayoutTitle}
|
contentTypeName={headerLayoutTitle}
|
||||||
onConfirmDeleteAll={handleConfirmDeleteAllData}
|
onConfirmDeleteAll={handleConfirmDeleteAllData}
|
||||||
onConfirmDelete={handleConfirmDeleteData}
|
onConfirmDelete={handleConfirmDeleteData}
|
||||||
@ -355,6 +357,7 @@ ListView.propTypes = {
|
|||||||
canCreate: PropTypes.bool.isRequired,
|
canCreate: PropTypes.bool.isRequired,
|
||||||
canDelete: PropTypes.bool.isRequired,
|
canDelete: PropTypes.bool.isRequired,
|
||||||
canRead: PropTypes.bool.isRequired,
|
canRead: PropTypes.bool.isRequired,
|
||||||
|
canPublish: PropTypes.bool.isRequired,
|
||||||
data: PropTypes.array.isRequired,
|
data: PropTypes.array.isRequired,
|
||||||
layout: PropTypes.exact({
|
layout: PropTypes.exact({
|
||||||
components: PropTypes.object.isRequired,
|
components: PropTypes.object.isRequired,
|
||||||
|
@ -32,9 +32,10 @@ const Table = ({
|
|||||||
rows,
|
rows,
|
||||||
withBulkActions,
|
withBulkActions,
|
||||||
withMainAction,
|
withMainAction,
|
||||||
|
renderBulkActionsBar,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const [entriesToDelete, setEntriesToDelete] = useState([]);
|
const [selectedEntries, setSelectedEntries] = useState([]);
|
||||||
const [showConfirmDeleteAll, setShowConfirmDeleteAll] = useState(false);
|
const [showConfirmDeleteAll, setShowConfirmDeleteAll] = useState(false);
|
||||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false);
|
const [showConfirmDelete, setShowConfirmDelete] = useState(false);
|
||||||
const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
|
const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
|
||||||
@ -44,7 +45,7 @@ const Table = ({
|
|||||||
const ROW_COUNT = rows.length + 1;
|
const ROW_COUNT = rows.length + 1;
|
||||||
const COL_COUNT = headers.length + (withBulkActions ? 1 : 0) + (withMainAction ? 1 : 0);
|
const COL_COUNT = headers.length + (withBulkActions ? 1 : 0) + (withMainAction ? 1 : 0);
|
||||||
const hasFilters = query?.filters !== undefined;
|
const hasFilters = query?.filters !== undefined;
|
||||||
const areAllEntriesSelected = entriesToDelete.length === rows.length && rows.length > 0;
|
const areAllEntriesSelected = selectedEntries.length === rows.length && rows.length > 0;
|
||||||
|
|
||||||
const content = hasFilters
|
const content = hasFilters
|
||||||
? {
|
? {
|
||||||
@ -57,9 +58,9 @@ const Table = ({
|
|||||||
const handleConfirmDeleteAll = async () => {
|
const handleConfirmDeleteAll = async () => {
|
||||||
try {
|
try {
|
||||||
setIsConfirmButtonLoading(true);
|
setIsConfirmButtonLoading(true);
|
||||||
await onConfirmDeleteAll(entriesToDelete);
|
await onConfirmDeleteAll(selectedEntries);
|
||||||
handleToggleConfirmDeleteAll();
|
handleToggleConfirmDeleteAll();
|
||||||
setEntriesToDelete([]);
|
setSelectedEntries([]);
|
||||||
setIsConfirmButtonLoading(false);
|
setIsConfirmButtonLoading(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setIsConfirmButtonLoading(false);
|
setIsConfirmButtonLoading(false);
|
||||||
@ -71,7 +72,7 @@ const Table = ({
|
|||||||
try {
|
try {
|
||||||
setIsConfirmButtonLoading(true);
|
setIsConfirmButtonLoading(true);
|
||||||
// await onConfirmDeleteAll(entriesToDelete);
|
// await onConfirmDeleteAll(entriesToDelete);
|
||||||
await onConfirmDelete(entriesToDelete[0]);
|
await onConfirmDelete(selectedEntries[0]);
|
||||||
handleToggleConfirmDelete();
|
handleToggleConfirmDelete();
|
||||||
setIsConfirmButtonLoading(false);
|
setIsConfirmButtonLoading(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -82,9 +83,9 @@ const Table = ({
|
|||||||
|
|
||||||
const handleSelectAll = () => {
|
const handleSelectAll = () => {
|
||||||
if (!areAllEntriesSelected) {
|
if (!areAllEntriesSelected) {
|
||||||
setEntriesToDelete(rows.map((row) => row.id));
|
setSelectedEntries(rows.map((row) => row.id));
|
||||||
} else {
|
} else {
|
||||||
setEntriesToDelete([]);
|
setSelectedEntries([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,19 +99,19 @@ const Table = ({
|
|||||||
|
|
||||||
const handleToggleConfirmDelete = () => {
|
const handleToggleConfirmDelete = () => {
|
||||||
if (showConfirmDelete) {
|
if (showConfirmDelete) {
|
||||||
setEntriesToDelete([]);
|
setSelectedEntries([]);
|
||||||
}
|
}
|
||||||
setShowConfirmDelete((prev) => !prev);
|
setShowConfirmDelete((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickDelete = (id) => {
|
const handleClickDelete = (id) => {
|
||||||
setEntriesToDelete([id]);
|
setSelectedEntries([id]);
|
||||||
|
|
||||||
handleToggleConfirmDelete();
|
handleToggleConfirmDelete();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectRow = ({ name, value }) => {
|
const handleSelectRow = ({ name, value }) => {
|
||||||
setEntriesToDelete((prev) => {
|
setSelectedEntries((prev) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
return prev.concat(name);
|
return prev.concat(name);
|
||||||
}
|
}
|
||||||
@ -119,6 +120,10 @@ const Table = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearSelectedEntries = () => {
|
||||||
|
setSelectedEntries([]);
|
||||||
|
};
|
||||||
|
|
||||||
const ConfirmDeleteAllComponent = components?.ConfirmDialogDeleteAll
|
const ConfirmDeleteAllComponent = components?.ConfirmDialogDeleteAll
|
||||||
? components.ConfirmDialogDeleteAll
|
? components.ConfirmDialogDeleteAll
|
||||||
: ConfirmDialog;
|
: ConfirmDialog;
|
||||||
@ -129,7 +134,7 @@ const Table = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{entriesToDelete.length > 0 && (
|
{selectedEntries.length > 0 && (
|
||||||
<Box>
|
<Box>
|
||||||
<Box paddingBottom={4}>
|
<Box paddingBottom={4}>
|
||||||
<Flex justifyContent="space-between">
|
<Flex justifyContent="space-between">
|
||||||
@ -140,9 +145,12 @@ const Table = ({
|
|||||||
id: 'content-manager.components.TableDelete.label',
|
id: 'content-manager.components.TableDelete.label',
|
||||||
defaultMessage: '{number, plural, one {# entry} other {# entries}} selected',
|
defaultMessage: '{number, plural, one {# entry} other {# entries}} selected',
|
||||||
},
|
},
|
||||||
{ number: entriesToDelete.length }
|
{ number: selectedEntries.length }
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{renderBulkActionsBar ? (
|
||||||
|
renderBulkActionsBar({ selectedEntries, clearSelectedEntries })
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleToggleConfirmDeleteAll}
|
onClick={handleToggleConfirmDeleteAll}
|
||||||
startIcon={<Trash />}
|
startIcon={<Trash />}
|
||||||
@ -151,6 +159,7 @@ const Table = ({
|
|||||||
>
|
>
|
||||||
{formatMessage({ id: 'global.delete', defaultMessage: 'Delete' })}
|
{formatMessage({ id: 'global.delete', defaultMessage: 'Delete' })}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</BlockActions>
|
</BlockActions>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
@ -159,7 +168,7 @@ const Table = ({
|
|||||||
<TableCompo colCount={COL_COUNT} rowCount={ROW_COUNT} footer={footer}>
|
<TableCompo colCount={COL_COUNT} rowCount={ROW_COUNT} footer={footer}>
|
||||||
<TableHead
|
<TableHead
|
||||||
areAllEntriesSelected={areAllEntriesSelected}
|
areAllEntriesSelected={areAllEntriesSelected}
|
||||||
entriesToDelete={entriesToDelete}
|
entriesToDelete={selectedEntries}
|
||||||
headers={headers}
|
headers={headers}
|
||||||
onSelectAll={handleSelectAll}
|
onSelectAll={handleSelectAll}
|
||||||
withMainAction={withMainAction}
|
withMainAction={withMainAction}
|
||||||
@ -175,7 +184,7 @@ const Table = ({
|
|||||||
) : (
|
) : (
|
||||||
Children.toArray(children).map((child) =>
|
Children.toArray(children).map((child) =>
|
||||||
cloneElement(child, {
|
cloneElement(child, {
|
||||||
entriesToDelete,
|
entriesToDelete: selectedEntries,
|
||||||
onClickDelete: handleClickDelete,
|
onClickDelete: handleClickDelete,
|
||||||
onSelectRow: handleSelectRow,
|
onSelectRow: handleSelectRow,
|
||||||
headers,
|
headers,
|
||||||
@ -219,6 +228,7 @@ Table.defaultProps = {
|
|||||||
rows: [],
|
rows: [],
|
||||||
withBulkActions: false,
|
withBulkActions: false,
|
||||||
withMainAction: false,
|
withMainAction: false,
|
||||||
|
renderBulkActionsBar: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
Table.propTypes = {
|
Table.propTypes = {
|
||||||
@ -248,6 +258,7 @@ Table.propTypes = {
|
|||||||
rows: PropTypes.array,
|
rows: PropTypes.array,
|
||||||
withBulkActions: PropTypes.bool,
|
withBulkActions: PropTypes.bool,
|
||||||
withMainAction: PropTypes.bool,
|
withMainAction: PropTypes.bool,
|
||||||
|
renderBulkActionsBar: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user