# MultiBulkActionToolbar

**Category:** Features/Actions/Bulk Actions

## Design

### Description

Provides the ability to perform multiple operations on multiple items.

There are 2 types of actions:

- **Primary actions:** Appear as separate CTAs and can be used a popover menus.

- **Secondary Actions:** Appear under the **More action** popover menu, and are found on the left next to the divider.

```tsx
import { MultiBulkActionToolbar } from '@wix/patterns';
```

---

### Variations

### Bulk action

Basic bulk action.

```tsx
import {
  Avatar,
  Card,
  CustomModalLayout,
  Modal,
  Page,
  Text,
} from '@wix/design-system';
import React from 'react';
import {
  MultiBulkActionToolbar,
  Filter,
  Table,
  OffsetQuery,
  stringsArrayFilter,
  useTableCollection,
  ToolbarTitle,
} from '@wix/patterns';
import { Edit, Download, ChangeOrder, Block } from '@wix/wix-ui-icons-common';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function BulkActions() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'MultiBulkActionToolbar-BulkActions',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {
      level: stringsArrayFilter({
        initialValue: ['Beginner'],
      }),
    },
  });
  const collection = state.collection;
  React.useEffect(() => {
    collection.bulkSelect.selectAll();
  }, []);

  return (
    <Page height="400px">
      <Page.Header title="Contacts" />
      <Page.Content>
        <Table
          state={state}
          title={<ToolbarTitle title="Contacts" showTotal />}
          columns={[
            {
              title: '',
              width: '50px',
              render: (contact) => (
                <Avatar
                  name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                  imgProps={{ src: contact.info?.picture?.image }}
                />
              ),
            },
            {
              title: 'Name',
              width: '250px',
              render: (contact) =>
                `${contact.info?.name?.first} ${contact.info?.name?.last}`,
            },
            {
              title: 'Level',
              render: (contact) => contact.info?.jobTitle,
            },
            {
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
          ]}
          bulkActionModal={({
            isModalOpen,
            selectedValues,
            closeModal,
            query,
          }) => (
            <Modal isOpen={isModalOpen}>
              <CustomModalLayout
                height={300}
                dataHook="bulk-action-custom-modal-layout"
                title="Edit Contacts"
                primaryButtonOnClick={closeModal}
                primaryButtonText="save"
                secondaryButtonOnClick={closeModal}
                secondaryButtonText="cancel"
              >
                {selectedValues.map((value, index) => (
                  <Card>
                    <Text
                      key={value._id}
                      dataHook={`selected-value-text-${index}`}
                    >
                      Page: {query.page}; Name:{' '}
                      {`${value.info?.name?.first} ${value.info?.name?.last}`}
                    </Text>
                  </Card>
                ))}
              </CustomModalLayout>
            </Modal>
          )}
          bulkActionToolbar={({ selectedValues, openModal }) => {
            const disabled = selectedValues.length > 4;
            return (
              <MultiBulkActionToolbar
                primaryActionItems={[
                  {
                    dataHook: 'edit-button',
                    onClick: openModal,
                    label: 'Edit',
                    prefixIcon: <Edit />,
                  },
                  {
                    dataHook: 'download-button',
                    onClick: openModal,
                    label: 'Download',
                    prefixIcon: <Download />,
                    tooltip: disabled
                      ? 'Downloading is supported for up to 10 items'
                      : undefined,
                    disabled,
                  },
                ]}
                secondaryActionItems={[
                  {
                    dataHook: 'block-button',
                    onClick: openModal,
                    label: 'Block',
                    prefixIcon: <Block />,
                  },
                  {
                    dataHook: 'change-button',
                    onClick: openModal,
                    label: 'Change',
                    disabled,
                    prefixIcon: <ChangeOrder />,
                  },
                ]}
              />
            );
          }}
        />
      </Page.Content>
    </Page>
  );
}
```

### Popover menu

A popoever menu for the primary action.

