# ToolbarFilters

**Category:** Features/Filter/Components

## Design

The Toolbar Filters feature allows you to manipulate data from a collection by applying specific criteria, conditions, or filters. For example, you can use the Toolbar Filter to filter data displayed in a table. You can define any filter item as the default. 
 
Filters are positioned on the left side of the toolbar unless there is existing left-side content such as a title. 
 
By default, when there are more than 2 filters, the toolbar automatically displays a filters button on the right side for the additional filters. Clicking on the filters button opens the additional filters in a side panel. 
 
You can override the default and set the number of inline-toolbar filters to display. For example, if you want 2 out of your 4 filters to be displayed inline, set `inline` to 2: 
 
 `` 
 
If you don't want any inline filters, then set `inline` to 0: 
 
 `` 
 
> **Note:** Some filters such as the [`DateRangeFilter`](./?path=%/story/features-filter-components--daterangefilter) and [`RadioGroupFilter`](./?path=/story/features-filter-components--radiogroupfilter), are always displayed in the side panel and aren't supported inline in the toolbar.

**Do:**

- Add a counter to a filter to indicate the amount of items in the search or filtered results.


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

---

### Variations

### 2 filters without a title

A table with a toolbar containing 2 filters, and no title. Filters are displayed on the left side of the toolbar unless there's existing left-side content, such as a title.

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { Edit } from '@wix/wix-ui-icons-common';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionToolbarFilters,
  Filter,
  idNameArrayFilter,
  MultiSelectCheckboxFilter,
  stringsArrayFilter,
  Table,
  useTableCollection,
  SingleSelectFilter,
  useStaticListFilterCollection,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';
import { subDays } from 'date-fns';

interface Duration {
  id: string;
  name: string;
  days: number | null;
}

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

function UpTo2Filters() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'contacts-Basic',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    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);
      }

      const lastActivityDays = filters.lastActivity?.[0]?.days;
      if (lastActivityDays) {
        queryBuilder = queryBuilder.gt(
          'lastActivity.activityDate',
          subDays(new window.Date(), lastActivityDays),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    filters: {
      level: stringsArrayFilter(),
      lastActivity: idNameArrayFilter(),
    },
    fetchErrorMessage: ({ err }) => String(err),
  });
  const collection = state.collection;

  const lastActivityCollection = useStaticListFilterCollection(
    collection.filters.lastActivity,
    [
      { id: 'w', name: 'Week', days: 7 },
      { id: 'm', name: 'Month', days: 30 },
      { id: 'y', name: 'Year', days: 365 },
      { id: 'o', name: 'Other', days: null },
    ],
  );

  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          filters={
            <CollectionToolbarFilters>
              <MultiSelectCheckboxFilter
                accordionItemProps={{ label: 'Level' }}
                popoverProps={{ appendTo: 'window' }}
                filter={collection.filters.level!}
                collection={levelsCollection}
                renderItem={(level) => ({ title: level })}
              />
              <SingleSelectFilter
                filter={collection.filters.lastActivity!}
                placeholder="Select Last Activity"
                accordionItemProps={{ label: 'Last Activity' }}
                collection={lastActivityCollection}
              />
            </CollectionToolbarFilters>
          }
          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(),
            },
          ]}
          actionCell={(contact, index) => ({
            secondaryActions: [
              {
                icon: <Edit />,
                text: 'Edit',
                disabled:
                  index % 5 === 0 || contact.info?.name?.first?.startsWith('A'),
                onClick: () => {},
              },
            ],
          })}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### 2 filters with a title

A table with a toolbar containing 2 filters and a title. Filters are displayed on the left side of the toolbar unless there's existing left-side content, such as a title.

