vbv/client/src/components/ui/ItDropdownSelect.vue

113 lines
3.5 KiB
Vue

<script setup lang="ts">
import type { DropdownSelectable } from "@/types";
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "@headlessui/vue";
import { computed } from "vue"; // https://stackoverflow.com/questions/64775876/vue-3-pass-reactive-object-to-component-with-two-way-binding
// https://stackoverflow.com/questions/64775876/vue-3-pass-reactive-object-to-component-with-two-way-binding
interface Props {
modelValue?: {
id: string | number;
name: string;
};
items?: DropdownSelectable[];
borderless?: boolean;
placeholderText?: string | null;
}
const emit = defineEmits<{
(e: "update:modelValue", data: object): void;
}>();
const props = withDefaults(defineProps<Props>(), {
modelValue: () => {
return {
id: "-1",
name: "",
};
},
items: () => [],
placeholderText: null,
});
const dropdownSelected = computed<DropdownSelectable>({
get: () => props.modelValue,
set: (val) => emit("update:modelValue", val),
});
</script>
<template>
<Listbox v-model="dropdownSelected" as="div">
<div class="relative w-full">
<ListboxButton
class="relative flex w-full cursor-default flex-row items-center bg-white py-3 pl-5 pr-10 text-left"
:class="{
border: !props.borderless,
'font-bold': !props.borderless,
}"
data-cy="dropdown-select"
>
<span v-if="dropdownSelected.iconName" class="mr-4">
<component :is="dropdownSelected.iconName"></component>
</span>
<span class="block truncate">
{{ dropdownSelected.name }}
<span v-if="placeholderText && !dropdownSelected.name" class="text-gray-900">
{{ placeholderText }}
</span>
</span>
<span
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
>
<it-icon-arrow-down class="h-5 w-5" aria-hidden="true" />
</span>
</ListboxButton>
<transition
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
>
<ListboxOption
v-for="item in items"
:key="item.id"
v-slot="{ active, selected }"
as="template"
:value="item"
>
<li
:class="[
active ? 'bg-blue-900 text-white' : 'text-black',
'relative cursor-default select-none py-2 pl-3 pr-9',
]"
class="flex flex-row items-center"
:data-cy="`dropdown-select-option-${item.name}`"
>
<span v-if="item.iconName" class="mr-4">
<component :is="item.iconName"></component>
</span>
<span
:class="[
dropdownSelected ? 'font-semibold' : 'font-normal',
'block truncate',
]"
>
{{ item.name }}
</span>
<span
v-if="dropdownSelected"
class="absolute inset-y-0 right-0 flex items-center pr-4 text-blue-900"
>
<it-icon-check v-if="selected" class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</template>