```tsx
import { Avatar, Page } from '@wix/design-system';
import React from 'react';
import {
  MultiBulkActionToolbar,
  Filter,
  Table,
  PageWrapper,
  OffsetQuery,
  stringsArrayFilter,
  useTableCollection,
  ToolbarTitle,
} from '@wix/patterns';
import { Visible, Hidden } from '@wix/wix-ui-icons-common';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function MultiBulkActionsPopoverMenu() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'MultiBulkActionToolbar-PopoverMenu',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {
      level: stringsArrayFilter({
        initialValue: ['Beginner'],
      }),
    },
  });
  const collection = state.collection;
  React.useEffect(() => {
    collection.bulkSelect.selectAll();
  }, []);

  const options = [
    { label: 'Visible', icon: <Visible /> },
    { label: 'Hidden', icon: <Hidden /> },
  ];

  const [selectedOption, setSelectedOption] = React.useState({
    label: 'Set Visibility',
    icon: <Visible />,
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Contacts" />
        <Page.Content>
          <Table
            state={state}
            title={<ToolbarTitle title="Contacts" showTotal />}
            columns={[
              {
                title: '',
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
              {
                title: 'Level',
                render: (contact) => contact.info?.jobTitle,
              },
              {
                title: 'Last Activity',
                render: (contact) =>
                  contact.lastActivity?.activityDate?.toLocaleString(),
              },
            ]}
            bulkActionToolbar={({ selectedValues, openModal }) => {
              return (
                <MultiBulkActionToolbar
                  primaryActionItems={[
                    {
                      dataHook: 'set-visibility',
                      label: selectedOption.label,
                      prefixIcon: selectedOption.icon,
                      subItems: options.map((el, i) => ({
                        prefixIcon: el.icon,
                        label: el.label,
                        onClick: () => setSelectedOption(el),
                      })),
                    },
                  ]}
                />
              );
            }}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Basic secondary action

Popoever menu for primary and secondary actions.

```tsx
import { Avatar, CustomModalLayout, Modal, Page } from '@wix/design-system';
import React from 'react';
import {
  MultiBulkActionToolbar,
  Filter,
  Table,
  PageWrapper,
  OffsetQuery,
  stringsArrayFilter,
  useTableCollection,
  ToolbarTitle,
} from '@wix/patterns';
import {
  Visible,
  Download,
  Block,
  ChangeOrder,
} from '@wix/wix-ui-icons-common';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function MultiBulkActionToolbarSecondaryActions() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'MultiBulkActionToolbar-SecondaryActions',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {
      level: stringsArrayFilter({
        initialValue: ['Beginner'],
      }),
    },
  });
  const collection = state.collection;
  React.useEffect(() => {
    collection.bulkSelect.selectAll();
  }, []);

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Content>
          <Table
            state={state}
            title={<ToolbarTitle title="Contacts" showTotal />}
            columns={[
              {
                title: '',
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
              {
                title: 'Level',
                render: (contact) => contact.info?.jobTitle,
              },
              {
                title: 'Last Activity',
                render: (contact) =>
                  contact.lastActivity?.activityDate?.toLocaleString(),
              },
            ]}
            bulkActionModal={({
              isModalOpen,
              allSelected,
              selectedValues,
              uncheckedValues,
              closeModal,
              query,
            }) => (
              <Modal isOpen={isModalOpen}>
                <CustomModalLayout
                  height={300}
                  dataHook="bulk-action-custom-modal-layout"
                  title="Edit Product"
                  primaryButtonOnClick={closeModal}
                  primaryButtonText="Save"
                  secondaryButtonOnClick={closeModal}
                  secondaryButtonText="Cancel"
                >
                  <pre>
                    {JSON.stringify(
                      {
                        allSelected,
                        selectedValues: selectedValues.length,
                        uncheckedValues: uncheckedValues.length,
                        query: {
                          page: query.page,
                        },
                      },
                      null,
                      2,
                    )}
                  </pre>
                </CustomModalLayout>
              </Modal>
            )}
            bulkActionToolbar={({ selectedValues, openModal }) => {
              return (
                <MultiBulkActionToolbar
                  primaryActionItems={[
                    {
                      dataHook: 'primary-action-1',
                      label: 'Primary 1',
                      prefixIcon: <Visible />,
                      subItems: [
                        { label: 'option 1' },
                        { label: 'option 2' },
                        { label: 'option 3' },
                      ],
                    },
                    {
                      dataHook: 'primary-action-2',
                      label: 'Primary 2',
                      prefixIcon: <Download />,
                      subItems: [
                        { label: 'option 1' },
                        { label: 'option 2' },
                        { label: 'option 3' },
                      ],
                    },
                  ]}
                  secondaryActionItems={[
                    {
                      dataHook: 'secondary-action-1',
                      onClick: openModal,
                      label: 'Secondary 1',
                      prefixIcon: <Block />,
                    },
                    {
                      dataHook: 'secondary-action-2',
                      onClick: openModal,
                      disabled: true,
                      label: 'Secondary 2',
                      prefixIcon: <ChangeOrder />,
                    },
                  ]}
                />
              );
            }}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Primary & Secondary actions with a divider

Create groups of `primaryActionItems -> subItems` & `secondaryActionItems` with a divider between each group. Each member of a group is defined in the same array. The groups arrays are included in the `items` array.

```tsx
import {
  Avatar,
  Card,
  CustomModalLayout,
  Modal,
  Page,
  Text,
} from '@wix/design-system';
import React from 'react';
import {
  MultiBulkActionToolbar,
  Filter,
  Table,
  OffsetQuery,
  stringsArrayFilter,
  useTableCollection,
  ToolbarTitle,
} from '@wix/patterns';
import {
  Edit,
  Download,
  ChangeOrder,
  Block,
  UploadExport,
} from '@wix/wix-ui-icons-common';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function BulkActionsWithDividers() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'MultiBulkActionToolbar-BulkActions',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {
      level: stringsArrayFilter({
        initialValue: ['Beginner'],
      }),
    },
  });
  const collection = state.collection;
  React.useEffect(() => {
    collection.bulkSelect.selectAll();
  }, []);

  return (
    <Page height="400px">
      <Page.Header title="Contacts" />
      <Page.Content>
        <Table
          state={state}
          title={<ToolbarTitle title="Contacts" showTotal />}
          columns={[
            {
              title: '',
              width: '50px',
              render: (contact) => (
                <Avatar
                  name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                  imgProps={{ src: contact.info?.picture?.image }}
                />
              ),
            },
            {
              title: 'Name',
              width: '250px',
              render: (contact) =>
                `${contact.info?.name?.first} ${contact.info?.name?.last}`,
            },
            {
              title: 'Level',
              render: (contact) => contact.info?.jobTitle,
            },
            {
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
          ]}
          bulkActionModal={({
            isModalOpen,
            selectedValues,
            closeModal,
            query,
          }) => (
            <Modal isOpen={isModalOpen}>
              <CustomModalLayout
                height={300}
                dataHook="bulk-action-custom-modal-layout"
                title="Edit Contacts"
                primaryButtonOnClick={closeModal}
                primaryButtonText="save"
                secondaryButtonOnClick={closeModal}
                secondaryButtonText="cancel"
              >
                {selectedValues.map((value, index) => (
                  <Card>
                    <Text
                      key={value._id}
                      dataHook={`selected-value-text-${index}`}
                    >
                      Page: {query.page}; Name:{' '}
                      {`${value.info?.name?.first} ${value.info?.name?.last}`}
                    </Text>
                  </Card>
                ))}
              </CustomModalLayout>
            </Modal>
          )}
          bulkActionToolbar={({ selectedValues, openModal }) => {
            const disabled = selectedValues.length > 4;
            return (
              <MultiBulkActionToolbar
                primaryActionItems={[
                  {
                    dataHook: 'delete-button',
                    onClick: openModal,
                    label: 'Delete Items',
                    prefixIcon: <Block />,
                    subItems: [
                      [
                        {
                          dataHook: 'remove-button',
                          label: 'Remove',
                          onClick: openModal,
                        },
                        {
                          dataHook: 'soft-delete-button',
                          label: 'Soft Delete',
                          onClick: openModal,
                        },
                      ],
                      [
                        {
                          dataHook: 'archive-button',
                          label: 'Archive',
                          onClick: openModal,
                        },
                      ],
                    ],
                  },
                  {
                    dataHook: 'edit-button',
                    onClick: openModal,
                    label: 'Edit',
                    prefixIcon: <Edit />,
                  },
                  {
                    dataHook: 'download-button',
                    onClick: openModal,
                    label: 'Download',
                    prefixIcon: <Download />,
                    tooltip: disabled
                      ? 'Downloading is supported for up to 10 items'
                      : undefined,
                    disabled,
                  },
                ]}
                secondaryActionItems={[
                  [
                    {
                      dataHook: 'block-button',
                      onClick: openModal,
                      label: 'Block',
                      prefixIcon: <Block />,
                    },
                    {
                      dataHook: 'change-button',
                      onClick: openModal,
                      label: 'Change',
                      disabled,
                      prefixIcon: <ChangeOrder />,
                    },
                  ],
                  [
                    {
                      dataHook: 'download-button',
                      onClick: openModal,
                      label: 'Download',
                      prefixIcon: <Download />,
                    },
                    {
                      dataHook: 'export-button',
                      onClick: openModal,
                      label: 'Export',
                      disabled,
                      prefixIcon: <UploadExport />,
                    },
                  ],
                ]}
              />
            );
          }}
        />
      </Page.Content>
    </Page>
  );
}
```

### Maximum selection

`maxSelection` sets a limit on the number of items a visitor can select.

```tsx
import {
  Avatar,
  Card,
  CustomModalLayout,
  Modal,
  Page,
  Text,
} from '@wix/design-system';
import React from 'react';
import {
  MultiBulkActionToolbar,
  Filter,
  Table,
  PageWrapper,
  OffsetQuery,
  stringsArrayFilter,
  useTableCollection,
  ToolbarTitle,
} from '@wix/patterns';
import { Edit, Download, ChangeOrder, Block } from '@wix/wix-ui-icons-common';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function MultiBulkActionToolbarMaxSelection() {
  const tableState = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'contacts-BulkActionsWithMaxSelection',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error Fetching Contacts',
    filters: {
      level: stringsArrayFilter({
        initialValue: ['Beginner'],
      }),
    },
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Contacts" />
        <Page.Content>
          <Table
            state={tableState}
            title={<ToolbarTitle title="Contacts" showTotal />}
            maxSelection={5}
            columns={[
              {
                title: '',
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
              {
                title: 'Level',
                render: (contact) => contact.info?.jobTitle,
              },
              {
                title: 'Last Activity',
                render: (contact) =>
                  contact.lastActivity?.activityDate?.toLocaleString(),
              },
            ]}
            bulkActionModal={({
              isModalOpen,
              selectedValues,
              closeModal,
              query,
            }) => (
              <Modal isOpen={isModalOpen}>
                <CustomModalLayout
                  height={300}
                  dataHook="bulk-action-custom-modal-layout"
                  title="Edit Contacts"
                  primaryButtonOnClick={closeModal}
                  primaryButtonText="save"
                  secondaryButtonOnClick={closeModal}
                  secondaryButtonText="cancel"
                >
                  {selectedValues.map((value, index) => (
                    <Card>
                      <Text
                        key={value._id}
                        dataHook={`selected-value-text-${index}`}
                      >
                        Page: {query.page}; Name:{' '}
                        {`${value.info?.name?.first} ${value.info?.name?.last}`}
                      </Text>
                    </Card>
                  ))}
                </CustomModalLayout>
              </Modal>
            )}
            bulkActionToolbar={({ selectedValues, openModal }) => {
              const disabled = selectedValues.length > 4;
              return (
                <MultiBulkActionToolbar
                  primaryActionItems={[
                    {
                      dataHook: 'edit-button',
                      onClick: openModal,
                      label: 'Edit',
                      prefixIcon: <Edit />,
                    },
                    {
                      dataHook: 'download-button',
                      onClick: openModal,
                      label: 'Download',
                      prefixIcon: <Download />,
                      tooltip: disabled
                        ? 'Downloading is supported for up to 10 items'
                        : undefined,
                      disabled,
                    },
                  ]}
                  secondaryActionItems={[
                    {
                      dataHook: 'block-button',
                      onClick: openModal,
                      label: 'Block',
                      prefixIcon: <Block />,
                    },
                    {
                      dataHook: 'change-button',
                      onClick: openModal,
                      label: 'Change',
                      disabled,
                      prefixIcon: <ChangeOrder />,
                    },
                  ]}
                />
              );
            }}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Extend menu items

You can extend existing menu items using [Slots and Plugins](./?path=/story/features-enrich-slots-and-plugins--slots-and-plugins) using your own `containerId`.

```tsx
import { Avatar, CustomModalLayout, Modal, Page } from '@wix/design-system';
import React from 'react';
import {
  MultiBulkActionToolbar,
  Filter,
  Table,
  PageWrapper,
  OffsetQuery,
  stringsArrayFilter,
  useTableCollection,
} from '@wix/patterns';
import {
  Visible,
  Download,
  Block,
  ChangeOrder,
} from '@wix/wix-ui-icons-common';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
};

