Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 31x 31x 2x 1x 1x 1x 1x 1x 1x 1x 34x 34x 34x 34x 34x 34x 3x 31x 34x 1x 33x 34x 1x 1x 1x 1x 1x | <template>
<div class="w-full">
<label
v-if="label"
:for="inputId"
class="block text-sm font-medium text-secondary-700 mb-1"
>
{{ label }}
<span v-if="required" class="text-error-500">*</span>
</label>
<input
:id="inputId"
:type="type"
:value="modelValue"
:placeholder="placeholder"
:required="required"
:disabled="disabled"
:class="inputClasses"
@input="handleInput"
@blur="emit('blur', $event)"
@focus="emit('focus', $event)"
/>
<p v-if="error" class="mt-1 text-sm text-error-600">
{{ error }}
</p>
<p v-else-if="hint" class="mt-1 text-sm text-secondary-500">
{{ hint }}
</p>
</div>
</template>
<script setup lang="ts">
/**
* BaseInput
* @description Reusable input component with label, error, and hint support
*/
interface Props {
/** v-model binding value */
modelValue: string
/** Input type */
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'date' | 'time' | 'datetime-local'
/** Input label */
label?: string
/** Placeholder text */
placeholder?: string
/** Required field indicator */
required?: boolean
/** Disabled state */
disabled?: boolean
/** Error message */
error?: string
/** Hint text shown below input */
hint?: string
/** Custom id (auto-generated if not provided) */
id?: string
}
interface Emits {
(e: 'update:modelValue', value: string): void
(e: 'blur', event: FocusEvent): void
(e: 'focus', event: FocusEvent): void
}
const props = withDefaults(defineProps<Props>(), {
type: 'text',
required: false,
disabled: false
})
const emit = defineEmits<Emits>()
// Use Vue's SSR-safe useId() for consistent hydration
const generatedId = useId()
const inputId = computed(() => props.id || generatedId)
const inputClasses = computed(() => {
const base = [
'w-full px-3 py-2 rounded-lg border',
'transition-colors duration-200',
'focus:outline-none focus:ring-2 focus:ring-offset-0'
]
const states = props.error
? ['border-error-300', 'focus:border-error-500', 'focus:ring-error-500']
: ['border-secondary-300', 'focus:border-primary-500', 'focus:ring-primary-500']
const disabled = props.disabled
? 'bg-secondary-100 text-secondary-500 cursor-not-allowed'
: 'bg-white text-secondary-900'
return [...base, ...states, disabled]
})
function handleInput(event: Event) {
const target = event.target as HTMLInputElement
emit('update:modelValue', target.value)
}
</script>
|