Skip to content

Commit

Permalink
feat(SelectMenu): handle async search (#426)
Browse files Browse the repository at this point in the history
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
  • Loading branch information
mcastagnetti and benjamincanac committed Jul 18, 2023
1 parent 46b444a commit 5f8fe85
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 10 deletions.
19 changes: 19 additions & 0 deletions docs/components/content/examples/SelectMenuExampleAsyncSearch.vue
Original file line number Diff line number Diff line change
@@ -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>
34 changes: 34 additions & 0 deletions docs/content/3.forms/4.select-menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

### `label`
Expand Down
33 changes: 23 additions & 10 deletions src/runtime/components/forms/SelectMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ import {
ListboxOptions as HListboxOptions,
ListboxOption as HListboxOption
} from '@headlessui/vue'
import { computedAsync, useDebounceFn } from '@vueuse/core'
import { defu } from 'defu'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
Expand Down Expand Up @@ -219,13 +220,17 @@ export default defineComponent({
default: false
},
searchable: {
type: Boolean,
type: [Boolean, Function] as PropType<boolean | ((query: string) => Promise<any[]> | any[])>,
default: false
},
searchablePlaceholder: {
type: String,
default: 'Search...'
},
debounce: {
type: Number,
default: 200
},
creatable: {
type: Boolean,
default: false
Expand Down Expand Up @@ -373,15 +378,23 @@ export default defineComponent({
)
})
const filteredOptions = computed(() =>
query.value === ''
? props.options
: (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 debouncedSearch = typeof props.searchable === 'function' ? useDebounceFn(props.searchable, props.debounce) : undefined
const filteredOptions = computedAsync(async () => {
if (props.searchable && debouncedSearch) {
return await debouncedSearch(query.value)
}
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(() => {
return query.value === '' ? null : { [props.optionAttribute]: query.value }
Expand Down

1 comment on commit 5f8fe85

@vercel
Copy link

@vercel vercel bot commented on 5f8fe85 Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ui – ./

ui-nuxtlabs.vercel.app
ui-git-dev-nuxtlabs.vercel.app
ui.nuxtlabs.com

Please sign in to comment.