DEV Community

loading...

Building a Custom Select Input with Tailwind and Vue

jringeisen profile image Jonathon Ringeisen ・2 min read

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!

Discussion

pic
Editor guide
Collapse
justageek profile image
Brian Smith

Thanks for the reply, how hard do you think it would be to refactor your tool so that it used an ajax request to return results based on the text entered, so say after a user types 3 letters you trigger the search via ajax and use the response to populate the v-for?

Collapse
justageek profile image
Brian Smith

Do you have a demo of this working so we can see it somewhere?

Collapse
jringeisen profile image