mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-28 10:50:40 +01:00
feat(SelectMenu): handle async search (#426)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
committed by
GitHub
parent
46b444a3e0
commit
5f8fe8559f
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup>
|
||||||
|
const search = async (q) => {
|
||||||
|
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||||
|
|
||||||
|
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email })).filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = ref([])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<USelectMenu
|
||||||
|
v-model="selected"
|
||||||
|
:searchable="search"
|
||||||
|
placeholder="Search for a user..."
|
||||||
|
multiple
|
||||||
|
by="id"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -149,6 +149,40 @@ props:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Async search
|
||||||
|
|
||||||
|
Pass a function to the `searchable` prop to customize the search behavior and filter options according to your needs. The function will receive the query as its first argument and should return an array.
|
||||||
|
|
||||||
|
Use the `debounce` prop to adjust the delay of the function.
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
#default
|
||||||
|
:select-menu-example-async-search{class="max-w-[12rem] w-full"}
|
||||||
|
|
||||||
|
#code
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
const search = async (q) => {
|
||||||
|
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||||
|
|
||||||
|
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email })).filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = ref([])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<USelectMenu
|
||||||
|
v-model="selected"
|
||||||
|
:searchable="search"
|
||||||
|
placeholder="Search for a user..."
|
||||||
|
multiple
|
||||||
|
by="id"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
## Slots
|
## Slots
|
||||||
|
|
||||||
### `label`
|
### `label`
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ import {
|
|||||||
ListboxOptions as HListboxOptions,
|
ListboxOptions as HListboxOptions,
|
||||||
ListboxOption as HListboxOption
|
ListboxOption as HListboxOption
|
||||||
} from '@headlessui/vue'
|
} from '@headlessui/vue'
|
||||||
|
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import UIcon from '../elements/Icon.vue'
|
import UIcon from '../elements/Icon.vue'
|
||||||
import UAvatar from '../elements/Avatar.vue'
|
import UAvatar from '../elements/Avatar.vue'
|
||||||
@@ -219,13 +220,17 @@ export default defineComponent({
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
searchable: {
|
searchable: {
|
||||||
type: Boolean,
|
type: [Boolean, Function] as PropType<boolean | ((query: string) => Promise<any[]> | any[])>,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
searchablePlaceholder: {
|
searchablePlaceholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'Search...'
|
default: 'Search...'
|
||||||
},
|
},
|
||||||
|
debounce: {
|
||||||
|
type: Number,
|
||||||
|
default: 200
|
||||||
|
},
|
||||||
creatable: {
|
creatable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@@ -373,15 +378,23 @@ export default defineComponent({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredOptions = computed(() =>
|
const debouncedSearch = typeof props.searchable === 'function' ? useDebounceFn(props.searchable, props.debounce) : undefined
|
||||||
query.value === ''
|
|
||||||
? props.options
|
const filteredOptions = computedAsync(async () => {
|
||||||
: (props.options as any[]).filter((option: any) => {
|
if (props.searchable && debouncedSearch) {
|
||||||
return (props.searchAttributes?.length ? props.searchAttributes : [props.optionAttribute]).some((searchAttribute: any) => {
|
return await debouncedSearch(query.value)
|
||||||
return typeof option === 'string' ? option.search(new RegExp(query.value, 'i')) !== -1 : (option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1)
|
}
|
||||||
})
|
|
||||||
})
|
if (query.value === '') {
|
||||||
)
|
return props.options
|
||||||
|
}
|
||||||
|
|
||||||
|
return (props.options as any[]).filter((option: any) => {
|
||||||
|
return (props.searchAttributes?.length ? props.searchAttributes : [props.optionAttribute]).some((searchAttribute: any) => {
|
||||||
|
return typeof option === 'string' ? option.search(new RegExp(query.value, 'i')) !== -1 : (option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const queryOption = computed(() => {
|
const queryOption = computed(() => {
|
||||||
return query.value === '' ? null : { [props.optionAttribute]: query.value }
|
return query.value === '' ? null : { [props.optionAttribute]: query.value }
|
||||||
|
|||||||
Reference in New Issue
Block a user