# TabsFilter

**Category:** Features/Filter/Components

## Design

### Description

Filter component that always renders in the toolbar.

Make sure to check your tab names in other languages. Default tab names may be short in English but not in other languages.

Stay away from using:
- Tabs and table filters together.
- More than four tabs.

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

---

### Variations

### Basic Tabs

This example uses the `` `data` prop to include filter tabs in the table.

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

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

function TabsFilterExample() {

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'TabsFilterExample',
    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.map(({ id }) => id),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: ({ err }) => String(err),
    filters: {
      level: idNameArrayFilter({
        initialValue: [{ id: 'Beginner', name: 'Beginner' }],
      }),
    },
  });
  const collection = state.collection;
  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          tabs={
            <TabsFilter
              filter={collection.filters.level}
              data={[
                { id: 'Beginner', name: 'Beginner' },
                { id: 'Professional', name: 'Professional' },
                { id: 'World Class', name: 'World Class' },
              ]}
              all="All Levels"
            />
          }
          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(),
            },
          ]}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Tabs with a basic counter

This example uses the `` `counterBadge` prop to include a counter badge in the table's filter tabs.

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

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

function TabsFilterTotalParensExample() {
  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'TabsFilterTotalParensExample',
    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.map(({ id }) => id),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: ({ err }) => String(err),
    filters: {
      level: idNameArrayFilter({
        initialValue: [{ id: 'Beginner', name: 'Beginner' }],
      }),
    },
  });
  const collection = state.collection;
  const optimisticActions = useOptimisticActions(collection, {
    predicate: ({ filters }) => {
      return (contact) => {
        if (
          Array.isArray(filters.level) &&
          filters.level.length &&
          filters.level.every((e) => e.id !== contact.info?.jobTitle)
        ) {
          return false;
        }

        return true;
      };
    },
  });
  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          actionCell={(item) => ({
            primaryAction:
              item.info?.jobTitle !== 'World Class'
                ? {
                    text: 'World Class',
                    onClick: () => {
                      const updatedContacts = [
                        {
                          ...item,
                          info: {
                            ...item.info,
                            jobTitle: 'World Class',
                          },
                        },
                      ];
                      optimisticActions.updateMany(updatedContacts, {
                        submit: (_contacts) =>
                          Promise.all(
                            _contacts.map((contact) =>
                              contacts.updateContact(
                                contact._id as string,
                                contact.info as contacts.ContactInfo,
                                contact.revision as number,
                              ),
                            ),
                          ),
                      });
                    },
                  }
                : undefined,
          })}
          tabs={
            <TabsFilter
              filter={collection.filters.level}
              data={[
                { id: 'Beginner', name: 'Beginner' },
                { id: 'Professional', name: 'Professional' },
                { id: 'World Class', name: 'World Class' },
              ]}
              all="All Levels"
              counterBadge={(item) => (
                <TabTotalParens
                  optimisticActions={optimisticActions}
                  value={item}
                />
              )}
            />
          }
          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(),
            },
          ]}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### Tabs with a counter badge

This example uses the `` `counterBadge` prop to include a counter badge in the table's filter tabs.

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

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

function TabsFilterTotalBadgeExample() {
  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'TabsFilterTotalBadgeExample',
    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.map(({ id }) => id),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: ({ err }) => String(err),
    filters: {
      level: idNameArrayFilter({
        initialValue: [{ id: 'Beginner', name: 'Beginner' }],
      }),
    },
  });
  const collection = state.collection;
  const optimisticActions = useOptimisticActions(collection, {
    predicate: ({ filters }) => {
      return (contact) => {
        if (
          Array.isArray(filters.level) &&
          filters.level.length &&
          filters.level.every((e) => e.id !== contact.info?.jobTitle)
        ) {
          return false;
        }

        return true;
      };
    },
  });

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          actionCell={(item) => ({
            primaryAction:
              item.info?.jobTitle !== 'World Class'
                ? {
                    text: 'World Class',
                    onClick: () => {
                      const updatedContacts = [
                        {
                          ...item,
                          info: {
                            ...item.info,
                            jobTitle: 'World Class',
                          },
                        },
                      ];

                      optimisticActions.updateMany(updatedContacts, {
                        submit: (_contacts) =>
                          Promise.all(
                            _contacts.map((contact) =>
                              contacts.updateContact(
                                contact._id as string,
                                contact.info as contacts.ContactInfo,
                                contact.revision as number,
                              ),
                            ),
                          ),
                      });
                    },
                  }
                : undefined,
          })}
          tabs={
            <TabsFilter
              filter={collection.filters.level}
              data={[
                { id: 'Beginner', name: 'Beginner' },
                { id: 'Professional', name: 'Professional' },
                { id: 'World Class', name: 'World Class' },
              ]}
              all="All Levels"
              counterBadge={(item) => (
                <TabTotalCounterBadge
                  optimisticActions={optimisticActions}
                  value={item}
                />
              )}
            />
          }
          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(),
            },
          ]}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `data` | `V[]` | Yes | - | List of tabs. |
| `filter` | `Filter<V[]>` | Yes | - | Defines the selected filter. <br><br> Must be an [arrayFilter](./?path=/story/features-filter-factories--arrayfilter) object. |
| `all` | `string \| false \| null` | No | - | Display name of the tab that indicates that all items are included. <br> Changes the value of `filter` to an empty array. |
| `counterBadge` | `((value: V \| null) => TabTotalCounterBadge<V> \| TabTotalParens<V> \| null) \| undefined` | No | - | Renders a counter badge next to the tabs' texts. <br><br> See different examples of this implementation in the [Overview](./?path=/story/features-filter-components--tabsfilter) tab. |
| `type` | `"uniformFull"` | No | - | Tabs layout type. <br><br> If this prop is omitted, the width depends on the label width. The total width of all the tabs cannot exceed 80% of the toolbar. <br><br> Supported values:<br> - `uniformFull`: Tabs are spread out uniformly. |
| `mobilePopoverProps` | `PopoverCommonProps` | No | - | Custom popover props for the mobile dropdown menu. On mobile devices, tabs are displayed as a dropdown menu. |

## BI

### Tabs Filter Events

  Event   |   Description   |
--------- | --------------- |
[144:121](https://bo.wix.com/data-tools/bi-catalog-app/event/144:121) | Sent when a user selects a tab 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

`getToolbarTabs` API is exposed from the `` [testkit](./?path=/story/base-components-collections-table-table--table&activeTab=Testkit)


