🎨chore: 使用 oxlint, oxfmt&格式化代码
This commit is contained in:
@@ -1,36 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import {computed, type PropType} from "vue";
|
||||
import { computed, type PropType } from 'vue'
|
||||
|
||||
const emit = defineEmits(['input', 'change', 'update:modelValue'])
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
required: false
|
||||
required: false,
|
||||
},
|
||||
items: {
|
||||
type: Array as PropType<SelectItem[]>,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
justify: {
|
||||
type: String as PropType<'start' | 'end'>,
|
||||
required: false,
|
||||
default: 'end'
|
||||
default: 'end',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
align: {
|
||||
type: String as PropType<'bottom' | 'top'>,
|
||||
required: false,
|
||||
default: 'bottom'
|
||||
}
|
||||
default: 'bottom',
|
||||
},
|
||||
})
|
||||
|
||||
const selectWrapperRef = ref()
|
||||
@@ -39,14 +39,17 @@ const optionsRef = ref()
|
||||
|
||||
const optionsAlign = computed(() => {
|
||||
switch (props.align) {
|
||||
case "bottom":
|
||||
case 'bottom':
|
||||
return 'top-full mt-2'
|
||||
case "top":
|
||||
case 'top':
|
||||
return 'bottom-full mb-2'
|
||||
}
|
||||
})
|
||||
const hasAnyIcon = computed(() => props.items.some(item => item.icon))
|
||||
const selectedItem = computed(() => props.items.find(item => item.value === props.modelValue) as SelectItem)
|
||||
const hasAnyIcon = computed(() => props.items.some((item) => item.icon))
|
||||
const selectedItem = computed(
|
||||
() =>
|
||||
props.items.find((item) => item.value === props.modelValue) as SelectItem
|
||||
)
|
||||
const optionsExpanded = ref(false)
|
||||
const selectedIconFlag = ref(true)
|
||||
|
||||
@@ -57,58 +60,113 @@ const handleOptionSelect = (option: SelectItem) => {
|
||||
emit('input', option.value)
|
||||
emit('change', option.value)
|
||||
emit('update:modelValue', option.value)
|
||||
selectedIconFlag.value = false;
|
||||
selectedIconFlag.value = false
|
||||
nextTick(() => {
|
||||
selectedIconFlag.value = true;
|
||||
});
|
||||
selectedIconFlag.value = true
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
selectRef.value.ownerDocument.addEventListener('click', (e: { target: any; }) => {
|
||||
if (optionsExpanded && !selectRef?.value?.contains(e.target)) {
|
||||
optionsExpanded.value = false
|
||||
selectRef.value.ownerDocument.addEventListener(
|
||||
'click',
|
||||
(e: { target: any }) => {
|
||||
if (optionsExpanded && !selectRef?.value?.contains(e.target)) {
|
||||
optionsExpanded.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col space-y-1"
|
||||
:class="{ 'justify-start': justify === 'start', 'justify-end': justify === 'end' }">
|
||||
<p class="block w-fit text-neutral-700 dark:text-neutral-300 text-sm font-bold font-['Nunito']" v-if="label">
|
||||
<div
|
||||
class="flex flex-col space-y-1"
|
||||
:class="{
|
||||
'justify-start': justify === 'start',
|
||||
'justify-end': justify === 'end',
|
||||
}"
|
||||
>
|
||||
<p
|
||||
class="block w-fit text-neutral-700 dark:text-neutral-300 text-sm font-bold font-['Nunito']"
|
||||
v-if="label"
|
||||
>
|
||||
{{ label }}
|
||||
</p>
|
||||
<div class="relative" ref="selectWrapperRef">
|
||||
<button class="relative w-full flex items-center gap-2.5 p-2 pr-6 rounded-md overflow-hidden border transition bg-white dark:bg-neutral-800
|
||||
border-neutral-200 dark:border-neutral-800 focus:border-neutral-400 dark:focus:border-neutral-700
|
||||
focus:ring-4 focus:ring-opacity-50 focus:ring-neutral-200 dark:focus:ring-neutral-800 shadow-sm"
|
||||
:class="{ 'cursor-not-allowed bg-neutral-100 dark:bg-neutral-900 text-neutral-400 dark:text-neutral-600': disabled }" ref="selectRef" type="button" @click="handleSelectClick" :disabled="disabled">
|
||||
<span v-if="selectedItem?.icon && !selectedIconFlag && hasAnyIcon"
|
||||
class="inline-block w-5 h-5 pointer-events-none"></span>
|
||||
<Icon v-else-if="selectedItem?.icon && selectedIconFlag && hasAnyIcon" :name="selectedItem?.icon"
|
||||
class="inline-block w-5 h-5 pointer-events-none" />
|
||||
<Transition name="select-item" mode="out-in">
|
||||
<span class="leading-snug whitespace-nowrap text-sm" :key="selectedItem?.value">{{
|
||||
<div
|
||||
class="relative"
|
||||
ref="selectWrapperRef"
|
||||
>
|
||||
<button
|
||||
class="relative w-full flex items-center gap-2.5 p-2 pr-6 rounded-md overflow-hidden border transition bg-white dark:bg-neutral-800 border-neutral-200 dark:border-neutral-800 focus:border-neutral-400 dark:focus:border-neutral-700 focus:ring-4 focus:ring-opacity-50 focus:ring-neutral-200 dark:focus:ring-neutral-800 shadow-sm"
|
||||
:class="{
|
||||
'cursor-not-allowed bg-neutral-100 dark:bg-neutral-900 text-neutral-400 dark:text-neutral-600':
|
||||
disabled,
|
||||
}"
|
||||
ref="selectRef"
|
||||
type="button"
|
||||
@click="handleSelectClick"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<span
|
||||
v-if="selectedItem?.icon && !selectedIconFlag && hasAnyIcon"
|
||||
class="inline-block w-5 h-5 pointer-events-none"
|
||||
></span>
|
||||
<Icon
|
||||
v-else-if="selectedItem?.icon && selectedIconFlag && hasAnyIcon"
|
||||
:name="selectedItem?.icon"
|
||||
class="inline-block w-5 h-5 pointer-events-none"
|
||||
/>
|
||||
<Transition
|
||||
name="select-item"
|
||||
mode="out-in"
|
||||
>
|
||||
<span
|
||||
class="leading-snug whitespace-nowrap text-sm"
|
||||
:key="selectedItem?.value"
|
||||
>
|
||||
{{
|
||||
selectedItem?.label || selectedItem?.value || 'Select an option'
|
||||
}}</span>
|
||||
}}
|
||||
</span>
|
||||
</Transition>
|
||||
<Icon name="tabler:dots-vertical"
|
||||
class="absolute bg-neutral-50 text-gray-500 dark:bg-neutral-700/50 dark:text-neutral-500 inset-y-0 right-0 h-full" />
|
||||
<Icon
|
||||
name="tabler:dots-vertical"
|
||||
class="absolute bg-neutral-50 text-gray-500 dark:bg-neutral-700/50 dark:text-neutral-500 inset-y-0 right-0 h-full"
|
||||
/>
|
||||
</button>
|
||||
<div class="absolute right-0 w-full md:w-fit rounded-md border overflow-x-hidden overflow-y-auto transition shadow-lg opacity-0
|
||||
bg-white dark:bg-neutral-800 border-neutral-200 dark:border-neutral-800 z-50 max-h-64"
|
||||
:class="{ 'opacity-100 pointer-events-auto': optionsExpanded, '-translate-y-4 pointer-events-none': !optionsExpanded, [optionsAlign]: optionsAlign }"
|
||||
ref="optionsRef">
|
||||
<div class="flex items-center gap-2.5 px-2 py-2 cursor-pointer
|
||||
dark:text-neutral-300 font-['Nunito'] transition whitespace-nowrap
|
||||
bg-white dark:bg-neutral-800 hover:bg-neutral-100 dark:hover:bg-neutral-700"
|
||||
v-for="(option, index) in items" :key="index" :class="{
|
||||
'!bg-neutral-200 dark:!bg-neutral-700 hover:!bg-neutral-200 dark:hover:!bg-neutral-700': option.value === selectedItem?.value,
|
||||
'!cursor-not-allowed text-neutral-300 dark:text-neutral-500 hover:bg-white dark:hover:!bg-neutral-800': option.disabled
|
||||
}" @click="!option.disabled ? handleOptionSelect(option) : void 0">
|
||||
<div class="inline-block w-5 h-5" v-if="hasAnyIcon && !option.icon"></div>
|
||||
<Icon :name="(option?.icon)" class="inline-block w-5 h-5" v-if="option.icon" />
|
||||
<span class="leading-none whitespace-nowrap text-sm font-sans">{{ option.label || 'No label' }}</span>
|
||||
<div
|
||||
class="absolute right-0 w-full md:w-fit rounded-md border overflow-x-hidden overflow-y-auto transition shadow-lg opacity-0 bg-white dark:bg-neutral-800 border-neutral-200 dark:border-neutral-800 z-50 max-h-64"
|
||||
:class="{
|
||||
'opacity-100 pointer-events-auto': optionsExpanded,
|
||||
'-translate-y-4 pointer-events-none': !optionsExpanded,
|
||||
[optionsAlign]: optionsAlign,
|
||||
}"
|
||||
ref="optionsRef"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-2.5 px-2 py-2 cursor-pointer dark:text-neutral-300 font-['Nunito'] transition whitespace-nowrap bg-white dark:bg-neutral-800 hover:bg-neutral-100 dark:hover:bg-neutral-700"
|
||||
v-for="(option, index) in items"
|
||||
:key="index"
|
||||
:class="{
|
||||
'!bg-neutral-200 dark:!bg-neutral-700 hover:!bg-neutral-200 dark:hover:!bg-neutral-700':
|
||||
option.value === selectedItem?.value,
|
||||
'!cursor-not-allowed text-neutral-300 dark:text-neutral-500 hover:bg-white dark:hover:!bg-neutral-800':
|
||||
option.disabled,
|
||||
}"
|
||||
@click="!option.disabled ? handleOptionSelect(option) : void 0"
|
||||
>
|
||||
<div
|
||||
class="inline-block w-5 h-5"
|
||||
v-if="hasAnyIcon && !option.icon"
|
||||
></div>
|
||||
<Icon
|
||||
:name="option?.icon"
|
||||
class="inline-block w-5 h-5"
|
||||
v-if="option.icon"
|
||||
/>
|
||||
<span class="leading-none whitespace-nowrap text-sm font-sans">
|
||||
{{ option.label || 'No label' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,12 +176,12 @@ onMounted(() => {
|
||||
<style scoped>
|
||||
.select-item-enter-active,
|
||||
.select-item-leave-active {
|
||||
transition: all .15s ease;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.select-item-enter-from,
|
||||
.select-item-leave-to {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
filter: blur(2px);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user