Skip to content

Instantly share code, notes, and snippets.

@jahidanowar
Created October 25, 2023 12:18
Show Gist options
  • Save jahidanowar/3e6014d538d8bcd80cb453470c973b2d to your computer and use it in GitHub Desktop.
Save jahidanowar/3e6014d538d8bcd80cb453470c973b2d to your computer and use it in GitHub Desktop.
FormTags Vue Component
<script setup lang="ts">
import { StringSchema } from "yup";
const props = defineProps<{
modelValue: string[];
singleField?: string;
separator?: string;
placeholder?: string;
id?: string;
validtion?: StringSchema;
error?: string;
outerClasses?: string;
label?: string;
prependIcon?: string;
appendIcon?: string;
}>();
const emit = defineEmits<{
(event: "update:modelValue", ...args: any[]): void;
(event: "update:singleField", ...args: any[]): void;
(event: "onAppendIconClick", ...args: any[]): void;
(event: "onPrependIconClick", ...args: any[]): void;
}>();
const onAppendIconClick = () => {
emit("onAppendIconClick");
};
const onPrependIconClick = () => {
emit("onPrependIconClick");
};
const list = computed({
get() {
return props.modelValue;
},
set(val: string[]) {
emit("update:modelValue", val);
},
});
function handleAddtag(e: KeyboardEvent) {
e.preventDefault();
const target = e.target as HTMLInputElement;
const value = target.value;
if (value && validate(value)) {
list.value.push(value);
target.value = "";
emit("update:modelValue", list.value);
emit("update:singleField", "");
}
}
function handlePaste(e: ClipboardEvent) {
const target = e.target as HTMLInputElement;
const pasteData = e.clipboardData?.getData("text");
if (pasteData) {
const tags = pasteData.split(props.separator || ",");
tags.forEach((tag) => {
if (tag && validate(tag)) {
list.value.push(tag);
}
});
emit("update:modelValue", list.value);
}
target.value = "";
emit("update:singleField", "");
}
function handleBackspace(e: KeyboardEvent) {
const target = e.target as HTMLInputElement;
const value = target.value;
if (!value && list.value.length) {
list.value.pop();
emit("update:modelValue", list.value);
}
}
function handleFieldChange(e: Event) {
// Check the input value is valid or not
const target = e.target as HTMLInputElement;
const value = target.value;
emit("update:singleField", value);
}
function validate(val: string): boolean {
if (!props.validtion || props.validtion === undefined) return true;
try {
props.validtion.validateSync(val);
return true;
} catch (err) {
return false;
}
}
</script>
<template>
<div
class="form-group"
:class="{
error: error,
[outerClasses || '']: outerClasses,
}"
>
<label :for="id">{{ label }}</label>
<div class="relative">
<button
v-if="prependIcon"
type="button"
class="absolute inset-y-0 left-0 flex items-center pl-3"
@click="onPrependIconClick"
>
<slot name="prependIcon">
<Icon :name="prependIcon" class="w-5 h-5 text-gray-400" />
</slot>
</button>
<div
class="form-control"
:class="{
'!pl-10': prependIcon,
}"
>
<ul class="flex gap-1 flex-wrap">
<li
v-for="item in list"
:key="item"
class="bg-gray-200 py-1 px-2 rounded text-xs inline-flex items-center"
>
<span>
{{ item }}
</span>
<button
type="button"
class="ml-1"
@click="list.splice(list.indexOf(item), 1)"
>
<Icon name="bx:x" class="w-4 h-4" />
</button>
</li>
<input
type="text"
class="appearance-none outline-none ml-1 grow p-0 border-none focus:outline-none focus:ring-0 text-sm"
:placeholder="placeholder"
:id="id"
:value="singleField"
@keydown.enter="handleAddtag"
@paste.prevent="handlePaste"
@keydown.backspace="handleBackspace"
@keydown.space="handleAddtag"
@change="handleFieldChange"
/>
</ul>
</div>
<button
v-if="appendIcon"
type="button"
class="absolute inset-y-0 right-0 flex items-center pr-3"
@click="onAppendIconClick"
>
<slot name="appendIcon">
<Icon :name="appendIcon" class="w-5 h-5 text-gray-400" />
</slot>
</button>
</div>
<span v-if="error" class="error-message">{{ error }}</span>
</div>
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment