UIFilterDropdown
UIFilterDropdown
A powerful filter dropdown component that enables users to apply and manage multiple filters with support for different filter types, operators, and values.
<template> <UIFilterDropdown v-model="filters" :options="filterOptions" :operators="operators" :presets="filterPresets" placeholder="Add filters" @change="handleFilterChange" > <template #trigger="{ activeFilters }"> <UIButton type="default"> <FilterIcon class="w-5 h-5 mr-2" /> {{ activeFilters.length ? `${activeFilters.length} filters` : 'Filter' }} </UIButton> </template> </UIFilterDropdown></template>
<script setup lang="ts">interface FilterOption { id: string label: string type: 'text' | 'number' | 'select' | 'date' | 'boolean' operators?: string[] options?: { label: string; value: any }[]}
const filters = ref([])
const filterOptions: FilterOption[] = [ { id: 'name', label: 'Name', type: 'text', operators: ['contains', 'equals', 'starts_with', 'ends_with'] }, { id: 'status', label: 'Status', type: 'select', options: [ { label: 'Active', value: 'active' }, { label: 'Inactive', value: 'inactive' }, { label: 'Pending', value: 'pending' } ] }, { id: 'created_at', label: 'Created Date', type: 'date', operators: ['equals', 'before', 'after', 'between'] }]
const operators = { text: [ { label: 'Contains', value: 'contains' }, { label: 'Equals', value: 'equals' }, { label: 'Starts with', value: 'starts_with' }, { label: 'Ends with', value: 'ends_with' } ], number: [ { label: 'Equals', value: 'equals' }, { label: 'Greater than', value: 'gt' }, { label: 'Less than', value: 'lt' }, { label: 'Between', value: 'between' } ]}
const filterPresets = [ { id: 'active_recent', label: 'Active & Recent', filters: [ { field: 'status', operator: 'equals', value: 'active' }, { field: 'created_at', operator: 'after', value: '2024-01-01' } ] }]</script>Props
modelValue(array): Active filtersoptions(array): Filter field optionsoperators(object): Available operators by typepresets(array): Predefined filter combinationsplaceholder(string): Input placeholderdisabled(boolean): Disable componentloading(boolean): Loading statemaxFilters(number): Maximum number of filterssearchable(boolean): Enable field searchclearable(boolean): Allow clearing filtersplacement(string): Dropdown placementsize(‘sm’ | ‘md’ | ‘lg’): Component sizewidth(number | string): Dropdown width
Events
update:modelValue: Filters changedchange: Filters changedpreset: Preset appliedclear: Filters clearedadd: Filter addedremove: Filter removedsearch: Field search
Slots
trigger: Custom trigger contentempty: Empty state contentfield: Custom field renderoperator: Custom operator rendervalue: Custom value renderpreset: Custom preset render
Usage Examples
- Basic Filter Dropdown:
<template> <UIFilterDropdown v-model="filters" :options="[ { id: 'status', label: 'Status', type: 'select', options: [ { label: 'Active', value: 'active' }, { label: 'Inactive', value: 'inactive' } ] }, { id: 'type', label: 'Type', type: 'select', options: [ { label: 'User', value: 'user' }, { label: 'Admin', value: 'admin' } ] } ]" /></template>
<script setup>const filters = ref([])</script>- Advanced Data Table Filters:
<template> <div class="space-y-4"> <div class="flex items-center justify-between"> <UIFilterDropdown v-model="tableFilters" :options="filterOptions" :operators="operators" :loading="loading" class="w-full max-w-xl" > <template #trigger="{ activeFilters }"> <UIButton type="default" class="w-full justify-between" > <div class="flex items-center"> <FilterIcon class="w-5 h-5 mr-2" /> {{ activeFilters.length ? `${activeFilters.length} filters applied` : 'Add filters' }} </div> <ChevronDownIcon class="w-5 h-5" /> </UIButton> </template>
<template #preset="{ preset, active, apply }"> <div class="flex items-center justify-between p-2 hover:bg-gray-50 cursor-pointer" :class="{ 'bg-primary-50': active }" @click="apply" > <div> <div class="font-medium"> {{ preset.label }} </div> <div class="text-sm text-gray-500"> {{ preset.description }} </div> </div>
<CheckIcon v-if="active" class="w-5 h-5 text-primary-500" /> </div> </template> </UIFilterDropdown>
<UIButton v-if="tableFilters.length" variant="ghost" class="ml-2" @click="clearFilters" > Clear </UIButton> </div>
<div v-if="tableFilters.length" class="flex flex-wrap gap-2" > <UITag v-for="filter in tableFilters" :key="filter.id" :closable="true" @close="removeFilter(filter)" > {{ formatFilter(filter) }} </UITag> </div>
<UITable :data="filteredData" :columns="columns" :loading="loading" /> </div></template>
<script setup>const formatFilter = (filter) => { const field = filterOptions.find(f => f.id === filter.field) const operator = operators[field.type].find(o => o.value === filter.operator) return `${field.label} ${operator.label} ${filter.value}`}</script>- Custom Filter Types:
<template> <UIFilterDropdown v-model="filters" :options="customFilters" > <template #value="{ field, value, onChange }"> <template v-if="field.type === 'price'"> <div class="flex items-center space-x-2"> <span class="text-gray-500">$</span> <UIInput v-model="value.min" type="number" placeholder="Min" class="w-24" @update:modelValue="v => onChange({ ...value, min: v })" /> <span class="text-gray-500">to</span> <UIInput v-model="value.max" type="number" placeholder="Max" class="w-24" @update:modelValue="v => onChange({ ...value, max: v })" /> </div> </template>
<template v-else-if="field.type === 'rating'"> <UIRating v-model="value" :max="5" @update:modelValue="onChange" /> </template> </template> </UIFilterDropdown></template>
<script setup>const customFilters = [ { id: 'price', label: 'Price Range', type: 'price', defaultValue: { min: null, max: null } }, { id: 'rating', label: 'Rating', type: 'rating' }]</script>Best Practices
-
User Experience:
- Clear labels
- Intuitive operators
- Visual feedback
- Keyboard support
-
Performance:
- Debounced updates
- Efficient rendering
- Lazy loading
- Memory management
-
Accessibility:
- ARIA labels
- Focus management
- Screen reader support
- Keyboard navigation
-
Mobile:
- Touch targets
- Responsive layout
- Clear feedback
- Simplified interaction
Common Use Cases
- Data Table Filters:
<template> <div class="flex items-center space-x-4"> <UISearch v-model="search" placeholder="Search..." class="w-64" />
<UIFilterDropdown v-model="filters" :options="filterOptions" :operators="operators" class="flex-1" />
<UIButton type="primary" :loading="loading" @click="applyFilters" > Apply </UIButton> </div></template>- Analytics Dashboard:
<template> <div class="space-y-4"> <div class="flex items-center justify-between"> <h2 class="text-lg font-medium"> Analytics </h2>
<div class="flex items-center space-x-2"> <UIFilterDropdown v-model="dateFilter" :options="[ { id: 'date_range', label: 'Date Range', type: 'daterange' } ]" > <template #trigger> <UIButton type="default"> <CalendarIcon class="w-5 h-5 mr-2" /> {{ formatDateRange(dateFilter[0]?.value) }} </UIButton> </template> </UIFilterDropdown>
<UIFilterDropdown v-model="metricFilters" :options="metricOptions" :presets="metricPresets" /> </div> </div>
<AnalyticsChart :data="filteredData" :filters="metricFilters" /> </div></template>- E-commerce Filters:
<template> <div class="flex items-start space-x-4"> <div class="w-64 space-y-4"> <UIFilterDropdown v-model="productFilters" :options="[ { id: 'category', label: 'Category', type: 'tree', options: categories }, { id: 'price', label: 'Price', type: 'range', min: 0, max: 1000, step: 10 }, { id: 'rating', label: 'Rating', type: 'rating' } ]" placement="bottom-start" class="w-full" > <template #value="{ field, value, onChange }"> <template v-if="field.type === 'range'"> <UISlider v-model="value" :min="field.min" :max="field.max" :step="field.step" range @update:modelValue="onChange" /> </template> </template> </UIFilterDropdown>
<div v-if="productFilters.length" class="space-y-2" > <div class="text-sm font-medium"> Active Filters </div>
<div class="space-y-1"> <UITag v-for="filter in productFilters" :key="filter.id" :closable="true" size="sm" class="w-full" @close="removeFilter(filter)" > {{ formatProductFilter(filter) }} </UITag> </div> </div> </div>
<div class="flex-1"> <ProductGrid :products="filteredProducts" :loading="loading" /> </div> </div></template>Component Composition
The UIFilterDropdown component works well with:
- UIButton for triggers
- UIInput for text/number filters
- UISelect for dropdown filters
- UIDatePicker for date filters
- UISlider for range filters
- UITag for active filters
- UITooltip for help text
- UIPopover for complex filters