AdvancedSearch
Usage
AdvancedSearch is a wrapper around the CommandPalette component and, for the most part, uses the same structure. Use the v-model directive to control the checked option.
<template>
<MeAdvancedSearch
:provider="provider"
>
<UButton
color="neutral"
variant="subtle"
>
Open
</UButton>
<template #content-top>
<UButton
color="neutral"
variant="outline"
:ui="{ base: 'rounded-sm' }"
>
Action A
</UButton>
<UButton
color="neutral"
variant="outline"
:ui="{ base: 'rounded-sm' }"
>
Action B
</UButton>
</template>
<template #footer>
<UButton :ui="{ base: 'rounded-sm' }">
Footer A
</UButton>
</template>
</MeAdvancedSearch>
</template>
<script setup>
function provider(args) {
if (!args) {
return new Promise((resolve) => {
resolve([{
id: 'group1',
label: 'Group 1',
items: [{
label: 'Group item 1',
id: 'group-item-1',
suffix: 'Group item 1 suffix',
children: true
}]
}, {
id: 'criterion',
label: 'Criterion',
items: [{
label: 'Name',
id: 'name',
suffix: 'Filter by name',
fieldType: 'string',
children: [
{
id: 'equal',
operatorKey: 'equal',
children: [
{ id: 'john-doe', label: 'John Doe', suffix: 'John Doe username' },
{ id: 'jane-doe', label: 'Jane Doe', suffix: 'Jane Doe username' }
]
},
{
id: 'not_equal',
operatorKey: 'not_equal',
children: [
{ id: 'document', label: '123456789', suffix: 'Document number' },
{ id: 'john-doe-email', label: 'john.doe@example.com', suffix: 'Email' }
]
}
]
}]
}])
})
}
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 'item-1', label: 'Item 1', suffix: 'Item 1 suffix' },
{ id: 'item-2', label: 'Item 2', suffix: 'Item 2 suffix' },
{ id: 'item-3', label: 'Item 3', suffix: 'Item 3 suffix' }
])
}, 1000)
})
}
</script>
Groups
Use the provider prop to define the groups. It must be a function that returns a Promise. When a group describes a criterion, its id must be "criterion" and its children can include the fieldType and operatorKey keys (see CriterionInput). This structure guides the component to correctly construct the criterion as desired by the user. When a criterion is fully created, the criterionSelect event is emitted with the AdvancedSearchCriterion object as payload.
AdvancedSearch also uses internal predefined IDs for its built-in groups that should not be reused as the ID for any group returned by the provider:
"criterion""genius""genius-items""recent-search""recent-search-item""content-top-slot-group""empty-slot-group""empty-slot-item"
If the children field of an AdvancedSearchItem is true, the provider will be called with AdvancedSearchProviderArgs to retrieve an array of children.
type provider = {
(): Promise<AdvancedSearchGroup<C>[]>
(args: AdvancedSearchProviderArgs<C>): Promise<AdvancedSearchItem<C>[]>
}
type AdvancedSearchProviderArgs<C extends boolean = false> = {
groupId: string
parentItemId?: string
selectedItem: AdvancedSearchItem<C>
}
type AdvancedSearchGroup<C extends boolean = false> = Omit<CommandPaletteGroup, 'items'> & {
items: AdvancedSearchItem<C>[]
}
type AdvancedSearchItem<C extends boolean = false> = Omit<CommandPaletteItem, 'children'> & {
id: string
children?: true | AdvancedSearchItem<C>[]
} & (C extends true ? {
fieldType?: MeFieldTypesValues
operatorKey?: MeOperatorKeysValues
} : Record<never, never>)
type AdvancedSearchCriterion = {
fieldId: string
operatorKey: MeOperatorKeysValues
value?: AdvancedSearchItem<true>
}
<template>
<MeAdvancedSearch :provider="provider">
<UButton
color="neutral"
variant="subtle"
>
Open
</UButton>
</MeAdvancedSearch>
</template>
<script setup lang="ts">
function provider(args) {
if (!args) {
return new Promise((resolve) => {
resolve([{
id: 'group1',
label: 'Group 1',
items: [{
label: 'Group item 1',
id: 'group-item-1',
suffix: 'Group item 1 suffix',
children: true
}]
}, {
id: 'criterion',
label: 'Criterion',
items: [{
label: 'Name',
id: 'name',
suffix: 'Filter by name',
fieldType: 'string',
children: [
{
id: 'equal',
operatorKey: 'equal',
children: [
{ id: 'john-doe', label: 'John Doe', suffix: 'John Doe username' },
{ id: 'jane-doe', label: 'Jane Doe', suffix: 'Jane Doe username' }
]
},
{
id: 'not_equal',
operatorKey: 'not_equal',
children: [
{ id: '123456789', label: 'Document', suffix: 'Document number' },
{ id: 'john-doe-email', label: 'john.doe@example.com', suffix: 'Email' }
]
}
]
}]
}])
})
}
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 'item-1', label: 'Item 1', suffix: 'Item 1 suffix' },
{ id: 'item-2', label: 'Item 2', suffix: 'Item 2 suffix' },
{ id: 'item-3', label: 'Item 3', suffix: 'Item 3 suffix' }
])
}, 1000)
})
}
</script>
Hide groups
Use the hideGroups prop to hide custom or built-in groups.
<template>
<MeAdvancedSearch
:provider="provider"
:hide-groups="['criterion']"
>
<UButton
color="neutral"
variant="subtle"
>
Open
</UButton>
</MeAdvancedSearch>
</template>
<script>
function provider(args) {
if (!args) {
return new Promise((resolve) => {
resolve([{
id: 'group1',
label: 'Group 1',
items: [{
label: 'Group item 1',
id: 'group-item-1',
suffix: 'Group item 1 suffix',
children: true
}]
}, {
id: 'criterion',
label: 'Criterion',
items: [{
label: 'Name',
id: 'name',
suffix: 'Filter by name',
fieldType: 'string',
children: [
{
id: 'equal',
operatorKey: 'equal',
children: [
{ id: 'john-doe', label: 'John Doe', suffix: 'John Doe username' },
{ id: 'jane-doe', label: 'Jane Doe', suffix: 'Jane Doe username' }
]
},
{
id: 'not_equal',
operatorKey: 'not_equal',
children: [
{ id: '123456789', label: 'Document', suffix: 'Document number' },
{ id: 'john-doe-email', label: 'john.doe@example.com', suffix: 'Email' }
]
}
]
}]
}])
})
}
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 'item-1', label: 'Item 1', suffix: 'Item 1 suffix' },
{ id: 'item-2', label: 'Item 2', suffix: 'Item 2 suffix' },
{ id: 'item-3', label: 'Item 3', suffix: 'Item 3 suffix' }
])
}, 1000)
})
}
</script>
Search
Use the placeholder prop and searchTerm model to customize the search.
<template>
<MeAdvancedSearch
v-model:search-term="searchTerm"
:provider="provider"
placeholder="Search"
>
<UButton
color="neutral"
variant="subtle"
>
Open
</UButton>
</MeAdvancedSearch>
</template>
<script setup lang="ts">
const searchTerm = ref('')
function provider(args) {
if (!args) {
return new Promise((resolve) => {
resolve([{
id: 'group1',
label: 'Group 1',
items: [{
label: 'Group item 1',
id: 'group-item-1',
suffix: 'Group item 1 suffix',
children: true
}]
}, {
id: 'criterion',
label: 'Criterion',
items: [{
label: 'Name',
id: 'name',
suffix: 'Filter by name',
fieldType: 'string',
children: [
{
id: 'equal',
operatorKey: 'equal',
children: [
{ id: 'john-doe', label: 'John Doe', suffix: 'John Doe username' },
{ id: 'jane-doe', label: 'Jane Doe', suffix: 'Jane Doe username' }
]
},
{
id: 'not_equal',
operatorKey: 'not_equal',
children: [
{ id: '123456789', label: 'Document', suffix: 'Document number' },
{ id: 'john-doe-email', label: 'john.doe@example.com', suffix: 'Email' }
]
}
]
}]
}])
})
}
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 'item-1', label: 'Item 1', suffix: 'Item 1 suffix' },
{ id: 'item-2', label: 'Item 2', suffix: 'Item 2 suffix' },
{ id: 'item-3', label: 'Item 3', suffix: 'Item 3 suffix' }
])
}, 1000)
})
}
</script>
Empty
Use the empty prop to customize the empty state when there are no search results and the genius group is hidden. It render a Empty component.
type empty = Pick<EmptyProps, 'title' | 'description' | 'actions'>
/*
title?: string
description?: string
actions?: ButtonProps[]
*/
<template>
<MeAdvancedSearch
:provider="provider"
:hide-groups="['genius']"
:empty="{
title: 'No results found.',
description: 'No results found for the current search.',
actions: [
{ label: 'Action A', variant: 'solid', color: 'primary' },
{ label: 'Action B', variant: 'subtle', color: 'neutral' }
]
}"
>
<UButton
color="neutral"
variant="subtle"
>
Open
</UButton>
</MeAdvancedSearch>
</template>
<script setup lang="ts">
function provider() {
return new Promise((resolve) => {
resolve([{
id: 'group1',
label: 'Group 1',
items: [{
label: 'Group item 1',
id: 'group-item-1',
suffix: 'Group item 1 suffix',
children: [
{ id: 'item-1', label: 'Item 1', suffix: 'Item 1 suffix' },
{ id: 'item-2', label: 'Item 2', suffix: 'Item 2 suffix' },
{ id: 'item-3', label: 'Item 3', suffix: 'Item 3 suffix' }
]
}]
}, {
id: 'criterion',
label: 'Criterion',
items: [{
label: 'Name',
id: 'name',
suffix: 'Filter by name',
fieldType: 'string',
children: [
{
id: 'equal',
operatorKey: 'equal',
children: [
{ id: 'john-doe', label: 'John Doe', suffix: 'John Doe username' },
{ id: 'jane-doe', label: 'Jane Doe', suffix: 'Jane Doe username' }
]
},
{
id: 'not_equal',
operatorKey: 'not_equal',
children: [
{ id: 'document', label: '123456789', suffix: 'Document number' },
{ id: 'john-doe-email', label: 'john.doe@example.com', suffix: 'Email' }
]
}
]
}]
}])
})
}
</script>
Custom content
Use the content-top, footer and empty slots to add custom content. The footer slot is scoped with a close function.
<template>
<MeAdvancedSearch
:provider="provider"
:hide-groups="['genius']"
>
<UButton
color="neutral"
variant="subtle"
>
Open
</UButton>
<template #empty>
<div class="w-full h-25 flex items-center justify-center">
<UButton
variant="solid"
color="primary"
>
Empty action
</UButton>
</div>
</template>
<template #content-top>
<UButton
color="neutral"
variant="outline"
:ui="{ base: 'rounded-sm' }"
>
Action A
</UButton>
<UButton
color="neutral"
variant="outline"
:ui="{ base: 'rounded-sm' }"
>
Action B
</UButton>
</template>
<template #footer="{ close }">
<UButton
:ui="{ base: 'rounded-sm' }"
@click="close"
>
Close
</UButton>
</template>
</MeAdvancedSearch>
</template>
<script setup lang="ts">
function provider(args) {
if (!args) {
return new Promise((resolve) => {
resolve([{
id: 'group1',
label: 'Group 1',
items: [{
label: 'Group item 1',
id: 'group-item-1',
suffix: 'Group item 1 suffix',
children: true
}]
}, {
id: 'criterion',
label: 'Criterion',
items: [{
label: 'Name',
id: 'name',
suffix: 'Filter by name',
fieldType: 'string',
children: [
{
id: 'equal',
operatorKey: 'equal',
children: [
{ id: 'john-doe', label: 'John Doe', suffix: 'John Doe username' },
{ id: 'jane-doe', label: 'Jane Doe', suffix: 'Jane Doe username' }
]
},
{
id: 'not_equal',
operatorKey: 'not_equal',
children: [
{ id: '123456789', label: 'Document', suffix: 'Document number' },
{ id: 'john-doe-email', label: 'john.doe@example.com', suffix: 'Email' }
]
}
]
}]
}])
})
}
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 'item-1', label: 'Item 1', suffix: 'Item 1 suffix' },
{ id: 'item-2', label: 'Item 2', suffix: 'Item 2 suffix' },
{ id: 'item-3', label: 'Item 3', suffix: 'Item 3 suffix' }
])
}, 1000)
})
}
</script>
API
Props
| Prop | Default | Type |
|---|---|---|
modelValue | AdvancedSearchItem The checked item, bind with v-model | |
searchTerm | '' | String The search input value, bind with v-model:search-term |
placeholder | 'Make a direct search or find a filter' | String The search input placeholder |
open | false | Boolean The open state, bind with v-model:open |
hideGroups | [] | String[] The IDs of the groups to omit |
provider | (): Promise<AdvancedSearchGroup<C>[]> | (args: AdvancedSearchProviderArgs<C>): Promise<AdvancedSearchItem<C>[]> | |
empty | { title?: string; description?: string; actions?: ButtonProps[] } |
Emits
| Event | Type |
|---|---|
searchEnter | [string] |
searchGenius | [string] |
searchRecent | [AdvancedSearchRecentSearch] |
criterionSelect | [AdvancedSearchCriterion] |
Slots
| Slot | Type |
|---|---|
default | {} |
content-top | {} |
empty | {} |
footer | { close: () => void } |