2023-01-26 18:21:41 +00:00
|
|
|
<template>
|
|
|
|
<Listbox
|
|
|
|
:modelValue="selected"
|
|
|
|
@update:modelValue="valueChanged"
|
|
|
|
:disabled="disabled"
|
|
|
|
by="value"
|
|
|
|
>
|
|
|
|
<div class="relative mt-1">
|
|
|
|
<ListboxButton :class="inputClass" :id="forId">
|
|
|
|
<span class="block truncate">{{ selected?.name || '' }}</span>
|
|
|
|
<span
|
|
|
|
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
|
|
|
>
|
|
|
|
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
|
|
|
|
</span>
|
|
|
|
</ListboxButton>
|
|
|
|
|
|
|
|
<Transition
|
|
|
|
leave-active-class="transition duration-100 ease-in"
|
|
|
|
leave-from-class="opacity-100"
|
|
|
|
leave-to-class="opacity-0"
|
|
|
|
>
|
|
|
|
<ListboxOptions
|
|
|
|
class="absolute z-20 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
|
|
|
|
>
|
|
|
|
<ListboxOption
|
|
|
|
v-slot="{ active, selected }"
|
|
|
|
v-for="option in options"
|
|
|
|
:key="option.name"
|
|
|
|
:value="option"
|
|
|
|
as="template"
|
|
|
|
>
|
|
|
|
<li :class="itemClass(active, selected)">
|
2023-01-27 16:27:14 +00:00
|
|
|
<slot
|
|
|
|
name="option"
|
|
|
|
:selected="selected"
|
|
|
|
:active="active"
|
|
|
|
:option="option"
|
2023-01-26 18:21:41 +00:00
|
|
|
>
|
2023-01-27 16:27:14 +00:00
|
|
|
<span
|
|
|
|
:class="[
|
|
|
|
selected ? 'font-medium' : 'font-normal',
|
|
|
|
'block truncate',
|
|
|
|
]"
|
|
|
|
>
|
|
|
|
{{ option.name }}
|
|
|
|
</span>
|
|
|
|
</slot>
|
2023-01-26 18:21:41 +00:00
|
|
|
<span
|
|
|
|
v-if="selected"
|
|
|
|
class="absolute inset-y-0 left-0 flex items-center pl-2 text-blue-600"
|
|
|
|
>
|
|
|
|
<CheckIcon class="h-5 w-5" aria-hidden="true" />
|
|
|
|
</span>
|
|
|
|
</li>
|
|
|
|
</ListboxOption>
|
|
|
|
</ListboxOptions>
|
|
|
|
</Transition>
|
|
|
|
</div>
|
|
|
|
</Listbox>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { computed, ref } from 'vue';
|
|
|
|
import {
|
|
|
|
Listbox,
|
|
|
|
ListboxButton,
|
|
|
|
ListboxOptions,
|
|
|
|
ListboxOption,
|
|
|
|
} from '@headlessui/vue';
|
|
|
|
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/24/outline';
|
2023-01-27 16:27:14 +00:00
|
|
|
import { SelectOption } from '../form.types';
|
2023-01-26 18:21:41 +00:00
|
|
|
|
|
|
|
const props = withDefaults(
|
|
|
|
defineProps<{
|
|
|
|
forId?: string;
|
|
|
|
modelValue: string | number | null;
|
|
|
|
options: SelectOption[];
|
|
|
|
disabled?: boolean;
|
|
|
|
invalid?: boolean;
|
|
|
|
placeholder?: string;
|
|
|
|
}>(),
|
|
|
|
{
|
|
|
|
modelValue: null,
|
|
|
|
disabled: false,
|
|
|
|
invalid: false,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
const localValue = ref(props.modelValue);
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
(e: 'update:modelValue', value: string | number | null): void;
|
|
|
|
}>();
|
|
|
|
|
|
|
|
const valueChanged = (option: SelectOption) => {
|
|
|
|
localValue.value = option.value;
|
|
|
|
emit('update:modelValue', localValue.value);
|
|
|
|
};
|
|
|
|
|
|
|
|
const selected = computed(() =>
|
|
|
|
props.options.find((item) => localValue.value === item.value)
|
|
|
|
);
|
|
|
|
|
|
|
|
const inputClass = computed(() => {
|
|
|
|
return [
|
|
|
|
props.invalid
|
|
|
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
|
|
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
|
|
|
|
`block w-full rounded-md shadow-sm sm:text-sm transition-colors duration-200`,
|
2023-01-26 18:30:17 +00:00
|
|
|
'py-2 pl-3 pr-10 text-left cursor-default h-[38px] border-[1px] focus:ring-1',
|
2023-01-26 18:21:41 +00:00
|
|
|
];
|
|
|
|
});
|
|
|
|
|
|
|
|
const itemClass = (active: boolean, selected: boolean) => {
|
|
|
|
const classList = ['relative cursor-default select-none py-2 pl-8 pr-4'];
|
|
|
|
if (selected) {
|
|
|
|
classList.push('text-blue-900');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (active && selected) {
|
|
|
|
classList.push('bg-blue-200');
|
|
|
|
} else if (active || selected) {
|
|
|
|
classList.push('bg-blue-100');
|
|
|
|
} else {
|
|
|
|
classList.push('text-gray-900');
|
|
|
|
}
|
|
|
|
|
|
|
|
return classList;
|
|
|
|
};
|
|
|
|
</script>
|