How to create a reusable autocomplete search component?

Hi all - am pretty new to this whole ecosystem. Was tasked with trying to build an autocomplete search dropdown similar to this
Autocomplete component — Vuetify - it should be resusable across different parts of the site - it will basically entail two parts: a regular input for typing into, and a panel underneath it for rendering the options and selection behavior

Here’s my code. Having a tough time figuring out where to start but wanted to just do it with dummy data and not any server side datalists yet. Thanks in advance for any guidance if anyone can offer. Is this all feasible without JS?

is where is starts.

defmodule LevelWeb.UI.AutocompleteDropdown do
  @moduledoc false
  use LevelWeb, :live_component

  @variants %{
    default: %{
      select_container: "px-[1rem] py-[0.5rem] border border-[#D0D5DD] rounded-full bg-white",
      placeholder_text: "text-base font-normal",
      icon: "fa-caret-down text-base"
    },
    primary: %{
      select_container: "pl-[1.5rem] py-[0.625rem] pr-[1.25rem] border border-[#D0D5DD] rounded-lg bg-white",
      placeholder_text: "text-sm font-medium",
      icon: "fa-chevron-down text-[0.625rem]"
    },
    primary_rounded: %{
      select_container: "pl-[1.5rem] py-[0.625rem] pr-[1.25rem] border border-[#D0D5DD] rounded-full bg-white",
      placeholder_text: "text-sm font-medium",
      icon: "fa-chevron-down text-[0.625rem]"
    }
  }

  @dynamic_styles %{
    select_container: "border-blue"
  }

  @impl Phoenix.LiveComponent
  def mount(socket) do
    socket =
      socket
      |> assign_new(:menu_open?, fn -> false end)
      |> assign_new(:placeholder_text, fn -> "Choose Major(s)" end)
      |> assign_new(:variant, fn -> :default end)
      |> assign_new(:chip_variant, fn -> :primary end)

    {:ok, socket}
  end

  @impl Phoenix.LiveComponent
  def render(assigns) do
    assigns =
      assigns
      |> assign_new(:target, fn -> assigns.id end)
      |> assign_new(:key, fn -> nil end)
      |> assign_new(:render_chips?, fn -> true end)

    ~H"""
    <div class="autocomplete-parent"> 
      <div class="relative">
        <input id={"#{@id}-search-input"} class="relative w-full appearance-none border border-gray-detailBorder rounded-xl bg-transparent py-0.5  pr-1 text-sm placeholder:text-[#666] focus:outline-none lg:w-[36rem]" type="text" list="matches" placeholder="Search.." />
        <datalist id="matches">
          <option>test1</option>
          <option>test2</option>
          <option>test3</option>
        </datalist>

      </div>
    </div>
    """
  end

  @impl Phoenix.LiveComponent
  def handle_event("toggle_menu", _, socket) do
    socket = assign(socket, :menu_open?, !socket.assigns.menu_open?)

    {:noreply, socket}
  end

  @impl Phoenix.LiveComponent
  def handle_event("close_menu", _, socket) do
    socket = assign(socket, :menu_open?, false)

    {:noreply, socket}
  end

  @spec is_selected?(map(), list(map())) :: boolean()
  defp is_selected?(item, selected_items) do
    Enum.find(selected_items, fn i -> i.value == item.value end) != nil
  end
end

You could check out: LiveSelect v1.2.2 — Documentation

2 Likes

Have a read of and adapt Typeahead with LiveView and Tailwind - Tutorials and screencasts for Elixir, Phoenix and LiveView

1 Like

Thanks a lot!

appreciate it, thanks!