-
-
Save jahidanowar/3e6014d538d8bcd80cb453470c973b2d to your computer and use it in GitHub Desktop.
FormTags Vue Component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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