```tsx
import { Avatar, Page } from '@wix/design-system';
import React from 'react';
import { Edit } from '@wix/wix-ui-icons-common';
import {
  CollectionToolbarFilters,
  Filter,
  idNameArrayFilter,
  MultiSelectCheckboxFilter,
  OffsetQuery,
  PageWrapper,
  SingleSelectFilter,
  stringsArrayFilter,
  Table,
  useTableCollection,
  useStaticListFilterCollection,
  ToolbarTitle,
} from '@wix/patterns';
import { contacts } from '@wix/crm';
import { subDays } from 'date-fns';

interface Duration {
  id: string;
  name: string;
  days: number | null;
}

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

function WithLeftContent() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'WithLeftContent',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    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);
      }

      const lastActivityDays = filters.lastActivity?.[0]?.days;
      if (lastActivityDays) {
        queryBuilder = queryBuilder.gt(
          'lastActivity.activityDate',
          subDays(new window.Date(), lastActivityDays),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    filters: {
      level: stringsArrayFilter(),
      lastActivity: idNameArrayFilter(),
    },
    fetchErrorMessage: ({ err }) => String(err),
  });
  const collection = state.collection;
  const lastActivityCollection = useStaticListFilterCollection(
    collection.filters.lastActivity,
    [
      { id: 'w', name: 'Week', days: 7 },
      { id: 'm', name: 'Month', days: 30 },
      { id: 'y', name: 'Year', days: 365 },
      { id: 'o', name: 'Other', days: null },
    ],
  );

  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Contacts" />
        <Page.Content>
          <Table
            state={state}
            title={<ToolbarTitle title="Title" />}
            filters={
              <CollectionToolbarFilters inline={2}>
                <MultiSelectCheckboxFilter
                  accordionItemProps={{ label: 'Level' }}
                  popoverProps={{ appendTo: 'window' }}
                  filter={collection.filters.level!}
                  collection={levelsCollection}
                  renderItem={(level) => ({ title: level })}
                />
                <SingleSelectFilter
                  filter={collection.filters.lastActivity!}
                  placeholder="Select Last Activity"
                  accordionItemProps={{ label: 'Last Activity' }}
                  collection={lastActivityCollection}
                />
              </CollectionToolbarFilters>
            }
            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(),
              },
            ]}
            actionCell={(contact, index) => ({
              secondaryActions: [
                {
                  icon: <Edit />,
                  text: 'Edit',
                  disabled:
                    index % 5 === 0 ||
                    contact.info?.name?.first?.startsWith('A'),
                  onClick: () => {},
                },
              ],
            })}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Custom label for tags

Sets a custom label for the filtered tags using toolbarTagProps.

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { Edit } from '@wix/wix-ui-icons-common';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionToolbarFilters,
  dateRangeFilter,
  DateRangeFilter,
  idNameArrayFilter,
  MultiAutoInlineCheckboxFilter,
  MultiSelectCheckboxFilter,
  stringsArrayFilter,
  Table,
  useTableCollection,
  useStaticListFilterCollection,
  Filter,
  RangeItem,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
  lastActivity: Filter<RangeItem<Date>>;
  locale: Filter<{ id: string; name: string }[]>;
};

function TagWithCustomLabel() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'contacts-TagWithCustomLabel',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    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);
      }

      if (filters.lastActivity) {
        queryBuilder = queryBuilder
          .gt('lastActivity.activityDate', filters.lastActivity.from)
          .lt('lastActivity.activityDate', filters.lastActivity.to);
      }

      if (filters.locale) {
        queryBuilder = queryBuilder.in(
          'info.locale',
          filters.locale.map(({ id }) => id),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    filters: {
      level: stringsArrayFilter(),
      lastActivity: dateRangeFilter(),
      locale: idNameArrayFilter(),
    },
    fetchErrorMessage: ({ err }) => String(err),
  });
  const collection = state.collection;

  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          filters={
            <CollectionToolbarFilters inline={0}>
              <MultiSelectCheckboxFilter
                accordionItemProps={{ label: 'Level' }}
                popoverProps={{ appendTo: 'window' }}
                filter={collection.filters.level}
                collection={levelsCollection}
                renderItem={(level) => ({ title: level })}
                toolbarTagProps={{ label: 'The levels are' }}
              />
              <MultiAutoInlineCheckboxFilter
                filter={collection.filters.locale}
                accordionItemProps={{ label: 'Locale' }}
                data={[
                  { id: 'en-US', name: 'en-US' },
                  { id: 'he-IL', name: 'he-IL' },
                  { id: 'fr-FR', name: 'fr-FR' },
                  { id: 'es-ES', name: 'es-ES' },
                  { id: 'de-DE', name: 'de-DE' },
                ]}
                toolbarTagProps={{ label: 'Custom Locale label' }}
              />

              <DateRangeFilter
                filter={collection.filters.lastActivity}
                accordionItemProps={{ label: 'Last Activity' }}
              />
            </CollectionToolbarFilters>
          }
          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(),
            },
            {
              title: 'Locale',
              render: (contact) => contact.info?.locale,
            },
          ]}
          actionCell={(contact, index) => ({
            secondaryActions: [
              {
                icon: <Edit />,
                text: 'Edit',
                disabled:
                  index % 5 === 0 || contact.info?.name?.first?.startsWith('A'),
                onClick: () => {},
              },
            ],
          })}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### More than 2 filters

If there are more than 2 filters, the filters are displayed in a side panel.

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { Edit } from '@wix/wix-ui-icons-common';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionToolbarFilters,
  dateRangeFilter,
  DateRangeFilter,
  idNameArrayFilter,
  MultiAutoInlineCheckboxFilter,
  MultiSelectCheckboxFilter,
  stringsArrayFilter,
  Table,
  useTableCollection,
  useStaticListFilterCollection,
  Filter,
  RangeItem,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
  lastActivity: Filter<RangeItem<Date>>;
  locale: Filter<{ id: string; name: string }[]>;
};

function MoreThan2Filters() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'contacts-Basic',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    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);
      }

      if (filters.lastActivity) {
        queryBuilder = queryBuilder
          .gt('lastActivity.activityDate', filters.lastActivity.from)
          .lt('lastActivity.activityDate', filters.lastActivity.to);
      }

      if (filters.locale) {
        queryBuilder = queryBuilder.in(
          'info.locale',
          filters.locale.map(({ id }) => id),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    filters: {
      level: stringsArrayFilter(),
      lastActivity: dateRangeFilter(),
      locale: idNameArrayFilter(),
    },
    fetchErrorMessage: ({ err }) => String(err),
  });
  const collection = state.collection;

  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          filters={
            <CollectionToolbarFilters inline={0}>
              <MultiSelectCheckboxFilter
                accordionItemProps={{ label: 'Level' }}
                popoverProps={{ appendTo: 'window' }}
                filter={collection.filters.level}
                collection={levelsCollection}
                renderItem={(level) => ({ title: level })}
              />
              <MultiAutoInlineCheckboxFilter
                filter={collection.filters.locale}
                accordionItemProps={{ label: 'Locale' }}
                data={[
                  { id: 'en-US', name: 'en-US' },
                  { id: 'he-IL', name: 'he-IL' },
                  { id: 'fr-FR', name: 'fr-FR' },
                  { id: 'es-ES', name: 'es-ES' },
                  { id: 'de-DE', name: 'de-DE' },
                ]}
              />
              <DateRangeFilter
                filter={collection.filters.lastActivity}
                accordionItemProps={{ label: 'Last Activity' }}
              />
            </CollectionToolbarFilters>
          }
          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(),
            },
            {
              title: 'Locale',
              render: (contact) => contact.info?.locale,
            },
          ]}
          actionCell={(contact, index) => ({
            secondaryActions: [
              {
                icon: <Edit />,
                text: 'Edit',
                disabled:
                  index % 5 === 0 || contact.info?.name?.first?.startsWith('A'),
                onClick: () => {},
              },
            ],
          })}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Apply changes

This example adds an apply filters button. If you want to get a reference to the filter that is about to be applied, use [getPendingFilter](./?path=%2Fstory%2Fcommon-state--toolbarcollectionstate).

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { Edit } from '@wix/wix-ui-icons-common';
import { CollectionPage } from '@wix/patterns/page';
import {
  AddApplyFiltersButton,
  CollectionToolbarFilters,
  dateRangeFilter,
  DateRangeFilter,
  Filter,
  idNameArrayFilter,
  MultiAutoInlineCheckboxFilter,
  MultiSelectCheckboxFilter,
  RangeItem,
  stringsArrayFilter,
  Table,
  useTableCollection,
  useStaticListFilterCollection,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
  lastActivity: Filter<RangeItem<Date>>;
  locale: Filter<{ id: string; name: string }[]>;
};

export function ApplyChanges() {
  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'contacts-Basic',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    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);
      }

      if (filters.lastActivity) {
        queryBuilder = queryBuilder
          .gt('lastActivity.activityDate', filters.lastActivity.from)
          .lt('lastActivity.activityDate', filters.lastActivity.to);
      }

      if (filters.locale) {
        queryBuilder = queryBuilder.in(
          'info.locale',
          filters.locale.map(({ id }) => id),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    filters: {
      level: stringsArrayFilter(),
      lastActivity: dateRangeFilter(),
      locale: idNameArrayFilter(),
    },
    fetchErrorMessage: ({ err }) => String(err),
  });
  const collection = state.collection;

  const levelsCollection = useStaticListFilterCollection(
    state.collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          dataHook="apply-changes-table"
          state={state}
          AddApplyFiltersButton={AddApplyFiltersButton}
          filters={
            <CollectionToolbarFilters useNewFilters>
              <MultiSelectCheckboxFilter
                accordionItemProps={{ label: 'Level' }}
                popoverProps={{ appendTo: 'window' }}
                filter={collection.filters.level}
                collection={levelsCollection}
                renderItem={(level) => ({ title: level })}
              />
              <MultiAutoInlineCheckboxFilter
                filter={collection.filters.locale}
                accordionItemProps={{ label: 'Locale' }}
                data={[
                  { id: 'en-US', name: 'en-US' },
                  { id: 'he-IL', name: 'he-IL' },
                  { id: 'fr-FR', name: 'fr-FR' },
                  { id: 'es-ES', name: 'es-ES' },
                  { id: 'de-DE', name: 'de-DE' },
                ]}
              />

              <DateRangeFilter
                filter={collection.filters.lastActivity}
                accordionItemProps={{ label: 'Last Activity' }}
              />
            </CollectionToolbarFilters>
          }
          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(),
            },
            {
              title: 'Locale',
              render: (contact) => contact.info?.locale,
            },
          ]}
          actionCell={(contact, index) => ({
            secondaryActions: [
              {
                icon: <Edit />,
                text: 'Edit',
                disabled:
                  index % 5 === 0 || contact.info?.name?.first?.startsWith('A'),
                onClick: () => {},
              },
            ],
          })}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Postpone collection filter initial fetch

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { Edit } from '@wix/wix-ui-icons-common';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionToolbarFilters,
  dateRangeFilter,
  DateRangeFilter,
  Filter,
  AddApplyFiltersButton,
  idNameArrayFilter,
  Table,
  MultiSelectCollectionFilter,
  RangeItem,
  useTableCollection,
  useFilterCollection,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactFilters = {
  contacts: Filter<Contact[]>;
  lastActivity: Filter<RangeItem<Date>>;
};

type Contact = {
  id: string;
  name: string;
};

function PostponeCollectionFilterInitialFetch() {

  const state = useTableCollection<contacts.Contact, ContactFilters>({
    queryName: 'PostponeCollectionFilterInitialFetch',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchData: (query: OffsetQuery<ContactFilters>) => {
      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.contacts) {
        queryBuilder = queryBuilder.in(
          '_id',
          filters.contacts.map(({ id }) => id),
        );
      }

      if (filters.lastActivity) {
        queryBuilder = queryBuilder
          .gt('lastActivity.activityDate', filters.lastActivity.from)
          .lt('lastActivity.activityDate', filters.lastActivity.to);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    filters: {
      contacts: idNameArrayFilter(),
      lastActivity: dateRangeFilter(),
    },
    fetchErrorMessage: ({ err }) => String(err),
  });
  const collection = state.collection;

  const contactsCollection = useFilterCollection<Contact>(
    collection.filters.contacts,
    {
      queryName: 'contacts',
      paginationMode: 'offset',
      fetchData: (query: OffsetQuery) => {
        const { limit, offset, search } = query;

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

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

        return queryBuilder
          .find()
          .then(({ items = [], totalCount: total }) => ({
            items: items.map((item) => ({
              id: item._id!,
              name: `${item.info?.name?.first} ${item.info?.name?.last}`,
            })),
            total,
          }));
      },
      filters: {},
    },
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          AddApplyFiltersButton={AddApplyFiltersButton}
          filters={
            <CollectionToolbarFilters inline={0}>
              <MultiSelectCollectionFilter
                filter={collection.filters.contacts}
                collection={contactsCollection}
                accordionItemProps={{ label: 'Contacts' }}
                initialFetchTiming="focus"
              />
              <DateRangeFilter
                filter={collection.filters.lastActivity}
                accordionItemProps={{ label: 'Last Activity' }}
              />
            </CollectionToolbarFilters>
          }
          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: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
          ]}
          actionCell={(contact, index) => ({
            secondaryActions: [
              {
                icon: <Edit />,
                text: 'Edit',
                disabled:
                  index % 5 === 0 || contact.info?.name?.first?.startsWith('A'),
                onClick: () => {},
              },
            ],
          })}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Customize filter display tags

Customize the text of the tags displayed when a user selects a filter item. 
 
 Set the `itemName` and `renderItem` property for each filter. 
 
 Default: Filter item name

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { Edit } from '@wix/wix-ui-icons-common';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionToolbarFilters,
  idNameArrayFilter,
  Table,
  MultiSelectCheckboxFilter,
  SingleSelectFilter,
  stringsArrayFilter,
  useTableCollection,
  Filter,
  useStaticListFilterCollection,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactFilters = {
  locale: Filter<{ id: string; name: string }[]>;
  level: Filter<string[]>;
};

function CustomizeSelectedTagsContent() {

  const state = useTableCollection<contacts.Contact, ContactFilters>({
    queryName: 'CustomizeSelectedTagsContent',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchData: (query: OffsetQuery<ContactFilters>) => {
      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);
      }

      if (filters.locale) {
        queryBuilder = queryBuilder.in(
          'info.locale',
          filters.locale.map(({ id }) => id),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    filters: {
      level: stringsArrayFilter({ itemName: (item) => `Level: ${item}` }),
      locale: idNameArrayFilter({
        itemName: (item) => `Locale: ${item.name}`,
      }),
    },
    fetchErrorMessage: ({ err }) => String(err),
  });
  const collection = state.collection;
  const localeCollection = useStaticListFilterCollection(
    collection.filters.locale,
    [
      { id: 'en-US', name: 'en-US' },
      { id: 'he-IL', name: 'he-IL' },
      { id: 'fr-FR', name: 'fr-FR' },
      { id: 'es-ES', name: 'es-ES' },
      { id: 'de-DE', name: 'de-DE' },
    ],
  );

  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          filters={
            <CollectionToolbarFilters useNewFilters>
              <MultiSelectCheckboxFilter
                accordionItemProps={{ label: 'Level' }}
                popoverProps={{ appendTo: 'window' }}
                filter={collection.filters.level!}
                collection={levelsCollection}
                renderItem={(level) => ({ title: level })}
                renderToolbarTag={(item) => ({
                  children: `Title: ${item}`,
                })}
              />
              <SingleSelectFilter
                filter={collection.filters.locale}
                placeholder="Select Locale"
                accordionItemProps={{ label: 'Locale Single' }}
                renderItem={(item) => ({ title: item.name })}
                collection={localeCollection}
                renderToolbarTag={(item) => ({
                  children: `The locale is: ${item.name}`,
                })}
              />
            </CollectionToolbarFilters>
          }
          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: 'Locale',
              render: (contact) => contact.info?.locale,
            },
            {
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
          ]}
          actionCell={(contact, index) => ({
            secondaryActions: [
              {
                icon: <Edit />,
                text: 'Edit',
                disabled:
                  index % 5 === 0 || contact.info?.name?.first?.startsWith('A'),
                onClick: () => {},
              },
            ],
          })}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Add filter presets for future dates.

```tsx
import { Avatar } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionToolbarFilters,
  CollectionEmptyState,
  DateRangeFilter,
  dateRangeFilter,
  OffsetQuery,
  Filter,
  RangeItem,
  useCollection,
} from '@wix/patterns';
import { TableVirtual } from '../../../src';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  lastActivity: Filter<RangeItem<Date>>;
};

