index.vue 8.62 KB
<template>
  <div class="material-input__component" :class="computedClasses">
    <input v-if="type === 'email'" type="email" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy"
      :readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)"
      @blur="handleFocus(false)" @input="handleModelInput">
    <input v-if="type === 'url'" type="url" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy"
      :readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)"
      @blur="handleFocus(false)" @input="handleModelInput">
    <input v-if="type === 'number'" type="number" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy"
      :readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :max="max" :min="min" :minlength="minlength" :maxlength="maxlength"
      :required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput">
    <input v-if="type === 'password'" type="password" class="material-input" :name="name" :id="id" :placeholder="placeholder"
      v-model="valueCopy" :readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :max="max" :min="min" :required="required"
      @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput">
    <input v-if="type === 'tel'" type="tel" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy"
      :readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)"
      @blur="handleFocus(false)" @input="handleModelInput">
    <input v-if="type === 'text'" type="text" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy"
      :readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :minlength="minlength" :maxlength="maxlength"
      :required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput">

    <span class="material-input-bar"></span>

    <label class="material-label">
            <slot></slot>
        </label>
    <div v-if="errorMessages" class="material-errors">
      <div v-for="error in computedErrors" class="material-error" :key='error'>
        {{ error }}
      </div>
    </div>
  </div>
</template>

<script>
	// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
export default {
  name: 'material-input',
  computed: {
    computedErrors() {
      return typeof this.errorMessages === 'string'
                        ? [this.errorMessages] : this.errorMessages
    },
    computedClasses() {
      return {
        'material--active': this.focus,
        'material--disabled': this.disabled,
        'material--has-errors': Boolean(!this.valid || (this.errorMessages && this.errorMessages.length)),
        'material--raised': Boolean(this.focus || this.valueCopy || // has value
                            (this.placeholder && !this.valueCopy)) // has placeholder
      }
    }
  },
  data() {
    return {
      valueCopy: null,
      focus: false,
      valid: true
    }
  },
  beforeMount() {
        // Here we are following the Vue2 convention on custom v-model:
        // https://github.com/vuejs/vue/issues/2873#issuecomment-223759341
    this.copyValue(this.value)
  },
  methods: {
    handleModelInput(event) {
      this.$emit('input', event.target.value, event)
      this.handleValidation()
    },
    handleFocus(focused) {
      this.focus = focused
    },
    handleValidation() {
      this.valid = this.$el ? this.$el.querySelector('.material-input').validity.valid : this.valid
    },
    copyValue(value) {
      this.valueCopy = value
      this.handleValidation()
    }
  },
  watch: {
    value(newValue) {
      this.copyValue(newValue)
    }
  },
  props: {
    id: {
      type: String,
      default: null
    },
    name: {
      type: String,
      default: null
    },
    type: {
      type: String,
      default: 'text'
    },
    value: {
      default: null
    },
    placeholder: {
      type: String,
      default: null
    },
    readonly: {
      type: Boolean,
      default: false
    },
    disabled: {
      type: Boolean,
      default: false
    },
    min: {
      type: String,
      default: null
    },
    max: {
      type: String,
      default: null
    },
    minlength: {
      type: Number,
      default: null
    },
    maxlength: {
      type: Number,
      default: null
    },
    required: {
      type: Boolean,
      default: true
    },
    autocomplete: {
      type: String,
      default: 'off'
    },
    errorMessages: {
      type: [Array, String],
      default: null
    }
  }
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
    // Fonts:
    $font-size-base: 16px;
    $font-size-small: 18px;
    $font-size-smallest: 12px;
    $font-weight-normal: normal;
    // Utils
    $spacer: 12px;
    $transition: 0.2s ease all;
    // Base clases:
    %base-bar-pseudo {
        content: '';
        height: 1px;
        width: 0;
        bottom: 0;
        position: absolute;
        transition: $transition;
    }

    // Mixins:
    @mixin slided-top() {
        top: -2 * $spacer;
        font-size: $font-size-small;
    }

    // Component:
    .material-input__component {
        /*margin-top: 30px;*/
        position: relative;
        * {
            box-sizing: border-box;
        }
        .material-input {
            font-size: $font-size-base;
            padding: $spacer $spacer $spacer $spacer / 2;
            display: block;
            width: 100%;
            border: none;
            border-radius: 0;
            &:focus {
                outline: none;
                border: none;
                border-bottom: 1px solid transparent; // fixes the height issue
            }
        }
        .material-label {
            font-size: $font-size-base;
            font-weight: $font-weight-normal;
            position: absolute;
            pointer-events: none;
            left: 0;
            top: $spacer;
            transition: $transition;
        }
        .material-input-bar {
            position: relative;
            display: block;
            width: 100%;
            &:before {
                @extend %base-bar-pseudo;
                left: 50%;
            }
            &:after {
                @extend %base-bar-pseudo;
                right: 50%;
            }
        }
        // Disabled state:
        &.material--disabled {
            .material-input {
                border-bottom-style: dashed;
            }
        }
        // Raised state:
        &.material--raised {
            .material-label {
                @include slided-top();
            }
        }
        // Active state:
        &.material--active {
            .material-input-bar {
                &:before,
                &:after {
                    width: 50%;
                }
            }
        }
        // Errors:
        .material-errors {
            position: relative;
            overflow: hidden;
            .material-error {
                font-size: $font-size-smallest;
                line-height: $font-size-smallest + 2px;
                overflow: hidden;
                margin-top: 0;
                padding-top: $spacer / 2;
                padding-right: $spacer / 2;
                padding-left: 0;
            }
        }
    }

    // Theme:
    $color-white: white;
    $color-grey: #9E9E9E;
    $color-grey-light: #E0E0E0;
    $color-blue: #2196F3;
    $color-red: #F44336;
    $color-black: black;
    .material-input__component {
        background: $color-white;
        .material-input {
            background: none;
            color: $color-black;
            text-indent: 30px;
            border-bottom: 1px solid $color-grey-light;
        }
        .material-label {
            color: $color-grey;
        }
        .material-input-bar {
            &:before,
            &:after {
                background: $color-blue;
            }
        }
        // Active state:
        &.material--active {
            .material-label {
                color: $color-blue;
            }
        }
        // Errors:
        &.material--has-errors {
            &.material--active .material-label {
                color: $color-red;
            }
            .material-input-bar {
                &:before,
                &:after {
                    background: $color-red;
                }
            }
            .material-errors {
                color: $color-red;
            }
        }
    }
</style>