mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
172 lines
4.5 KiB
Vue
172 lines
4.5 KiB
Vue
<template>
|
|
<Listbox
|
|
:model-value="modelValue"
|
|
as="div"
|
|
:class="wrapperClass"
|
|
@update:model-value="$emit('update:modelValue', $event)"
|
|
>
|
|
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default">
|
|
|
|
<ListboxButton :class="selectCustomClass">
|
|
<span class="block truncate">{{ modelValue[textAttribute] }}</span>
|
|
<span :class="iconWrapperClass">
|
|
<Icon name="heroicons-solid:selector" :class="iconClass" 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 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 u-ring-gray-200 overflow-auto focus:outline-none sm:text-sm">
|
|
<ListboxOption
|
|
v-for="(option, index) in options"
|
|
v-slot="{ active, selected, disabled }"
|
|
:key="index"
|
|
as="template"
|
|
:value="option"
|
|
:disabled="option.disabled"
|
|
>
|
|
<li :class="resolveOptionClass({ active, disabled })">
|
|
<span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
|
|
<slot name="option" :option="option">
|
|
{{ option[textAttribute] }}
|
|
</slot>
|
|
</span>
|
|
|
|
<span v-if="selected" :class="resolveOptionIconClass({ active })">
|
|
<Icon name="heroicons-solid:check" :class="listOptionIconSizeClass" aria-hidden="true" />
|
|
</span>
|
|
</li>
|
|
</ListboxOption>
|
|
</ListboxOptions>
|
|
</transition>
|
|
</Listbox>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
import {
|
|
Listbox,
|
|
ListboxButton,
|
|
ListboxOptions,
|
|
ListboxOption
|
|
} from '@headlessui/vue'
|
|
import Icon from '../elements/Icon'
|
|
import { classNames } from '../../utils'
|
|
import $ui from '#build/ui'
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: [String, Number, Object],
|
|
default: ''
|
|
},
|
|
options: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
required: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
size: {
|
|
type: String,
|
|
default: 'md',
|
|
validator (value) {
|
|
return Object.keys($ui.selectCustom.size).includes(value)
|
|
}
|
|
},
|
|
wrapperClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.wrapper
|
|
},
|
|
baseClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.base
|
|
},
|
|
iconBaseClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.icon.base
|
|
},
|
|
customClass: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
listBaseClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.base
|
|
},
|
|
listOptionBaseClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.option.base
|
|
},
|
|
listOptionActiveClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.option.active
|
|
},
|
|
listOptionInactiveClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.option.inactive
|
|
},
|
|
listOptionDisabledClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.option.disabled
|
|
},
|
|
listOptionIconBaseClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.option.icon.base
|
|
},
|
|
listOptionIconActiveClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.option.icon.active
|
|
},
|
|
listOptionIconInactiveClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.option.icon.inactive
|
|
},
|
|
listOptionIconSizeClass: {
|
|
type: String,
|
|
default: () => $ui.selectCustom.list.option.icon.size
|
|
},
|
|
textAttribute: {
|
|
type: String,
|
|
default: 'text'
|
|
}
|
|
})
|
|
|
|
defineEmits(['update:modelValue'])
|
|
|
|
const selectCustomClass = computed(() => {
|
|
return classNames(
|
|
props.baseClass,
|
|
$ui.selectCustom.size[props.size],
|
|
$ui.selectCustom.spacing[props.size],
|
|
$ui.selectCustom.appearance.default,
|
|
$ui.selectCustom.trailing.spacing[props.size],
|
|
props.customClass
|
|
)
|
|
})
|
|
|
|
const iconClass = computed(() => {
|
|
return classNames(
|
|
props.iconBaseClass,
|
|
$ui.selectCustom.icon.size[props.size],
|
|
$ui.selectCustom.icon.trailing.spacing[props.size]
|
|
)
|
|
})
|
|
|
|
const iconWrapperClass = $ui.selectCustom.icon.trailing.wrapper
|
|
|
|
function resolveOptionClass ({ active, disabled }) {
|
|
return classNames(
|
|
props.listOptionBaseClass,
|
|
active ? props.listOptionActiveClass : props.listOptionInactiveClass,
|
|
disabled && props.listOptionDisabledClass
|
|
)
|
|
}
|
|
|
|
function resolveOptionIconClass ({ active }) {
|
|
return classNames(
|
|
props.listOptionIconBaseClass,
|
|
active ? props.listOptionIconActiveClass : props.listOptionIconInactiveClass
|
|
)
|
|
}
|
|
</script>
|