Files
simrsx-fe/app/components/pub/my-ui/form/select.vue

172 lines
5.1 KiB
Vue

<script setup lang="ts">
import { SelectRoot } from 'radix-vue'
import { watch } from 'vue'
import SelectContent from '~/components/pub/ui/select/SelectContent.vue'
import SelectGroup from '~/components/pub/ui/select/SelectGroup.vue'
import SelectItem from '~/components/pub/ui/select/SelectItem.vue'
import SelectLabel from '~/components/pub/ui/select/SelectLabel.vue'
import SelectSeparator from '~/components/pub/ui/select/SelectSeparator.vue'
import SelectTrigger from '~/components/pub/ui/select/SelectTrigger.vue'
import SelectValue from '~/components/pub/ui/select/SelectValue.vue'
import { cn } from '~/lib/utils'
export interface SelectItem {
value: string
label: string
code?: string
priority?: number // Priority untuk sorting: negatif = bawah, positif = atas, 0/undefined = normal sorting
}
const props = defineProps<{
modelValue?: string
items: SelectItem[]
placeholder?: string
label?: string
separator?: boolean
class?: string
isSelectedFirst?: boolean
preserveOrder?: boolean
isDisabled?: boolean
autoWidth?: boolean
autoFill?: boolean
// otherPlacement sudah tidak digunakan, diganti dengan priority system di Item interface
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const processedItems = computed(() => {
const itemsWithSelection = props.items.map((item) => ({
...item,
isSelected: item.value === props.modelValue,
}))
// Jika preserveOrder true, kembalikan urutan array asli
if (props.preserveOrder) {
return itemsWithSelection
}
// Sorting dengan priority system
return itemsWithSelection.sort((a, b) => {
const aPriority = a.priority ?? 0
const bPriority = b.priority ?? 0
// Jika ada priority, sort berdasarkan priority (descending: tinggi ke rendah)
if (aPriority !== bPriority) {
return bPriority - aPriority
}
// Jika priority sama (termasuk 0/undefined), lakukan sorting normal
if (props.isSelectedFirst) {
if (a.isSelected && !b.isSelected) return -1
if (!a.isSelected && b.isSelected) return 1
}
return a.label.localeCompare(b.label)
})
})
// Computed property untuk menghitung width optimal berdasarkan konten terpanjang
const optimalWidth = computed(() => {
// Jika autoWidth false atau undefined, gunakan full width
if (!props.autoWidth) return '100%'
if (!props.items.length) return 'auto'
// Mencari label terpanjang
const longestLabel = props.items.reduce((longest, item) => {
const itemText = item.code ? `${item.label} ${item.code}` : item.label
return itemText.length > longest.length ? itemText : longest
}, '')
// Menghitung width berdasarkan panjang karakter (estimasi)
// Setiap karakter ~0.6em, ditambah padding dan space untuk icon
const estimatedWidth = Math.max(longestLabel.length * 0.6 + 3, 8) // minimum 8em
return `${Math.min(estimatedWidth, 25)}em` // maksimum 25em
})
function onValueChange(value: string) {
emit('update:modelValue', value)
}
// Auto fill logic - automatically select first item if autoFill is enabled
watch(
() => props.items,
(newItems) => {
if (props.autoFill && newItems.length > 0 && !props.modelValue) {
// Auto select first item only if no value is currently selected
const firstItem = newItems[0]
if (firstItem?.value) {
emit('update:modelValue', firstItem.value)
}
}
},
{ immediate: true },
)
</script>
<template>
<SelectRoot
:model-value="modelValue"
:disabled="isDisabled"
@update:model-value="onValueChange"
>
<SelectTrigger
:class="
cn(
'rounded-md focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white',
{
'cursor-not-allowed bg-gray-100 opacity-50': isDisabled,
'bg-white text-black dark:bg-gray-800 dark:text-white': !isDisabled,
'w-full': !autoWidth,
},
props.class,
)
"
:style="autoWidth ? { width: optimalWidth, minWidth: '8em' } : {}"
icon-name="i-radix-icons-chevron-down"
icon-class="text-gray-500 dark:text-gray-300"
>
<SelectValue
:placeholder="placeholder || 'Pilih item'"
:class="
cn('', {
'text-gray-400': !props.modelValue,
'text-black dark:text-white': props.modelValue,
'text-gray-500': isDisabled,
})
"
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel v-if="label">
{{ label }}
</SelectLabel>
<SelectItem
v-for="item in processedItems"
:key="item.value"
:value="item.value"
class="cursor-pointer hover:bg-primary hover:text-white focus:bg-primary focus:text-white"
>
<div class="flex w-full items-center justify-between">
<span>{{ item.label }}</span>
<span
v-if="item.code"
class="ml-2 text-xs text-muted-foreground"
>
{{ item.code }}
</span>
</div>
</SelectItem>
<SelectSeparator v-if="separator" />
</SelectGroup>
</SelectContent>
</SelectRoot>
</template>