function FutureDateRangeFilter() {
  const collection = useCollection<contacts.Contact, ContactsFilters>({
    queryName: 'contacts-Basic',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    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.lastActivity) {
        queryBuilder = queryBuilder
          .gt('lastActivity.activityDate', filters.lastActivity.from)
          .lt('lastActivity.activityDate', filters.lastActivity.to);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    fetchErrorMessage: ({ err }) => String(err),
    filters: {
      lastActivity: dateRangeFilter(),
    },
  });

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <TableVirtual
          state={collection}
          rowHeight={78}
          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: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
          ]}
          emptyState={<CollectionEmptyState />}
          filters={
            <CollectionToolbarFilters
              panelTitle="Filter your contacts"
              useNewFilters
            >
              <DateRangeFilter
                filter={collection.filters.lastActivity}
                accordionItemProps={{ label: 'Last Activity' }}
                mode="COMBINE"
                dateRangeOptions={[
                  'SEVEN_DAYS',
                  'FOURTEEN_DAYS',
                  'MONTH',
                  'TODAY',
                  'TOMORROW',
                  'NEXT_THREE_DAYS',
                  'NEXT_SEVEN_DAYS',
                  'NEXT_THIRTY_DAYS',
                ]}
              />
            </CollectionToolbarFilters>
          }
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Mini toolbar

