View Source Cheatsheet
Style LiveSelect
like a Phoenix Core Component, with label and errors
1. Add this to core_components.ex
:
def live_select(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
assigns =
assigns
|> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
|> assign(:live_select_opts, assigns_to_attributes(assigns, [:errors, :label]))
~H"""
<div phx-feedback-for={@field.name}>
<.label for={@field.id}><%= @label %></.label>
<LiveSelect.live_select
field={@field}
text_input_class={[
"mt-2 block w-full rounded-lg border-zinc-300 py-[7px] px-[11px]",
"text-zinc-900 focus:outline-none focus:ring-4 sm:text-sm sm:leading-6",
"phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5",
"border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5",
@errors != [] && "border-rose-400 focus:border-rose-400 focus:ring-rose-400/10"
]}
{@live_select_opts}
/>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
end
2. Then call it this way:
<.live_select field={@form[:city]} label="City" phx-target={@myself} />
You can also pass any of the other LiveSelect
options.
Implementing a simple search functionality
1. With no options displayed if the user doesn't enter any text
Heex template:
<.live_select field={@form[:locations]} update_min_len={1} phx-focus="clear" />
Live view:
@impl true
def handle_event("live_select_change", %{"id" => id, "text" => text}, socket) do
options =
retrieve_locations()
|> Enum.filter(&(String.downcase(&1) |> String.contains?(String.downcase(text))))
send_update(LiveSelect.Component, options: options, id: id)
{:noreply, socket}
end
@impl true
def handle_event("clear", %{"id" => id}, socket) do
send_update(LiveSelect.Component, options: [], id: id)
{:noreply, socket}
end
2. With a fixed set of default options to be displayed when the text input is empty
Heex template:
<.live_select field={@form[:locations]} update_min_len={0} phx-focus="set-default" options={@default_locations} />
Live view:
@impl true
def mount(socket) do
socket = assign(socket, default_locations: default_locations())
{:ok, socket}
end
@impl true
def handle_event("live_select_change", %{"id" => id, "text" => text}, socket) do
options =
if text == "" do
socket.assigns.default_locations
else
retrieve_locations()
|> Enum.filter(&(String.downcase(&1) |> String.contains?(String.downcase(text))))
end
send_update(LiveSelect.Component, options: options, id: id)
{:noreply, socket}
end
@impl true
def handle_event("set-default", %{"id" => id}, socket) do
send_update(LiveSelect.Component, options: socket.assigns.default_locations, id: id)
{:noreply, socket}
end
Dropdown that opens above the input field
Tailwind
Heex template
<.live_select
field={@form[:my_field]}
dropdown_extra_class="!top-full bottom-full" />
DaisyUI
Heex template
<.live_select
field={@form[:my_field]}
style={:daisyui}
container_extra_class="dropdown-top" />
Display tags underneath the input field (Tailwind)
Heex template:
<.live_select
field={@form[:my_field]}
mode={:tags}
container_extra_class="flex flex-col"
dropdown_extra_class="top-11"
tags_container_extra_class="order-last" />
Limit the height of the dropdown and make it scrollable (Tailwind)
Heex template:
<.live_select
field={@form[:my_field]}
dropdown_extra_class="max-h-60 overflow-y-scroll" />