complete Complete User Request Submission section RAD-1
This commit is contained in:
@@ -0,0 +1,515 @@
|
||||
<template>
|
||||
<div class="ui-input-wrapper" :class="directionClass">
|
||||
<label v-if="label" :for="id" class="ui-input-label">
|
||||
{{ label }}
|
||||
<span v-if="required" class="ui-input-required-indicator">*</span>
|
||||
</label>
|
||||
|
||||
<div class="ui-input">
|
||||
<div v-if="hasPrefixes" :class="['.ui-input-wrapper-border', prefixClasses]">
|
||||
<i v-if="actualIconEnd" :class="[actualIconEnd, iconColorClass]" class="ui-input-icon-end !mt-2"></i>
|
||||
<div
|
||||
v-if="isSvgIconEnd && svgIconEndContent"
|
||||
class="ui-input-icon-end has-svg"
|
||||
:class="iconColorClass"
|
||||
v-html="svgIconEndContent"
|
||||
/>
|
||||
<span v-if="prefixEnd" class="ui-input-prefix-end" :class="prefixColorClass">{{ prefixEnd }}</span>
|
||||
<InputText
|
||||
:id="id"
|
||||
:model-value="displayValue"
|
||||
:class="inputClassesNoBorder"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:type="inputType"
|
||||
:maxlength="maxlength"
|
||||
:minlength="minlength"
|
||||
:autocomplete="autocomplete"
|
||||
:autofocus="autofocus"
|
||||
:spellcheck="spellcheck"
|
||||
:tabindex="tabindex"
|
||||
@input="handleInput"
|
||||
@change="handleChange"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@keyup="handleKeyup"
|
||||
@keydown="handleKeydown"
|
||||
/>
|
||||
<span v-if="prefixStart" class="ui-input-prefix-start" :class="prefixColorClass">{{ prefixStart }}</span>
|
||||
<i v-if="actualIconStart" :class="[actualIconStart, iconColorClass]" class="ui-input-icon-start !mt-2"></i>
|
||||
<div
|
||||
v-if="isSvgIconStart && svgIconStartContent"
|
||||
class="ui-input-icon-start has-svg"
|
||||
:class="iconColorClass"
|
||||
v-html="svgIconStartContent"
|
||||
/>
|
||||
<i
|
||||
v-if="showPasswordToggle"
|
||||
:class="[passwordToggleIcon, iconColorClass]"
|
||||
class="ui-input-password-toggle !mt-2 cursor-pointer"
|
||||
@click="togglePasswordVisibility"
|
||||
></i>
|
||||
</div>
|
||||
<div v-else-if="prefix" :class="['.ui-input-wrapper-border', props.wrapperBorderClass, directionClass]">
|
||||
<span class="ui-input-prefix">{{ prefix }}</span>
|
||||
<InputText
|
||||
:id="id"
|
||||
:model-value="displayValue"
|
||||
:class="inputClassesNoBorder"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:type="inputType"
|
||||
:maxlength="maxlength"
|
||||
:minlength="minlength"
|
||||
:autocomplete="autocomplete"
|
||||
:autofocus="autofocus"
|
||||
:spellcheck="spellcheck"
|
||||
:tabindex="tabindex"
|
||||
@input="handleInput"
|
||||
@change="handleChange"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@keyup="handleKeyup"
|
||||
@keydown="handleKeydown"
|
||||
/>
|
||||
<i
|
||||
v-if="showPasswordToggle"
|
||||
:class="[passwordToggleIcon, iconColorClass]"
|
||||
class="ui-input-password-toggle !mt-2 cursor-pointer"
|
||||
@click="togglePasswordVisibility"
|
||||
></i>
|
||||
</div>
|
||||
<div v-else class="relative">
|
||||
<InputText
|
||||
:id="id"
|
||||
:model-value="displayValue"
|
||||
:class="inputClasses"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:type="inputType"
|
||||
:maxlength="maxlength"
|
||||
:minlength="minlength"
|
||||
:autocomplete="autocomplete"
|
||||
:autofocus="autofocus"
|
||||
:spellcheck="spellcheck"
|
||||
:tabindex="tabindex"
|
||||
@input="handleInput"
|
||||
@change="handleChange"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@keyup="handleKeyup"
|
||||
@keydown="handleKeydown"
|
||||
/>
|
||||
<i
|
||||
v-if="showPasswordToggle"
|
||||
:class="[passwordToggleIcon, iconColorClass]"
|
||||
class="ui-input-password-toggle absolute top-1/2 left-3 transform -translate-y-1/2 cursor-pointer"
|
||||
@click="togglePasswordVisibility"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<small v-if="error" class="ui-input-error-message">
|
||||
{{ error }}
|
||||
</small>
|
||||
|
||||
<small v-if="hint" class="ui-input-hint-message">
|
||||
{{ hint }}
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { convertPersianToEnglish } from '../../utils/numberUtils.js'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
validator: (value) => ['text', 'password', 'email', 'number', 'tel', 'url', 'search'].includes(value)
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: () => `input-${Math.random().toString(36).substr(2, 9)}`
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
minlength: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: 'off'
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
spellcheck: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tabindex: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator: (value) => ['small', 'default', 'large'].includes(value)
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator: (value) => ['default', 'outlined', 'filled'].includes(value)
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
prefixStart: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
prefixEnd: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
iconStart: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
iconEnd: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'rtl',
|
||||
validator: (value) => ['rtl', 'ltr'].includes(value)
|
||||
},
|
||||
prefixColor: {
|
||||
type: String,
|
||||
default: 'dominant-700'
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: 'dominant-700'
|
||||
},
|
||||
passwordDisplayType: {
|
||||
type: String,
|
||||
default: 'dots',
|
||||
validator: (value) => ['default', 'dots', 'asterisks'].includes(value)
|
||||
},
|
||||
convertPersianNumbers: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
wrapperBorderClass: {
|
||||
type: String,
|
||||
default: 'ui-input-wrapper-border'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'input',
|
||||
'change',
|
||||
'focus',
|
||||
'blur',
|
||||
'keyup',
|
||||
'keydown'
|
||||
])
|
||||
|
||||
const inputValue = ref(props.modelValue)
|
||||
const passwordVisible = ref(false)
|
||||
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
inputValue.value = newValue
|
||||
})
|
||||
|
||||
watch(inputValue, (newValue) => {
|
||||
emit('update:modelValue', newValue)
|
||||
})
|
||||
|
||||
const directionClass = computed(() => {
|
||||
return props.direction === 'ltr' ? 'ui-input--ltr' : 'ui-input--rtl'
|
||||
})
|
||||
|
||||
const hasPrefixes = computed(() => {
|
||||
return props.prefixStart || props.prefixEnd || props.iconStart || props.iconEnd
|
||||
})
|
||||
|
||||
const isSvgIconStart = computed(() => {
|
||||
return props.iconStart && props.iconStart.startsWith('svg:')
|
||||
})
|
||||
|
||||
const isSvgIconEnd = computed(() => {
|
||||
return props.iconEnd && props.iconEnd.startsWith('svg:')
|
||||
})
|
||||
|
||||
const svgIconStartName = computed(() => {
|
||||
return isSvgIconStart.value ? props.iconStart.replace('svg:', '') : ''
|
||||
})
|
||||
|
||||
const svgIconEndName = computed(() => {
|
||||
return isSvgIconEnd.value ? props.iconEnd.replace('svg:', '') : ''
|
||||
})
|
||||
|
||||
const actualIconStart = computed(() => {
|
||||
return isSvgIconStart.value ? '' : props.iconStart
|
||||
})
|
||||
|
||||
const actualIconEnd = computed(() => {
|
||||
return isSvgIconEnd.value ? '' : props.iconEnd
|
||||
})
|
||||
|
||||
const showPasswordToggle = computed(() => {
|
||||
return props.type === 'password'
|
||||
})
|
||||
|
||||
const inputType = computed(() => {
|
||||
if (props.type === 'password') {
|
||||
if (passwordVisible.value || props.passwordDisplayType !== 'default') {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
}
|
||||
return props.type
|
||||
})
|
||||
|
||||
const displayValue = computed(() => {
|
||||
if (props.type === 'password' && !passwordVisible.value && inputValue.value) {
|
||||
if (props.passwordDisplayType === 'dots') {
|
||||
return '•'.repeat(inputValue.value.length)
|
||||
} else if (props.passwordDisplayType === 'asterisks') {
|
||||
return '*'.repeat(inputValue.value.length)
|
||||
}
|
||||
}
|
||||
return inputValue.value
|
||||
})
|
||||
|
||||
const passwordToggleIcon = computed(() => {
|
||||
return passwordVisible.value ? 'fi-rr-eye-crossed' : 'fi-rr-eye'
|
||||
})
|
||||
|
||||
const prefixClasses = computed(() => {
|
||||
const classes = [directionClass.value]
|
||||
|
||||
if (props.prefixStart) classes.push('ui-input-with-prefix-start')
|
||||
if (props.prefixEnd) classes.push('ui-input-with-prefix-end')
|
||||
if (props.iconStart || isSvgIconStart.value) classes.push('ui-input-with-icon-start')
|
||||
if (props.iconEnd || isSvgIconEnd.value) classes.push('ui-input-with-icon-end')
|
||||
if (showPasswordToggle.value) classes.push('ui-input-with-password-toggle')
|
||||
|
||||
if (props.wrapperBorderClass) {
|
||||
classes.push(props.wrapperBorderClass + ' ui-input-wrapper-border')
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
})
|
||||
|
||||
const prefixColorClass = computed(() => {
|
||||
return `text-${props.prefixColor}`
|
||||
})
|
||||
|
||||
const iconColorClass = computed(() => {
|
||||
return `text-${props.iconColor}`
|
||||
})
|
||||
|
||||
const loadSvgContent = async (svgName) => {
|
||||
try {
|
||||
const svgModule = await import(`../../components/svg/${svgName}.svg?raw`)
|
||||
return svgModule.default
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const svgIconStartContent = ref('')
|
||||
const svgIconEndContent = ref('')
|
||||
|
||||
watch(() => svgIconStartName.value, async (newName) => {
|
||||
if (newName) {
|
||||
const content = await loadSvgContent(newName)
|
||||
svgIconStartContent.value = content
|
||||
} else {
|
||||
svgIconStartContent.value = ''
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => svgIconEndName.value, async (newName) => {
|
||||
if (newName) {
|
||||
const content = await loadSvgContent(newName)
|
||||
svgIconEndContent.value = content
|
||||
} else {
|
||||
svgIconEndContent.value = ''
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const togglePasswordVisibility = () => {
|
||||
passwordVisible.value = !passwordVisible.value
|
||||
}
|
||||
|
||||
const inputClassesNoBorder = computed(() => {
|
||||
const classes = []
|
||||
|
||||
switch (props.size) {
|
||||
case 'small':
|
||||
classes.push('p-2', 'text-xs')
|
||||
break
|
||||
case 'large':
|
||||
classes.push('p-4', 'text-base')
|
||||
break
|
||||
default:
|
||||
classes.push('p-3', 'text-sm')
|
||||
}
|
||||
|
||||
if (props.variant === 'filled') {
|
||||
classes.push('bg-gray-100')
|
||||
}
|
||||
|
||||
if (props.error) {
|
||||
classes.push('border-red-500')
|
||||
}
|
||||
|
||||
if (props.disabled) {
|
||||
classes.push('opacity-50', 'cursor-not-allowed')
|
||||
}
|
||||
|
||||
if (props.customClass) {
|
||||
classes.push(props.customClass)
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
})
|
||||
|
||||
const inputClasses = computed(() => {
|
||||
const classes = []
|
||||
|
||||
switch (props.size) {
|
||||
case 'small':
|
||||
classes.push('p-2', 'text-xs')
|
||||
break
|
||||
case 'large':
|
||||
classes.push('p-4', 'text-base')
|
||||
break
|
||||
default:
|
||||
classes.push('p-3', 'text-sm')
|
||||
}
|
||||
|
||||
switch (props.variant) {
|
||||
case 'outlined':
|
||||
classes.push('border-2')
|
||||
break
|
||||
case 'filled':
|
||||
classes.push('bg-gray-100')
|
||||
break
|
||||
default:
|
||||
classes.push('border')
|
||||
}
|
||||
|
||||
if (props.error) {
|
||||
classes.push('border-red-500')
|
||||
}
|
||||
|
||||
if (props.disabled) {
|
||||
classes.push('opacity-50', 'cursor-not-allowed')
|
||||
}
|
||||
|
||||
if (props.customClass) {
|
||||
classes.push(props.customClass)
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
})
|
||||
|
||||
const handleInput = (event) => {
|
||||
let newValue = event.target.value
|
||||
|
||||
if (props.convertPersianNumbers) {
|
||||
newValue = convertPersianToEnglish(newValue)
|
||||
event.target.value = newValue
|
||||
}
|
||||
|
||||
if (props.type === 'password' && !passwordVisible.value && props.passwordDisplayType !== 'default') {
|
||||
const currentLength = inputValue.value.length
|
||||
const newLength = newValue.length
|
||||
|
||||
if (newLength > currentLength) {
|
||||
inputValue.value = inputValue.value + newValue.slice(currentLength)
|
||||
} else if (newLength < currentLength) {
|
||||
inputValue.value = inputValue.value.slice(0, newLength)
|
||||
}
|
||||
} else {
|
||||
inputValue.value = newValue
|
||||
}
|
||||
emit('input', event)
|
||||
}
|
||||
|
||||
const handleChange = (event) => {
|
||||
emit('change', event)
|
||||
}
|
||||
|
||||
const handleFocus = (event) => {
|
||||
emit('focus', event)
|
||||
}
|
||||
|
||||
const handleBlur = (event) => {
|
||||
emit('blur', event)
|
||||
}
|
||||
|
||||
const handleKeyup = (event) => {
|
||||
emit('keyup', event)
|
||||
}
|
||||
|
||||
const handleKeydown = (event) => {
|
||||
emit('keydown', event)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user