Skip to content

Instantly share code, notes, and snippets.

@TheDutchCoder
Created August 17, 2021 15:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TheDutchCoder/696715b2f4c4fbbce83951754f3b1856 to your computer and use it in GitHub Desktop.
Save TheDutchCoder/696715b2f4c4fbbce83951754f3b1856 to your computer and use it in GitHub Desktop.
<template>
<div
class="relative rounded border bg-white w-full"
:class="!isValid && isDirty ? 'border-negative-700' : ''"
>
<label
:for="id"
class="absolute top-0 left-0 text-xs px-1 ml-2 bg-white leading-none transform-gpu -translate-y-1/2"
:class="hasFocus ? 'text-black-700' : 'text-secondary-700'"
>{{ label }}<span
v-if="$attrs.required !== undefined"
class="text-negative-800"
> *</span></label>
<input
:id="id"
ref="element"
:placeholder="placeholder"
class="rounded bg-white p-3 text-sm w-full focus:ring-2 outline-none transition-all"
:class="!isValid && isDirty ? 'focus:ring-negative-700' : 'focus:ring-primary-700'"
:value="modelValue"
v-bind="$attrs"
:aria-describedby="`${id}-error`"
:aria-invalid="!isValid"
@input="handleInput"
@focus="focus"
@blur="blur"
>
<transition
enter-active-class="transition duration-200 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition duration-150 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="!isValid && isDirty"
:id="`${id}-error`"
class="absolute bottom-0 right-0 mr-2 px-1 bg-white text-xs transform-gpu translate-y-1/2 text-negative-800"
role="alert"
>
{{ message }}
</div>
</transition>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, onMounted, watch } from 'vue'
import type { Ref } from 'vue'
export default defineComponent({
name: 'ChalkInput',
inheritAttrs: false,
props: {
modelValue: {
type: String,
required: true,
},
errorMessage: {
type: String,
default: 'This field is invalid',
},
placeholder: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
id: {
type: String,
required: true,
},
},
emits: ['update:modelValue'],
setup (props, { emit }) {
const element: Ref<HTMLInputElement | null> = ref(null)
const isDirty = ref(props.modelValue !== '')
const isValid = ref(false)
const hasFocus = ref(false)
const message = computed(() => props.modelValue === '' ? 'This field is required' : props.errorMessage)
onMounted(() => checkValidity())
watch(
() => props.modelValue,
() => checkValidity(),
{ flush: 'post' }
)
const handleInput = (evt: Event) => {
const value = (evt.target as HTMLInputElement).value
emit('update:modelValue', value)
setDirty()
checkValidity()
}
const setDirty = () => isDirty.value = true
const checkValidity = () => {
isValid.value = (element.value as HTMLInputElement).checkValidity()
}
const focus = () => hasFocus.value = true
const blur = () => hasFocus.value = false
return {
element,
isDirty,
isValid,
hasFocus,
message,
handleInput,
focus,
blur,
}
},
})
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment