113 lines
3.5 KiB
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>
|