Forms
Validation form Preview link
form.vue
<template>
<form class="row g-3 needs-validation custom-input" novalidate @submit.prevent="submitForm()">
<div class="col-md-4 position-relative">
<InputWrapper :title="'First Name'" :required="true">
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'First name is required.'"
v-model:modelValue="tooltipValidationForm.first_name"
:inputId="'tooltip-form-first-name'"
:placeholder="'Enter first name'"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-md-4 position-relative">
<InputWrapper :title="'Last Name'" :required="true">
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'Last name is required.'"
v-model:modelValue="tooltipValidationForm.last_name"
:inputId="'tooltip-form-last-name'"
:placeholder="'Enter last name'"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-md-4 position-relative">
<InputWrapper :title="'Username'" :required="true">
<div class="input-group has-validation">
<span class="input-group-text" id="validationTooltipUsernamePrepend">@</span>
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'User name is required.'"
v-model:modelValue="tooltipValidationForm.user_name"
:inputId="'tooltip-form-user-name'"
:placeholder="'Enter user name'"
:tooltipValidation="true"
/>
</div>
</InputWrapper>
</div>
<div class="col-md-6 position-relative">
<InputWrapper :title="'City'" :required="true">
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'City is required.'"
v-model:modelValue="tooltipValidationForm.city"
:inputId="'tooltip-form-city'"
:placeholder="'Enter city'"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-md-3 position-relative">
<InputWrapper :title="'State'" :required="true">
<Select
getValueKey="label"
display-key="label"
:placeholder="'Select state'"
v-model="tooltipValidationForm.state"
:errorMessage="'Please select state.'"
:options="states"
:formSubmitted="formSubmitted"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-md-3 position-relative">
<InputWrapper :title="'Zip'" :required="true">
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'Zip is required.'"
v-model:modelValue="tooltipValidationForm.zip"
:inputId="'tooltip-form-zip'"
:placeholder="'Enter zip'"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-12">
<button class="btn btn-primary" type="submit">Submit form</button>
</div>
</form>
</template>
<script setup lang="ts">
import { initInputField, initSelectField } from '~/public/data/common';
import { states } from '~/public/data/country';
import { validateForm } from '~/utils/validators/formValidators';
const { resetForm } = baseUtils();
let formSubmitted = ref(false);
let tooltipValidationForm = reactive({
first_name: initInputField(),
last_name: initInputField(),
user_name: initInputField(),
city: initInputField(),
state: initSelectField(),
zip: initInputField(),
});
function submitForm() {
formSubmitted.value = true;
const { isValid, formData } = validateForm(tooltipValidationForm);
if (isValid) {
tooltipValidationForm = resetForm(tooltipValidationForm);
formSubmitted.value = false;
}
}
</script>
<style scoped></style>
InputWrapper.vue
<template>
<label class="form-label" :class="props.class" v-if="!props.labelPositionBottom"
>{{ props.title }}
<span class="txt-danger" v-if="props.required">*</span>
</label>
<slot />
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
title: string;
class?: string;
labelPositionBottom?: boolean;
required?: boolean;
}>(),
{
class: '',
required: false,
},
);
</script>
<style scoped></style>
InputField.vue
<template>
<input
:value="modelValue?.data ? modelValue?.data : ''"
:type="inputType"
:id="inputId"
v-bind:placeholder="isPlaceholder ? placeholder || 'Enter Value' : undefined"
class="form-control"
:class="[props.class, { 'is-invalid ': modelValue?.errorMessage && !props.browserValidation }, { 'animated input-shake': animationClass }]"
@input="onInput"
@focusout="onInput"
@focus="showBadge()"
@blur="hideBadge()"
v-if="inputType !== 'textarea' && inputType !== 'file'"
:required="props.required && props.browserValidation"
:disabled="props.disabled"
:list="props.datalist?.length ? `datalistOptions-${inputId}` : undefined"
:maxlength="props.maxlength"
:min="props.inputType === 'date' && props.minDate ? formatDateForInput(props.minDate) : undefined"
:max="props.inputType === 'date' && props.maxDate ? formatDateForInput(props.maxDate) : undefined"
/>
<slot />
<template v-if="modelValue?.errorMessage && required && !props.browserValidation">
<div class="invalid-tooltip" v-if="props.tooltipValidation">{{ modelValue?.errorMessage }}</div>
<div class="invalid-feedback" v-else>{{ modelValue?.errorMessage }}</div>
</template>
<div class="helper-text" v-if="props.helperText">
<p class="fst-italic c-o-light" v-html="`*${props.helperText}`"></p>
</div>
</template>
<script setup lang="ts">
import { validateNonEmptyFields, validateEmail } from '~/utils/validators/InputFieldValidators';
import type { InputField, Select } from '~/types/common';
interface ValidationStatus {
errorMessage: string;
valid: boolean;
}
const props = withDefaults(
defineProps<{
formSubmitted?: boolean;
inputId?: string;
modelValue?: InputField;
errorMessage?: string;
class?: string;
placeholder?: string;
inputType?: string;
required?: boolean;
minLength?: number;
rows?: number;
multiple?: boolean;
helperText?: string;
disabled?: boolean;
tooltipValidation?: boolean;
browserValidation?: boolean;
animation?: boolean;
datalist?: Select[];
maxlength?: number;
isPlaceholder?: boolean;
showLengthBadge?: boolean;
formatValue?: boolean;
formatFunction?: Function | null;
minDate?: Date;
maxDate?: Date;
}>(),
{
inputType: 'text',
required: true,
rows: 3,
multiple: false,
helperText: '',
disabled: false,
tooltipValidation: false,
browserValidation: false,
animation: false,
datalist: () => [],
isPlaceholder: true,
showLengthBadge: false,
formatValue: false,
formatFunction: null,
},
);
const emits = defineEmits(['update:modelValue', 'badgeVisible']);
let validStatus = ref<ValidationStatus>({
errorMessage: '',
valid: false,
});
let changed = ref(false);
const animationClass = ref('');
watch(
() => props.formSubmitted,
() => {
props.formSubmitted && updated(props.modelValue?.data || '');
},
{ deep: true },
);
onMounted(() => {
if (props.formSubmitted) {
updated(props.modelValue?.data || '');
}
});
function onInput(event: Event) {
const target = event.target as HTMLInputElement | null;
if (!target) return;
updated(target.value);
}
function updated(inputValue?: string | number | File | null) {
changed.value = true;
// Handle validation
if (props.required) {
if (props.inputType === 'email') {
validStatus.value = validateEmail(String(inputValue));
} else if (props.inputType === 'file') {
const isValid = !!inputValue;
validStatus.value = {
valid: isValid,
errorMessage: isValid ? '' : props.errorMessage || 'File is required.',
};
} else {
validStatus.value = validateNonEmptyFields({
value: String(inputValue),
minLength: props.minLength,
errorMessage: props.errorMessage,
});
}
} else {
validStatus.value = { valid: true, errorMessage: '' };
}
}
// Prepare emitted value
let data = inputValue;
if (props.inputType === 'number') {
data = inputValue === '' || inputValue == null ? '' : Number(inputValue);
} else if (props.inputType === 'file') {
data = inputValue instanceof File ? inputValue : null;
}
// Emit value
emits('update:modelValue', {
data,
errorMessage: validStatus.value.errorMessage,
});
</script>
<style scoped></style>
Base input Preview link
<div class="card-wrapper border rounded-3">
<form class="grid grid-cols-12 gap-4">
<div class="col-span-12">
<label class="form-label" for="inputEmail">Email Address</label>
<input class="form-control" id="inputEmail" type="email" placeholder="name@example.com">
</div>
</form>
</div>
Checkbox & Radio Preview link
<template>
<div class="container">
<h4>Checkbox & Radio</h4>
<div class="group">
<!-- Radio Group -->
<div class="column">
<label>
<input type="radio" name="radio" value="option1" />
<span>Option 1</span>
</label>
<label class="disabled">
<input type="radio" name="radio" disabled />
<span>Disabled</span>
</label>
<label>
<input type="radio" name="radio" checked />
<span>Checked</span>
</label>
</div>
<!-- Checkbox Group -->
<div class="column">
<label>
<input type="checkbox" />
<span>Default</span>
</label>
<label class="disabled">
<input type="checkbox" disabled />
<span>Disabled</span>
</label>
<label>
<input type="checkbox" checked />
<span>Checked</span>
</label>
</div>
</div>
</div>
</template>
<script setup></script>
<style scoped></style>
Datepicker Official link Preview link
Installation
npm install flatpickr
datepicker.vue
<template>
<Flatpickr class="form-control digits" placeholder="dd-mm-yyyy" v-model="dateState.defaultDate" :config="dateConfigs.dateConfig" />
</template>
<script setup>
import flatpickr from 'flatpickr'
import 'flatpickr/dist/flatpickr.css'
const date = new Date();
const dateState = reactive({
defaultDate: null as string | Date | null,
});
const dateConfigs = reactive({
dateConfig: {
dateFormat: 'd-m-Y',
},
})
</script>
Uninstalling Package
npm uninstall flatpickr
Switch Preview link
<label class="switch">
<input type="checkbox" checked="" data-bs-original-title="" title="">
<span class="switch-state"></span>
</label>
<label class="switch">
<input type="checkbox" data-bs-original-title="" title="">
<span class="switch-state"></span>
</label>
Typeahead Preview link
typeahead.vue
<template>
<div>
<Vue3SimpleTypeahead
id="typeahead-basic"
placeholder="Search for a state"
:items="states"
v-model="model"
:minInputLength="2"
@selectItem="onSelectState"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Vue3SimpleTypeahead from 'vue3-simple-typeahead';
import 'vue3-simple-typeahead/dist/vue3-simple-typeahead.css';
import { states } from '~/data/states';
const model = ref('');
const onSelectState = (selected) => {
model.value = selected;
};
</script>
states.ts
export const states = [
'Alabama',
'Alaska',
'American Samoa',
'Arizona',
'Arkansas',
'California',
'Colorado',
'Connecticut',
'Delaware',
'District Of Columbia',
'Federated States Of Micronesia',
'Florida',
'Georgia',
'Guam',
'Hawaii',
'Idaho',
'Illinois',
'Indiana',
'Iowa',
'Kansas',
'Kentucky',
'Louisiana',
'Maine',
'Marshall Islands',
'Maryland',
'Massachusetts',
'Michigan',
'Minnesota',
'Mississippi',
'Missouri',
'Montana',
'Nebraska',
'Nevada',
'New Hampshire',
'New Jersey',
'New Mexico',
'New York',
'North Carolina',
'North Dakota',
'Northern Mariana Islands',
'Ohio',
'Oklahoma',
'Oregon',
'Palau',
'Pennsylvania',
'Puerto Rico',
'Rhode Island',
'South Carolina',
'South Dakota',
'Tennessee',
'Texas',
'Utah',
'Vermont',
'Virgin Islands',
'Virginia',
'Washington',
'West Virginia',
'Wisconsin',
'Wyoming',
];