enriquebv
10/29/2017 - 12:29 PM

Simple Material Design Select (with search) using Vue. Demo: https://jsfiddle.net/o4gowmvb/2/

Simple Material Design Select (with search) using Vue. Demo: https://jsfiddle.net/o4gowmvb/2/

div.dropdown {
  display: inline-block;
  background: #FFF;
  z-index: 1;
}

div.dropdown, div.options {
  width:175px;
}

div.dropdown.active {
  z-index:10;
  visibility:visible;
}

div.dropdown div.selected {
  text-align: left;
  border-bottom: 2px solid #DDD;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  display: flex;
  flex-flow: row nowrap;
  justify-content:space-between;
  margin:0;
  cursor: pointer;
}

div.dropdown div.selected {
  padding: 10px 6px 8px 6px;
}

div.dropdown.active div.selected {
  border-bottom: 0;
  font-weight: bold;
}

div.dropdown div.selected svg {
  transition: transform ease-in-out 0.195s;
  z-index:2;
}

div.dropdown div.selected span.option, div.dropdown div.selected svg {
  margin: auto 0px auto 0px;
}

div.dropdown.active div.selected svg {
  transform: rotate(180deg);
}

div.dropdown div.options {
  box-shadow:0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
}

div.dropdown div.options {
  height: 0;
  position:absolute;
  overflow: hidden;
  z-index:1;
}

div.dropdown.active div.options {
  height:inherit!important;
}

div.dropdown div.options ul.list {
  background:#FFF;
  padding:0;
  margin:0;
  list-style:none;
  max-height: 400px;
  overflow-y: scroll;
}

div.dropdown div.options ul.list li {
  text-align: left;
  padding: 12px;
  cursor: pointer;
  background: #FFF;
  color: rgba(0, 0, 0, 0.75);
  transition: all 0.195s ease-in-out;
  cursor: pointer;
  box-sizing:border-box;
}

div.dropdown div.options ul.list li.empty {
  color:rgba(189, 195, 199,1.0);
  text-align:center;
}

div.dropdown div.options ul.list li.empty:hover {
  cursor:default;
  background:#FFF!important;
}

div.dropdown div.options input.search {
  margin:0;
  width:100%;
  border:0;
  font-family:Roboto;
  padding:12px;
  color:rgba(0, 0, 0, 0.75);
  font-size:1em;
  box-sizing:border-box;
}

div.dropdown div.options ul.list li:hover {
  background: rgba(236, 240, 241, 0.6);
}

div.dropdown[disabled] {
  cursor:default;
}

div.dropdown[disabled] div.selected{
  color:rgba(189, 195, 199,1.0);
  border-bottom:0px;
}

div.dropdown[disabled] svg{
  fill:rgba(189, 195, 199,1.0);
}
<material-dropdown
  :dropdown-selected="selected"
  :dropdown-options="options"
  :dropdown-set="changeSelected"
  :dropdown-disabled="itsDisabled"
  :dropdown-placeholder="placeholder"
  :dropdown-empty-text="emptyText">
</material-dropdown>
Vue.component('material-dropdown', {
  props: [
    'dropdownSelected',
    'dropdownOptions',
    'dropdownSet',
    'dropdownDisabled',
    'dropdownPlaceholder',
    'dropdownSearchPlaceholder',
    'dropdownEmptyText'
  ],
  data: function () {
    return {
      allOptions: this.dropdownOptions,
      options: this.dropdownOptions,
      isActive: false,
      optionsPosition: {}
    }
  },
  template: `
  <div class="dropdown" :class="{active: isActive}" :disabled="dropdownDisabled">
    <div class="selected" @click="drop">
    	<span v-if="isActive"></span>
      <span v-else>{{ dropdownSelected.name || dropdownPlaceholder || 'Select an option.' }}</span>
      <svg fill="#444444" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
        <path d="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
        <path d="M0-.75h24v24H0z" fill="none" />
      </svg>
    </div>
    <div class="options" :style="optionsPosition">
    	<input class="search" @keyup="searchOptions" :placeholder="dropdownSearchPlaceholder || 'Search options.'"/>
      <ul class="list">
        <li v-for="option in options" @click.stop="emitSet(option)"> {{ option.name }} </li>
        <li class="empty" v-if="options.length === 0">
          {{ dropdownEmptyText || 'Without options.' }}
        </li>
    	</ul>
    </div>
  </div>`,
  methods: {
    drop: function (event) {
      let select = this.$el
      let search = select.querySelector('div.options input.search')

      if (this.dropdownDisabled) return ''

      if (this.isActive) {
       	this.options = this.allOptions
        search.value = ''
      } else {
        search.focus()
      }

      this.setOptionsPosition()
      this.isActive = !this.isActive
    },
    emitSet: function (option) {
      this.dropdownSet(option)
      this.drop()
    },
    searchOptions: function (event) {
      let search = event.target.value.toLowerCase()

      if (search) {
        this.options = this.allOptions.filter(option => {
          let name = option.name.toLowerCase()
          return name.indexOf(search) !== -1
        })
      } else {
        this.options = this.allOptions
      }
    },
    setOptionsPosition: function () {
      let select = this.$el
      let options = select.querySelector('div.options')

      this.optionsPosition = {
        top: `${select.offsetTop}px`,
        left: `${select.offsetLeft}px`
      }
    }
  },
  updated: function () {
    if (this.itsActivated) this.setOptionsPosition()
  }
})