To create a mini toolbar, set the `width` prop of the `` component to a minimum of `228px`.

```tsx
import { Avatar, Box } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionEmptyState,
  Table,
  OffsetQuery,
  useOptimisticActions,
  useTableCollection,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

function MinifiedToolbarScroll() {
  const state = useTableCollection<contacts.Contact>({
    queryName: 'MinifiedToolbarScroll',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;

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

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

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    fetchErrorMessage: () => 'Error fetching contacts',
    limit: 50,
    filters: {},
  });
  const collection = state.collection;
  useOptimisticActions(collection);

  return (
    <CollectionPage height="622px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Box direction="vertical" width="390px" height="482px">
          <Table
            state={collection}
            horizontalScroll
            internalScroll
            columns={[
              {
                id: 'avatar',
                title: '',
                hideable: true,
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                title: 'Name',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
            ]}
            actionCell={() => {
              return {
                primaryAction: {
                  text: 'Edit',
                  onClick: () => {},
                },
              };
            }}
            emptyState={<CollectionEmptyState />}
          />
        </Box>
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Mini toolbar with filters

To create a mini toolbar, set the `width` prop of the `` component to a minimum of `228px`. To add filters, set the Table component's `filters` prop.

```tsx
import { Avatar, Box } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionEmptyState,
  CollectionToolbarFilters,
  Filter,
  OffsetQuery,
  SingleSelectFilter,
  stringsArrayFilter,
  Table,
  useTableCollection,
  useOptimisticActions,
  useStaticListFilterCollection,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

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

