Textarea Component
First Pattern
Features
- All Textarea attributes as props
- Debounce
- autofocus support
- Error, Disabled, Focus State
- optional error, label, placeholder texts etc
Dependencies
- "tailwindcss" - for styling
- "vueuse" - for debounce (useDebounceFn)
Demo
Address is:
Normal Mode / No Resize / 4 rows
Debounce Mode (500 milliseconds) / Resize Y
Disabled State / No Resize
Error State
address 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 {
modelValue?: string | number;
id?: string;
label?: string;
placeholder?: string;
required?: boolean;
error?: boolean;
errorText?: string;
disabled?: boolean;
maxlength?: string;
autofocus?: boolean;
debounceTime?: number;
maxWaitTime?: number;
rows?: number;
cols?: number;
readonly?: boolean;
wrap?: "hard" | "soft";
resize?: "resize-none" | "resize-y" | "resize-x" | "";
}
const props = withDefaults(defineProps<Prop>(), {
modelValue: "",
id: "",
label: "",
placeholder: "",
required: false,
error: false,
errorText: "",
disabled: false,
max: "",
autofocus: false,
debounceTime: 0,
maxWaitTime: 5000,
rows: 3,
readonly: false,
wrap: "soft",
resize: "",
});
const textareaRef = ref() as Ref<HTMLTextAreaElement>;
const handleInput = (e: Event) => {
const target = e.target as HTMLTextAreaElement;
emit("onTextChanged", 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() {
textareaRef.value.focus();
}
watch(textareaRef, () => {
if (props.autofocus === true) {
textareaRef.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 textareaClass = computed(() => {
let base = `px-[14px] py-2 w-full outline-none text-sm rounded-[4px] transition-all ease duration-200 ${props.resize}`;
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>
<textarea
:id="id"
:maxlength="maxlength"
:rows="rows"
:cols="cols"
:value="modelValue"
@input="handleInput"
:required="required"
:disabled="disabled"
ref="inputFieldRef"
:class="textareaClass"
:placeholder="placeholder"
:readonly="readonly"
:wrap="wrap"
></textarea>
<p
class="block text-xs first-letter:capitalize mt-[6px] text-red-500"
v-if="error && errorText && !disabled"
>
{{ errorText }}
</p>
</div>
</template>
<style scoped></style>
vue
<script setup lang="ts">
import { ref } from "vue";
import Textarea1 from "../components/Textarea/Textarea1.vue";
const address = ref("");
</script>
<template>
<div class="p-3 mt-4">
<p>Address is: {{ address }}</p>
<div>
<p class="italic text-xs font-medium uppercase">
Normal Mode / No Resize / 4 rows
</p>
<Textarea1
v-model="address"
label="Enter address"
placeholder="eg. Foo"
resize="resize-none"
:rows="4"
></Textarea1>
</div>
<div>
<p class="italic text-xs font-medium uppercase">
Debounce Mode (500 milliseconds) / Resize Y
</p>
<Textarea1
v-model="address"
label="Enter address"
placeholder="eg. Foo"
:debounce-time="500"
resize="resize-y"
></Textarea1>
</div>
<div>
<p class="italic text-xs font-medium uppercase">
Disabled State / No Resize
</p>
<Textarea1
v-model="address"
label="Enter address"
placeholder="eg. Foo"
disabled
resize="resize-none"
></Textarea1>
</div>
<div>
<p class="italic text-xs font-medium uppercase">Error State</p>
<Textarea1
v-model="address"
label="Enter address"
placeholder="eg. Foo"
required
error
error-text="address is required"
></Textarea1>
</div>
</div>
</template>