function MultiBulkActionToolbarExtensionMenuItems() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'MultiBulkActionToolbar-ExtensionMenuItems',
    paginationMode: 'offset',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (filters.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {
      level: stringsArrayFilter({
        initialValue: ['Beginner'],
      }),
    },
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Contacts" />
        <Page.Content>
          <Table
            state={state}
            columns={[
              {
                title: '',
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
              {
                title: 'Level',
                render: (contact) => contact.info?.jobTitle,
              },
              {
                title: 'Last Activity',
                render: (contact) =>
                  contact.lastActivity?.activityDate?.toLocaleString(),
              },
            ]}
            bulkActionModal={({
              isModalOpen,
              allSelected,
              selectedValues,
              uncheckedValues,
              closeModal,
              query,
            }) => (
              <Modal isOpen={isModalOpen}>
                <CustomModalLayout
                  height={300}
                  dataHook="bulk-action-custom-modal-layout"
                  title="Edit Product"
                  primaryButtonOnClick={closeModal}
                  primaryButtonText="Save"
                  secondaryButtonOnClick={closeModal}
                  secondaryButtonText="Cancel"
                >
                  <pre>
                    {JSON.stringify(
                      {
                        allSelected,
                        selectedValues: selectedValues.length,
                        uncheckedValues: uncheckedValues.length,
                        query: {
                          page: query.page,
                        },
                      },
                      null,
                      2,
                    )}
                  </pre>
                </CustomModalLayout>
              </Modal>
            )}
            bulkActionToolbar={({ selectedValues, openModal }) => {
              return (
                <MultiBulkActionToolbar
                  containerId="cd910877-1bb2-445e-822a-aa04a342c2f5"
                  primaryActionItems={[
                    {
                      dataHook: 'primary-action-1',
                      label: 'Primary 1',
                      prefixIcon: <Visible />,
                      subItems: [
                        { label: 'option 1' },
                        { label: 'option 2' },
                        { label: 'option 3' },
                      ],
                    },
                    {
                      dataHook: 'primary-action-2',
                      label: 'Primary 2',
                      prefixIcon: <Download />,
                      subItems: [
                        { label: 'option 1' },
                        { label: 'option 2' },
                        { label: 'option 3' },
                      ],
                    },
                  ]}
                  secondaryActionItems={[
                    {
                      dataHook: 'secondary-action-1',
                      onClick: openModal,
                      label: 'Secondary 1',
                      prefixIcon: <Block />,
                    },
                    {
                      dataHook: 'secondary-action-2',
                      onClick: openModal,
                      disabled: true,
                      label: 'Secondary 2',
                      prefixIcon: <ChangeOrder />,
                    },
                  ]}
                />
              );
            }}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

## API

### Extensions

Any menu item that extends the provided `containerId` will be invoked with the following payload:

```ts
{
  /**
   * An array of IDs representing the items that have been selected.
   */
  selectedIds: string[];

  /**
   * An array of IDs representing the items that have been unchecked, applicable only when the "Select All" option was selected.
   */
  uncheckedIds: string[];

  /**
   * A boolean value indicating whether the "Select All" option was selected.
   */
  allSelected: boolean;

  /**
   * The total number of items that were selected. The calculation of this value depends on whether you have opted in to use the `useNewSelectAllLogic` behavior or not.
   */
  total: number;
}
```

Also, you can pass more data to the extension by using the `containerProps` property in the `MultiBulkActionToolbar` component. This data will be passed to the extension as part of the payload.

```jsx
To read more about extensions in general and what else is possible, go to the main [Slots and Plugins](./?path=/story/features-enrich-slots-and-plugins--slots-and-plugins) guide.


### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `primaryActionItems` | `ActionSubitem[]` | No | - | List of primary actions. Primary actions are displayed as individual buttons. |
| `secondaryActionItems` | `ActionSubitem[] \| ActionSubitem[][]` | No | - | List of secondary actions. Secondary actions are displayed when clicking the More Actions menu. Secondary actions can be grouped by using an array of arrays, and a divider will be added between each group. \| [ActionSubitem[][]](./?path=/story/common-types--actionsubitem) |
| `secondaryActionsPopoverMenuProps` | `Partial<PopoverMenuProps>` | No | - | [PopoverMenu](https://www.docs.wixdesignsystem.com/?path=/story/components-overlays--popovermenu) props to pass to the 'More Action' popover menu. |
| `containerId` | `string` | No | - | This ID is used to specify the target container for [dashboard menu plugin](./?path=/story/features-enrich-slots-and-plugins--slots-and-plugins) items. |
| `containerProps` | `Record<string, any>` | No | - | This prop is used to pass additional properties that can be used on the onClick of the container's extensions menu items. |

## BI

### Custom Columns Events

 Event   |   Description   |
--------- | --------------- |
[144:104](https://bo.wix.com/data-tools/bi-catalog-app/event/144:104) | Actual pick inside the Wix Patterns picker (CTA button or immediate CTA)
[144:105](https://bo.wix.com/data-tools/bi-catalog-app/event/144:105) | Picker button actions (not within the modal)
[144:114](https://bo.wix.com/data-tools/bi-catalog-app/event/144:114) | Sent when a user select/deselects an item(s) in a list
[144:115](https://bo.wix.com/data-tools/bi-catalog-app/event/144:115) | Sent when a user clicks a CTA ('save', 'add', 'delete', 'continue', etc.) in a Wix Patterns component



### Component load events
Event   |   Description   |
--------- | --------------- |
[144:110](https://bo.wix.com/data-tools/bi-catalog-app/event/144:110) | Sent when a Wix Patterns component starts loading
[144:111](https://bo.wix.com/data-tools/bi-catalog-app/event/144:111) | Sent when a Wix Patterns component is done loading


#### 🐪 Couldn't find what you need?
* Check our [BI Catalog](https://bo.wix.com/data-tools/bi-catalog-app?viewId=all-items-view&selectedColumns=src%2Cevid%2Cname%2Cowner%2Cproduct%2CuserType+false%2CdateUpdated%2CdateCreated+false%2CcreatedBy+false%2Cstatus&source=%5B%7B%22id%22%3A%22144%22%2C%22name%22%3A%22144+-+Cairo%22%7D%5D)
* Contact us on [#cairo-bi](https://wix.slack.com/archives/C03N53KURH9)


## Testkit

### Usage

All `` testkit APIs are exposed directly from the `` [testkit](./?path=/story/base-components-collections-table-table--table&activeTab=Testkit)


