Crate your configurable Vue component for Input Text

Working on a project I needed an Input text field with:

  • basic animation for a better visual on focus and blur input state
  • label, hint and icon support
  • color and other configuration available

Something to use like :

<input-text
  v-model="firstname"
  placeholder="please input your firstname"
  label="Firstname"
  icon="persone"
  hint="input your firstname"
  color="#2196F3"
  />

See a live demo here

Data, props and computed properties

In order to set the component working properly we set

  • show as data property (true/false)

the following props:

  • value to manage the user input (required)
  • placeholder to set the input field placeholder
  • label to set the field label
  • hint to set a hint message to visualize when user focus on the input field
  • icon the icon name
  • color any css valid color for the label and the field effect

and the computed properties:

  • filled set the class has__content when input field has some value
  • has__icon set the class input__has__icon if the icon prop is set to a value
  • focus__border to change the background of the focus__border element (used for the animation) getting the value of the color prop
Template

<template>
    <div class="input__container">
      <div :class="'input__effect ' + filled">
        <input
    type="text"
    :value="value"
    :placeholder="placeholder" @input="$emit('input',$event.target.value)"
    :class="'effect ' + has__icon"
    @focus="show=!show"
    @blur="show=!show"
    />  
        <label :style="{color:color}">{{label}}</label>
        <span class="focus__border" :style="focus__border"></span>
      </div>
      <span class="input__hint" v-if="show">{{hint}}</span>
      <i class="material-icons input__icon">{{icon}}</i>
    </div>
</template>
Script
<script>
export default{
  data:()=>({
    show: false
  }),
  computed:{
    filled(){
      if ( !this.show && this.value ) {
        return 'has__content'
      }
      return ''
    },
    has__icon(){
      if ( this.icon ){
        return 'input__has__icon'
      }
      return
    },
    focus__border(){
      return {
        "background-color" : this.color
      }
    }
  },
  props: {
    value : { type: String , required: false, default: ''},
    label: { type: String , required: false, default: ''},
    hint: { type: String, required:false , default:'hint'},
    icon: { type: String, required: false , default: '' },
    placeholder: { type: String, required: false, default: ''},
    color: { type: String , required: false , default: 'indigo' }
  },
}
</script>
CSS Style (scoped)
<style scoped>
::placeholder {
  opacity:.4;
}

input[type="text"]{
  color: #555;
  width: 100%;
  box-sizing: border-box;
  letter-spacing: 1px;
  outline:none;
}

.input__container {
  width:100%; padding:.5rem .5rem 0 0
}

.input__effect {
  float: left;
  width: 100%;
  margin: 1.5rem 0rem 1.5rem 0;
  position: relative;
} /* necessary to give position: relative to parent. */

.input__icon {
  position: relative;
  left: 0rem;
  top: -3.5rem;
  opacity:.3;
}
.input__hint {
  float: left;
  width: 100%;
  margin:-1.2rem 0 0 0;
  position: relative;
  font-size:.8rem; opacity:.6;
}

.input__has__icon {
  padding-left: 2rem!important;
}

.effect {
  border: 0;
  padding: 4px 0;
  border-bottom: 1px solid #ccc;
  background-color: transparent;
}

.effect ~ .focus__border {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0;
  height: 2px;
  background-color: indigo;
  transition: 0.4s;
}

.effect:focus ~ .focus__border ,
.has-content.effect ~ .focus__border {
  width: 100%;
  transition: 0.4s;
}

.effect ~ label {
  position: absolute;
  left: 0;
  width: 100%;
  top: -1.3rem;
  color: #aaa;
  transition: 0.3s;
  z-index: -1;
  letter-spacing: 0.5px;
}

.effect:focus ~ label,
.has__content.effect ~ label {
  top: -1rem;
  font-size: 12px;
  color: indigo;
  transition: 0.3s;
}
</style>

MIT License