87 lines
1.7 KiB
Vue
87 lines
1.7 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* @file FormInput.vue
|
|
* @description Reusable input component with label, error handling, and support for disabled/required states.
|
|
*/
|
|
|
|
defineProps<{
|
|
modelValue: string
|
|
label: string
|
|
type?: string
|
|
placeholder?: string
|
|
error?: string
|
|
required?: boolean
|
|
disabled?: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
/** Update v-model value */
|
|
'update:modelValue': [value: string]
|
|
}>()
|
|
|
|
const updateValue = (event: Event) => {
|
|
emit('update:modelValue', (event.target as HTMLInputElement).value)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="form-group" :class="{ 'has-error': error }">
|
|
<label class="input-label">
|
|
{{ label }}
|
|
<span v-if="required" class="required-mark">*</span>
|
|
</label>
|
|
<input
|
|
:type="type || 'text'"
|
|
:value="modelValue"
|
|
class="input-field"
|
|
:class="{ 'input-error': error }"
|
|
:placeholder="placeholder"
|
|
:disabled="disabled"
|
|
@input="updateValue"
|
|
>
|
|
<span v-if="error" class="error-message">
|
|
<span class="error-icon">⚠</span>
|
|
{{ error }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.form-group {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.required-mark {
|
|
color: var(--error);
|
|
margin-left: 2px;
|
|
}
|
|
|
|
.input-error {
|
|
border-color: var(--error) !important;
|
|
background-color: rgba(239, 68, 68, 0.05);
|
|
}
|
|
|
|
.input-error:focus {
|
|
outline-color: var(--error) !important;
|
|
}
|
|
|
|
.error-message {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
color: var(--error);
|
|
font-size: 12px;
|
|
margin-top: 6px;
|
|
animation: shake 0.3s ease-in-out;
|
|
}
|
|
|
|
.error-icon {
|
|
font-size: 14px;
|
|
}
|
|
|
|
@keyframes shake {
|
|
0%, 100% { transform: translateX(0); }
|
|
25% { transform: translateX(-4px); }
|
|
75% { transform: translateX(4px); }
|
|
}
|
|
</style>
|