Skip to content

Commit

Permalink
chore(SelectCustom): improve component
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamincanac committed Jul 16, 2022
1 parent 46ea467 commit 7907b12
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 15 deletions.
99 changes: 84 additions & 15 deletions src/runtime/components/forms/SelectCustom.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<template>
<Listbox
<Combobox
v-slot="{ open }"
:model-value="modelValue"
:multiple="multiple"
:nullable="nullable"
as="div"
:class="wrapperClass"
@update:model-value="$emit('update:modelValue', $event)"
>
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1">

<ListboxButton ref="trigger" as="div">
<ComboboxButton ref="trigger" as="div">
<slot :open="open">
<button :class="selectCustomClass">
<slot name="label">
Expand All @@ -23,13 +24,23 @@
</slot>
</button>
</slot>
</ListboxButton>
</ComboboxButton>

<div v-if="open" ref="container" :class="listContainerClass">
<transition appear leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
<ListboxOptions static :class="listBaseClass">
<ListboxOption
v-for="(option, index) in options"
<ComboboxOptions static :class="listBaseClass">
<ComboboxInput
v-if="searchable"
:display-value="() => query"
name="q"
placeholder="Search..."
autofocus
autocomplete="off"
:class="listInputClass"
@change="query = $event.target.value"
/>
<ComboboxOption
v-for="(option, index) in filteredOptions"
v-slot="{ active, selected, disabled }"
:key="index"
as="template"
Expand All @@ -47,22 +58,37 @@
<Icon :name="listOptionIcon" :class="listOptionIconSizeClass" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</ComboboxOption>

<ComboboxOption v-if="queryOption" v-slot="{ active, selected }" :value="queryOption" as="template">
<li :class="resolveOptionClass({ active, selected })">
<div :class="listOptionContainerClass">
<slot name="option" :option="queryOption" :active="active" :selected="selected">
<span class="block truncate">Create "{{ queryOption[textAttribute] }}"</span>
</slot>
</div>
</li>
</ComboboxOption>
<p v-else-if="searchable && query" class="text-sm u-text-gray-400 px-4 py-2">
No results found for "{{ query }}".
</p>
</ComboboxOptions>
</transition>
</div>
</Listbox>
</Combobox>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { ref, computed } from 'vue'
import {
Listbox,
ListboxButton,
ListboxOptions,
ListboxOption
Combobox,
ComboboxButton,
ComboboxOptions,
ComboboxOption,
ComboboxInput
} from '@headlessui/vue'
import Icon from '../elements/Icon'
import Input from '../forms/Input'
import { classNames, usePopper } from '../../utils'
import $ui from '#build/ui'
Expand Down Expand Up @@ -97,6 +123,18 @@ const props = defineProps({
type: Boolean,
default: false
},
nullable: {
type: Boolean,
default: false
},
searchable: {
type: Boolean,
default: false
},
creatable: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: 'Select an option'
Expand Down Expand Up @@ -143,6 +181,10 @@ const props = defineProps({
type: String,
default: () => $ui.selectCustom.list.container
},
listInputClass: {
type: String,
default: () => $ui.selectCustom.list.input
},
listOptionBaseClass: {
type: String,
default: () => $ui.selectCustom.list.option.base
Expand Down Expand Up @@ -194,6 +236,10 @@ const props = defineProps({
textAttribute: {
type: String,
default: 'text'
},
searchAttributes: {
type: Array,
default: null
}
})
Expand Down Expand Up @@ -223,6 +269,8 @@ const [trigger, container] = usePopper({
}]
})
const query = ref('')
const selectCustomClass = computed(() => {
return classNames(
props.baseClass,
Expand All @@ -242,11 +290,32 @@ const iconClass = computed(() => {
)
})
const filteredOptions = computed(() =>
query.value === ''
? props.options
: props.options.filter((option: any) => {
return (props.searchAttributes?.length ? props.searchAttributes : [props.textAttribute]).some((searchAttribute: any) => {
return option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1
})
})
)
const queryOption = computed(() => {
if (!props.creatable) {
return null
}
if (!query.value) {
return null
}
return query.value === '' ? null : { [props.textAttribute]: query.value }
})
const iconWrapperClass = classNames(
$ui.selectCustom.icon.trailing.wrapper
)
function resolveOptionClass ({ active, selected, disabled }: { active: boolean, selected: boolean, disabled: boolean }) {
function resolveOptionClass ({ active, selected, disabled }: { active: boolean, selected: boolean, disabled?: boolean }) {
return classNames(
props.listOptionBaseClass,
active ? props.listOptionActiveClass : props.listOptionInactiveClass,
Expand Down
1 change: 1 addition & 0 deletions src/runtime/presets/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export default (variantColors: string[]) => {
list: {
container: 'z-10 w-full py-1',
base: 'u-bg-white shadow-lg rounded-md ring-1 u-ring-gray-200 focus:outline-none overflow-y-auto py-1 max-h-60',
input: 'relative block w-full focus:ring-transparent text-sm px-4 py-2 u-text-gray-700 border-l-0 border-t-0 border-r-0 u-border-gray-200 focus:u-border-gray-200',
option: {
base: 'cursor-default select-none relative py-2 pl-4 pr-10 text-sm group',
container: 'flex items-center gap-3',
Expand Down

0 comments on commit 7907b12

Please sign in to comment.