Input Component
First Pattern
Features
- Multi type support
- Debounce
- autofocus support
- Error, Disabled, Focus State
- optional error, label, placeholder texts etc
Dependencies
- "tailwindcss" - for styling
- "vueuse" - for debounce (useDebounceFn)
Demo
Name is:
Normal Mode
Debounce Mode (500 milliseconds)
Disabled State
Error State
name is required
Code
vue
<script setup lang="ts">
import { ref, computed, watch, type Ref } from "vue";
import { useDebounceFn } from "@vueuse/core";
const emit = defineEmits<{
(event: "update:modelValue", value: string | number): void;
(event: "onTextChanged", value: string | number): void;
}>();
interface Prop {
type?: string;
modelValue?: string | number;
id?: string;
label?: string;
placeholder?: string;
required?: boolean;
error?: boolean;
errorText?: string;
disabled?: boolean;
max?: string;
autofocus?: boolean;
debounceTime?: number;
maxWaitTime?: number;
}
const props = withDefaults(defineProps<Prop>(), {
type: "text",
modelValue: "",
id: "",
label: "",
placeholder: "",
required: false,
error: false,
errorText: "",
disabled: false,
max: "",
autofocus: false,
debounceTime: 0,
maxWaitTime: 5000,
});
const inputFieldRef = ref() as Ref<HTMLInputElement>;
const handleInput = (e: Event) => {
const target = e.target as HTMLInputElement;
emit("onTextChanged", target.value);
if (props.type === "number") {
return debouncedEmit("update:modelValue", Number(target.value));
}
debouncedEmit("update:modelValue", target.value);
};
/**
* this controls the debounce time for the input field
* default is 0 max is 5000
*/
const debouncedEmit = useDebounceFn(
(emitText, value) => {
emit(emitText, value);
},
props.debounceTime,
{ maxWait: props.maxWaitTime }
);
function focusInput() {
inputFieldRef.value.focus();
}
watch(inputFieldRef, () => {
if (props.autofocus === true) {
inputFieldRef.value.focus();
}
});
const labelClass = computed(() => {
let base = "block text-[13px] first-letter:capitalize mb-[6px] w-fit";
if (props.disabled) {
base += " text-gray-300 dark:text-gray-700";
} else if (props.error) {
base += " text-red-500";
} else {
base += " text-gray-700 dark:text-gray-300";
}
return base;
});
const inputClass = computed(() => {
let base =
"h-[43px] px-[14px] w-full outline-none text-sm rounded-[4px] transition-all ease duration-200";
if (props.disabled) {
base += " placeholder:text-gray-300 dark:placeholder:text-gray-700 ring-[1px] ring-gray-300 dark:ring-gray-700 cursor-not-allowed";
} else if (props.error) {
base += " ring-[1.5px] ring-red-500";
} else {
base +=
" placeholder:text-grey-300 ring-[1px] ring-green-300 focus:ring-[1.5px] focus:ring-green-600";
}
return base;
});
</script>
<template>
<div class="w-full">
<label @click="focusInput" :class="labelClass" v-if="label" :for="id">
{{ label }}
</label>
<input
:class="inputClass"
:placeholder="placeholder"
:id="id"
:type="type"
:max="max"
:value="modelValue"
@input="handleInput"
:required="required"
:disabled="disabled"
ref="inputFieldRef"
/>
<p
class="block text-xs first-letter:capitalize mt-[6px] text-red-500"
v-if="error && errorText && !disabled"
>
{{ errorText }}
</p>
</div>
</template>
<style scoped>
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type="number"] {
-moz-appearance: textfield;
}
</style>vue
<script setup lang="ts">
import { ref } from "vue";
import Input1 from "../components/Input/Input1.vue";
const name = ref("");
</script>
<template>
<div class="p-3 mt-4">
<p>Name is: {{ name }}</p>
<div>
<p class="italic text-xs font-medium uppercase">Normal Mode</p>
<Input1 v-model="name" label="Enter name" placeholder="eg. Foo" />
</div>
<div>
<p class="italic text-xs font-medium uppercase">
Debounce Mode (500 milliseconds)
</p>
<Input1
v-model="name"
label="Enter name"
placeholder="eg. Foo"
:debounce-time="500"
/>
</div>
<div>
<p class="italic text-xs font-medium uppercase">Disabled State</p>
<Input1
v-model="name"
label="Enter name"
placeholder="eg. Foo"
disabled
/>
</div>
<div>
<p class="italic text-xs font-medium uppercase">Error State</p>
<Input1
v-model="name"
label="Enter name"
placeholder="eg. Foo"
required
error
error-text="name is required"
/>
</div>
</div>
</template>