Skip to content

Filters

The FilterChip, FilterDropdown & FilterSelect components are meant to be composed together to filter a list of items. They are intended to be used within DataViewFilters.

Brands

Select All

The can-select-all boolean prop renders an additional "All" option for selecting all options.

Brands

Filter Dropdowns

FilterDropdown will emit an apply event. Downstream apps will be responsibile for keep track of filter values, and updating the active filter count.

INFO

Dev Note: Due to this responsibility, it is recommended to use a form state ref that is mapped to your filter form fields. Click the "Show Code" button below the demo to see the demo code.

In the demo below, the filter dropdown instances are labeled by "Type" and "Price".

Product Name
Brand
Category
Price per unit
Strain
No results
vue
<script setup lang="ts">
  import { onMounted, ref } from 'vue';

  import {
    brandOptions,
    fetchProducts as apiGetProducts,
    Product,
    strainOptions,
  } from '../../../src/components/DataView/DataView.fixtures';
  import DataView from '../../../src/components/DataView/DataView.vue';
  import DataViewFilters, {
    useFilters,
    UseFiltersSchema,
  } from '../../../src/components/DataViewFilters/DataViewFilters.vue';
  import DataViewToolbar from '../../../src/components/DataViewToolbar/DataViewToolbar.vue';
  import FilterDrawerItem from '../../../src/components/FilterDrawerItem/FilterDrawerItem.vue';
  import FilterDropdown from '../../../src/components/FilterDropdown/FilterDropdown.vue';
  import Input from '../../../src/components/Input/Input.vue';
  import Select from '../../../src/components/Select/Select.vue';
  import Table from '../../../src/components/Table/Table.vue';
  import TableCell from '../../../src/components/TableCell/TableCell.vue';
  import TableHeaderCell from '../../../src/components/TableHeaderCell/TableHeaderCell.vue';
  import TableRow from '../../../src/components/TableRow/TableRow.vue';
  import { money } from '../../../src/utils/i18n';

  const PAGE_SIZE = 4;

  const dataViewRef = ref<InstanceType<typeof DataView>>();
  const products = ref<Product[]>([]);
  const isLoadingProducts = ref(false);
  const totalProductCount = ref(products.value.length);

  // #region Filters
  interface FilterValues {
    strain?: (typeof strainOptions)[number];
    brand?: (typeof brandOptions)[number];
    priceMin?: string | number;
    priceMax?: string | number;
  }

  type FilterGroups = 'type' | 'price';

  const filterSchema: UseFiltersSchema<FilterValues, FilterGroups> = {
    strain: {
      group: 'type',
      defaultValue: strainOptions[1],
    },
    brand: {
      group: 'type',
    },
    priceMin: {
      group: 'price',
    },
    priceMax: {
      group: 'price',
      defaultValue: 2000,
    },
  };

  const useFiltersInstance = useFilters({ schema: filterSchema, dataViewRef });

  const { workingFilters, appliedFilters } = useFiltersInstance;
  // #endregion

  async function fetchProducts() {
    if (!dataViewRef.value || isLoadingProducts.value) {
      return;
    }

    try {
      isLoadingProducts.value = true;

      const response = await apiGetProducts({
        params: {
          page: dataViewRef.value.page || 1,
          pageSize: dataViewRef.value.pageSize || PAGE_SIZE,
          ordering: dataViewRef.value.ordering,
          search: dataViewRef.value.search,
          strain: appliedFilters.value.strain?.id,
          brandId: appliedFilters.value.brand?.id,
          priceMin: appliedFilters.value.priceMin,
          priceMax: appliedFilters.value.priceMax,
        },
      });

      products.value = response.results;
      totalProductCount.value = response.count;
    } finally {
      isLoadingProducts.value = false;
    }
  }

  onMounted(() => {
    fetchProducts();
  });
</script>

<template>
  <DataView
    ref="dataViewRef"
    variant="table"
    :data="products"
    :page-size="4"
    :total-data-count="totalProductCount"
    :is-loading="isLoadingProducts"
    :is-empty="products.length === 0"
    @update="fetchProducts()"
  >
    <DataViewFilters
      :use-filters-instance="useFiltersInstance"
      :search-bar-props="{
        hintText: 'Search for Product Name',
        placeholder: 'Ex: Citrus',
      }"
    >
      <FilterDropdown label="Type" group="type">
        <Select v-model="workingFilters.strain" label="Strain" single add-bottom-space :options="strainOptions" />
        <Select v-model="workingFilters.brand" label="Brand name" single add-bottom-space :options="brandOptions" />
      </FilterDropdown>
      <FilterDropdown label="Price" group="price">
        <Input v-model="workingFilters.priceMin" label="Minimum Price" type="number" add-bottom-space />
        <Input v-model="workingFilters.priceMax" label="Maximum Price" type="number" add-bottom-space />
      </FilterDropdown>
      <template #drawer>
        <div class="tw-p-4">
          <FilterDrawerItem title="Type" group="type">
            <Select v-model="workingFilters.strain" label="Strain" single add-bottom-space :options="strainOptions" />
            <Select v-model="workingFilters.brand" label="Brand name" single add-bottom-space :options="brandOptions" />
          </FilterDrawerItem>
          <FilterDrawerItem title="Price" group="price">
            <Input v-model="workingFilters.priceMin" label="Minimum Price" type="number" add-bottom-space />
            <Input v-model="workingFilters.priceMax" label="Maximum Price" type="number" add-bottom-space />
          </FilterDrawerItem>
        </div>
      </template>
    </DataViewFilters>

    <DataViewToolbar />

    <Table>
      <template #head>
        <tr>
          <TableHeaderCell class="tw-min-w-[300px]" sort-id="product_name">Product Name</TableHeaderCell>
          <TableHeaderCell class="tw-min-w-[140px]">Brand</TableHeaderCell>
          <TableHeaderCell sort-id="category_name">Category</TableHeaderCell>
          <TableHeaderCell sort-id="price_per_unit_amount">Price per unit</TableHeaderCell>
          <TableHeaderCell sort-id="strain_classification">Strain</TableHeaderCell>
        </tr>
      </template>
      <template #body>
        <TableRow v-for="product in products" :key="product.id">
          <TableCell>
            <div class="tw-flex tw-items-center">
              <img :src="product.featured_image" width="60" class="tw-mr-3 tw-rounded" />
              {{ product.name }}
            </div>
          </TableCell>
          <TableCell>
            {{ product.brand.name }}
          </TableCell>
          <TableCell>
            {{ product.category.name }}
          </TableCell>
          <TableCell>
            {{ money(product.price_per_unit) }}
          </TableCell>
          <TableCell class="show-empty">
            {{ product.strain_classification }}
          </TableCell>
        </TableRow>
      </template>
    </Table>
  </DataView>
</template>

Long content

The height of the dropdown is limited to 400px. If the content inside is greater than 400px, then a scrollbar will appear.

API

See the documentation below for a complete reference to all the props and classes available to the components mentioned here.