mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-10-29 20:51:09 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			217 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <BaseContentPlaceholders v-if="contentLoading">
 | |
|     <BaseContentPlaceholdersBox :rounded="true" class="w-full h-10" />
 | |
|   </BaseContentPlaceholders>
 | |
|   <Listbox
 | |
|     v-else
 | |
|     v-model="selectedValue"
 | |
|     as="div"
 | |
|     v-bind="{
 | |
|       ...$attrs,
 | |
|     }"
 | |
|   >
 | |
|     <ListboxLabel
 | |
|       v-if="label"
 | |
|       class="block text-sm not-italic font-medium text-primary-800 mb-0.5"
 | |
|     >
 | |
|       {{ label }}
 | |
|     </ListboxLabel>
 | |
| 
 | |
|     <div class="relative">
 | |
|       <!-- Select Input button -->
 | |
|       <ListboxButton
 | |
|         class="
 | |
|           relative
 | |
|           w-full
 | |
|           py-2
 | |
|           pl-3
 | |
|           pr-10
 | |
|           text-left
 | |
|           bg-white
 | |
|           border border-gray-200
 | |
|           rounded-md
 | |
|           shadow-sm
 | |
|           cursor-default
 | |
|           focus:outline-none
 | |
|           focus:ring-1 focus:ring-primary-500
 | |
|           focus:border-primary-500
 | |
|           sm:text-sm
 | |
|         "
 | |
|       >
 | |
|         <span v-if="getValue(selectedValue)" class="block truncate">
 | |
|           {{ getValue(selectedValue) }}
 | |
|         </span>
 | |
|         <span v-else-if="placeholder" class="block text-gray-400 truncate">
 | |
|           {{ placeholder }}
 | |
|         </span>
 | |
|         <span v-else class="block text-gray-400 truncate">
 | |
|           Please select an option
 | |
|         </span>
 | |
| 
 | |
|         <span
 | |
|           class="
 | |
|             absolute
 | |
|             inset-y-0
 | |
|             right-0
 | |
|             flex
 | |
|             items-center
 | |
|             pr-2
 | |
|             pointer-events-none
 | |
|           "
 | |
|         >
 | |
|           <BaseIcon
 | |
|             name="SelectorIcon"
 | |
|             class="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-10
 | |
|             w-full
 | |
|             py-1
 | |
|             mt-1
 | |
|             overflow-auto
 | |
|             text-base
 | |
|             bg-white
 | |
|             rounded-md
 | |
|             shadow-lg
 | |
|             max-h-60
 | |
|             ring-1 ring-black ring-opacity-5
 | |
|             focus:outline-none
 | |
|             sm:text-sm
 | |
|           "
 | |
|         >
 | |
|           <ListboxOption
 | |
|             v-for="option in options"
 | |
|             v-slot="{ active, selected }"
 | |
|             :key="option.id"
 | |
|             :value="option"
 | |
|             as="template"
 | |
|           >
 | |
|             <li
 | |
|               :class="[
 | |
|                 active ? 'text-white bg-primary-600' : 'text-gray-900',
 | |
|                 'cursor-default select-none relative py-2 pl-3 pr-9',
 | |
|               ]"
 | |
|             >
 | |
|               <span
 | |
|                 :class="[
 | |
|                   selected ? 'font-semibold' : 'font-normal',
 | |
|                   'block truncate',
 | |
|                 ]"
 | |
|               >
 | |
|                 {{ getValue(option) }}
 | |
|               </span>
 | |
| 
 | |
|               <span
 | |
|                 v-if="selected"
 | |
|                 :class="[
 | |
|                   active ? 'text-white' : 'text-primary-600',
 | |
|                   'absolute inset-y-0 right-0 flex items-center pr-4',
 | |
|                 ]"
 | |
|               >
 | |
|                 <BaseIcon name="CheckIcon" aria-hidden="true" />
 | |
|                 />
 | |
|               </span>
 | |
|             </li>
 | |
|           </ListboxOption>
 | |
|           <slot />
 | |
|         </ListboxOptions>
 | |
|       </transition>
 | |
|     </div>
 | |
|   </Listbox>
 | |
| </template>
 | |
| 
 | |
| <script setup>
 | |
| import { ref, watch } from 'vue'
 | |
| import {
 | |
|   Listbox,
 | |
|   ListboxButton,
 | |
|   ListboxLabel,
 | |
|   ListboxOption,
 | |
|   ListboxOptions,
 | |
| } from '@headlessui/vue'
 | |
| 
 | |
| const props = defineProps({
 | |
|   contentLoading: {
 | |
|     type: Boolean,
 | |
|     default: false,
 | |
|   },
 | |
|   modelValue: {
 | |
|     type: [String, Number, Boolean, Object, Array],
 | |
|     default: '',
 | |
|   },
 | |
|   options: {
 | |
|     type: Array,
 | |
|     required: true,
 | |
|   },
 | |
|   label: {
 | |
|     type: String,
 | |
|     default: '',
 | |
|   },
 | |
|   placeholder: {
 | |
|     type: String,
 | |
|     default: '',
 | |
|   },
 | |
|   labelKey: {
 | |
|     type: [String],
 | |
|     default: 'label',
 | |
|   },
 | |
|   valueProp: {
 | |
|     type: String,
 | |
|     default: null,
 | |
|   },
 | |
|   multiple: {
 | |
|     type: Boolean,
 | |
|     default: false,
 | |
|   },
 | |
| })
 | |
| 
 | |
| const emit = defineEmits(['update:modelValue'])
 | |
| 
 | |
| let selectedValue = ref(props.modelValue)
 | |
| 
 | |
| function isObject(val) {
 | |
|   return typeof val === 'object' && val !== null
 | |
| }
 | |
| 
 | |
| function getValue(val) {
 | |
|   if (isObject(val)) {
 | |
|     return val[props.labelKey]
 | |
|   }
 | |
|   return val
 | |
| }
 | |
| 
 | |
| watch(
 | |
|   () => props.modelValue,
 | |
|   () => {
 | |
|     if (props.valueProp && props.options.length) {
 | |
|       selectedValue.value = props.options.find((val) => {
 | |
|         if (val[props.valueProp]) {
 | |
|           return val[props.valueProp] === props.modelValue
 | |
|         }
 | |
|       })
 | |
|     } else {
 | |
|       selectedValue.value = props.modelValue
 | |
|     }
 | |
|   }
 | |
| )
 | |
| 
 | |
| watch(selectedValue, (val) => {
 | |
|   if (props.valueProp) {
 | |
|     emit('update:modelValue', val[props.valueProp])
 | |
|   } else {
 | |
|     emit('update:modelValue', val)
 | |
|   }
 | |
| })
 | |
| </script>
 |