Files

301 lines
8.4 KiB
Vue

<template>
<div class="ui-select-wrapper" :class="directionClass">
<label v-if="label" :for="id" class="ui-select-label">
{{ label }}
<span v-if="required" class="ui-select-required-indicator">*</span>
</label>
<div class="ui-select">
<div v-if="hasPrefixes" class="ui-select-wrapper-border" :class="prefixClasses">
<template v-if="direction === 'rtl'">
<i v-if="iconEnd" :class="[iconEnd, iconColorClass]" class="ui-select-icon-end !mt-2"></i>
<span v-if="prefixEnd" class="ui-select-prefix-end" :class="prefixColorClass">{{ prefixEnd }}</span>
<Dropdown
:id="id"
:model-value="internalModelValue"
:options="options"
:option-label="optionLabel"
:option-value="optionValue"
:dataKey="optionValue"
:placeholder="placeholder"
:disabled="disabled"
:class="selectClassesNoBorder"
empty-filter-message="نتیجه‌ای یافت نشد"
:empty-message="emptyMessage"
:filter="filter"
:reset-filter-on-hide="true"
:filter-placeholder="filterPlaceholder"
:auto-filter-focus="false"
:show-clear="showClear"
class="ui-select-dropdown"
@update:model-value="handleUpdate"
@change="handleUpdate"
@focus="handleFocus"
@blur="handleBlur"
/>
<span v-if="prefixStart" class="ui-select-prefix-start" :class="prefixColorClass">{{ prefixStart }}</span>
<i v-if="iconStart" :class="[iconStart, iconColorClass]" class="ui-select-icon-start !mt-2"></i>
</template>
<template v-else>
<i v-if="iconStart" :class="[iconStart, iconColorClass]" class="ui-select-icon-start !mt-2"></i>
<span v-if="prefixStart" class="ui-select-prefix-start" :class="prefixColorClass">{{ prefixStart }}</span>
<Dropdown
:id="id"
:model-value="internalModelValue"
:options="options"
:option-label="optionLabel"
:option-value="optionValue"
:dataKey="optionValue"
:placeholder="placeholder"
:disabled="disabled"
:class="selectClassesNoBorder"
empty-filter-message="نتیجه‌ای یافت نشد"
:empty-message="emptyMessage"
:filter="filter"
:reset-filter-on-hide="true"
:filter-placeholder="filterPlaceholder"
:auto-filter-focus="false"
:show-clear="showClear"
class="ui-select-dropdown"
@update:model-value="handleUpdate"
@change="handleUpdate"
@focus="handleFocus"
@blur="handleBlur"
/>
<span v-if="prefixEnd" class="ui-select-prefix-end" :class="prefixColorClass">{{ prefixEnd }}</span>
<i v-if="iconEnd" :class="[iconEnd, iconColorClass]" class="ui-select-icon-end !mt-2"></i>
</template>
</div>
<div v-else-if="prefix" class="ui-select-wrapper-border" :class="directionClass">
<span class="ui-select-prefix">{{ prefix }}</span>
<Dropdown
:id="id"
:model-value="internalModelValue"
:options="options"
:option-label="optionLabel"
:option-value="optionValue"
:dataKey="optionValue"
:placeholder="placeholder"
:disabled="disabled"
:class="selectClassesNoBorder"
empty-filter-message="نتیجه‌ای یافت نشد"
:empty-message="emptyMessage"
:filter="filter"
:reset-filter-on-hide="true"
:filter-placeholder="filterPlaceholder"
:show-clear="showClear"
:auto-filter-focus="false"
class="ui-select-dropdown"
@update:model-value="handleUpdate"
@change="handleUpdate"
@focus="handleFocus"
@blur="handleBlur"
/>
</div>
<div v-else class="relative select">
<Dropdown
:id="id"
:model-value="internalModelValue"
:options="options"
:option-label="optionLabel"
:option-value="optionValue"
:dataKey="optionValue"
:placeholder="placeholder"
:disabled="disabled"
:class="selectClasses"
empty-filter-message="نتیجه‌ای یافت نشد"
:empty-message="emptyMessage"
:filter="filter"
:reset-filter-on-hide="true"
:filter-placeholder="filterPlaceholder"
:show-clear="showClear"
class="ui-select-dropdown"
@update:model-value="handleUpdate"
@change="handleUpdate"
@focus="handleFocus"
@blur="handleBlur"
/>
</div>
</div>
<small v-if="error" class="ui-select-error-message">{{ error }}</small>
<small v-if="hint" class="ui-select-hint-message">{{ hint }}</small>
</div>
</template>
<script setup>
import { computed } from 'vue'
import Dropdown from 'primevue/dropdown'
const props = defineProps({
id: {
type: String,
default: () => `ui-select-${Math.random().toString(36).substr(2, 9)}`
},
modelValue: { type: [String, Number, Object, null], default: null },
options: {
type: Array,
default: () => []
},
optionLabel: { type: String, default: 'name' },
optionValue: { type: String, default: undefined },
placeholder: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
error: {
type: String,
default: ''
},
hint: {
type: String,
default: ''
},
required: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
},
direction: {
type: String,
default: 'rtl',
validator: (value) => ['rtl', 'ltr'].includes(value)
},
prefix: {
type: String,
default: ''
},
prefixStart: {
type: String,
default: ''
},
prefixEnd: {
type: String,
default: ''
},
prefixColor: {
type: String,
default: 'dominant-600'
},
iconStart: {
type: String,
default: ''
},
iconEnd: {
type: String,
default: ''
},
iconColor: {
type: String,
default: 'dominant-600'
},
filter: {
type: Boolean,
default: false
},
filterPlaceholder: {
type: String,
default: 'جستجو...'
},
emptyMessage: {
type: String,
default: 'نتیجه‌ای یافت نشد'
},
showClear: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue','change','focus','blur'])
const internalModelValue = computed(() => {
if (props.optionValue && props.modelValue && typeof props.modelValue === 'object') {
return props.modelValue[props.optionValue]
}
return props.modelValue
})
const extractValue = (e) => (e && typeof e === 'object' && 'value' in e) ? e.value : e
const handleUpdate = (e) => {
const val = extractValue(e)
if (props.optionValue && val !== null && val !== undefined) {
const selectedOption = props.options.find(o => o[props.optionValue] === val) ?? null
emit('update:modelValue', selectedOption)
emit('change', selectedOption)
} else {
emit('update:modelValue', val)
emit('change', val)
}
}
const handleFocus = (e) => emit('focus', e)
const handleBlur = (e) => emit('blur', e)
const directionClass = computed(() => {
return props.direction === 'ltr' ? 'ui-select--ltr' : 'ui-select--rtl'
})
const hasPrefixes = computed(() => {
return props.prefixStart || props.prefixEnd || props.iconStart || props.iconEnd
})
const prefixClasses = computed(() => {
const classes = [directionClass.value]
if (props.prefixStart) classes.push('ui-select-with-prefix-start')
if (props.prefixEnd) classes.push('ui-select-with-prefix-end')
if (props.iconStart) classes.push('ui-select-with-icon-start')
if (props.iconEnd) classes.push('ui-select-with-icon-end')
return classes.join(' ')
})
const prefixColorClass = computed(() => {
return `text-${props.prefixColor}`
})
const iconColorClass = computed(() => {
return `text-${props.iconColor}`
})
const selectClassesNoBorder = computed(() => {
const classes = []
if (props.error) {
classes.push('has-error')
}
if (props.disabled) {
classes.push('disabled')
}
return classes.join(' ')
})
const selectClasses = computed(() => {
const classes = []
if (props.error) {
classes.push('has-error')
}
if (props.disabled) {
classes.push('disabled')
}
return classes.join(' ')
})
</script>