Building a Custom Select Input with Tailwind and Vue

Jonathon Ringeisen - Nov 4 '20 - - Dev Community

Have you ever used Element UI? I am currently using this in a production application and realized that it's not very mobile-friendly, like at all! I'm using it for a couple of things like a select input with search functionality and a date/time picker. Both fail miserably on mobile devices and I found this out because my users started reporting it to me.

So I decided that I would build my own custom Vue components this way I can ensure that they're mobile friendly and I'll have more flexibility when it comes to customizing the component.

I decided to start with the AutoComplete component which I think is actually considered a select component.

The component looks like this:

<auto-complete
  :data="data"
  v-model.trim="formData.client"
  @chosen="handleChosen"
  placeholder="Search for state..."
></auto-complete>
Enter fullscreen mode Exit fullscreen mode

My goal was to keep it simple but make it customizable so if anyone else wanted to use it they can customize it to their liking. The props include: placeholder, data, inputClass, dropdownClass.

I think I'm going to add some more to make it more customizable.

Alt Text

Alright, let's get to the good part, the code!

<template>
  <div class="relative" v-click-outside="clickedOutside">
    <input
      :value="value"
      @input="handleInput"
      :placeholder="placeholder"
      ref="input"
      tabindex="0"
      :class="inputClass"
    />
    <span
      v-if="value"
      @click.prevent="reset()"
      class="absolute inset-y-0 right-0 pr-3 flex items-center cursor-pointer"
    >
      x
    </span>
    <div
      v-show="value && showOptions"
      @click.self="handleSelf()"
      @focusout="showOptions = false"
      tabindex="0"
      :class="dropdownClass"
    >
      <ul class="py-1">
        <li
          v-for="(item, index) in searchResults"
          :key="index"
          @click="handleClick(item)"
          class="px-3 py-2 cursor-pointer hover:bg-gray-200"
        >
          {{ item.name }}
        </li>
        <li v-if="!searchResults.length" class="px-3 py-2 text-center">
          No Matching Results
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      required: false,
    },
    placeholder: {
      type: String,
      required: false,
      default: "Enter text here.",
    },
    data: {
      type: Array,
      required: true,
    },
    inputClass: {
      type: String,
      required: false,
      default:
        "border border-gray-300 py-2 px-3 rounded-md focus:outline-none focus:shadow-outline",
    },
    dropdownClass: {
      type: String,
      required: false,
      default:
        "absolute w-full z-50 bg-white border border-gray-300 mt-1 mh-48 overflow-hidden overflow-y-scroll rounded-md shadow-md",
    },
  },

  data() {
    return {
      showOptions: false,
      chosenOption: "",
      searchTerm: "",
    };
  },

  computed: {
    searchResults() {
      return this.data.filter((item) => {
        return item.name.toLowerCase().includes(this.searchTerm.toLowerCase());
      });
    },
  },

  methods: {
    reset() {
      this.$emit("input", "");
      this.chosenOption = "";
    },

    handleInput(evt) {
      this.$emit("input", evt.target.value);
      this.searchTerm = evt.target.value;
      this.showOptions = true;
    },

    handleClick(item) {
      this.$emit("input", item.name);
      this.$emit("chosen", item);
      this.chosenOption = item.name;
      this.showOptions = false;
      this.$refs.input.focus();
    },

    clickedOutside() {
      this.showOptions = false;

      if (!this.chosenOption) {
        this.$emit("input", "");
      }
    },
  },
};
</script>

<style scoped>
.mh-48 {
  max-height: 10rem;
}
</style>
Enter fullscreen mode Exit fullscreen mode

If you have any improvement suggestions please comment below. I'd appreciate your feedback!

. . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player