homemanager-fe/src/components/form/base/Select.vue

134 lines
3.7 KiB
Vue

<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)">
<slot
name="option"
:selected="selected"
:active="active"
:option="option"
>
<span
:class="[
selected ? 'font-medium' : 'font-normal',
'block truncate',
]"
>
{{ option.name }}
</span>
</slot>
<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';
import { SelectOption } from '../form.types';
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',
props.disabled ? 'bg-gray-100 hover:border-gray-400' : '',
`block w-full rounded-md shadow-sm sm:text-sm transition-colors duration-200`,
'py-2 pl-3 pr-10 text-left cursor-default border-[1px] focus:ring-1',
'sm:min-h-[38px] min-h-[42px]',
];
});
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>