function MinifiedToolbarSearchAndFilters() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'MinifiedToolbarScroll',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    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,
      }));
    },
    fetchErrorMessage: () => 'Error fetching contacts',
    limit: 50,
    filters: {
      level: stringsArrayFilter(),
    },
  });
  const collection = state.collection;
  useOptimisticActions(collection);

  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
      'Ye',
    ],
  );

  return (
    <CollectionPage height="622px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Box direction="vertical" width="390px" height="482px">
          <Table
            state={collection}
            internalScroll
            filters={
              <CollectionToolbarFilters inline={0}>
                <SingleSelectFilter
                  accordionItemProps={{
                    label: 'Contact Level',
                  }}
                  border="round"
                  filter={collection.filters.level}
                  collection={levelsCollection}
                />
              </CollectionToolbarFilters>
            }
            columns={[
              {
                id: 'avatar',
                title: '',
                hideable: true,
                width: '50px',
                render: (contact) => (
                  <Avatar
                    name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                    imgProps={{ src: contact.info?.picture?.image }}
                  />
                ),
              },
              {
                title: 'Name',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
            ]}
            actionCell={() => {
              return {
                primaryAction: {
                  text: 'Edit',
                  onClick: () => {},
                },
              };
            }}
            emptyState={<CollectionEmptyState />}
          />
        </Box>
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `inline` | `0 \| 1 \| 2 \| 3` | No | - | Number of filter controls to be rendered inside the table toolbar |
| `useNewFilters` | `true` | No | - | A flag that indicates whether Patterns should use a new filters layout our not. |
| `panelTitle` | `ReactNode` | No | - | Title of the filters panel |
| `children` | `boolean \| FilterElement<any> \| (boolean \| FilterElement<any> \| undefined)[] \| undefined` | Yes | - | `React` children elements, that must accept [FilterProps](./?path=/story/features-filter-props--filterprops), such as: - [MultiSelectCheckboxFilter](./?path=/story/features-filter-components--multiselectcheckboxfilter) - [Custom Filters](./?path=/story/features-filter-factories--customfilter) |

## BI

### Toolbar Filters Events

 Event   |   Description   |
--------- | --------------- |
[144:117](https://bo.wix.com/data-tools/bi-catalog-app/event/144:117) | Sent when a user triggers/removes a filter from a Wix Patterns component
[144:118](https://bo.wix.com/data-tools/bi-catalog-app/event/144:118) | Sent when a user clears all of the filters used in a Wix Patterns component
[144:119](https://bo.wix.com/data-tools/bi-catalog-app/event/144:119) |  Sent when a feature sidebar/menu in a Wix Patterns component is opened or closed



### 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)


