zoeys.computer/lib/zoeyscomputer_web/live/gist_live/select.ex
2024-10-26 21:41:22 -04:00

125 lines
4.4 KiB
Elixir

defmodule ZoeyscomputerWeb.GistLive.Select do
use Phoenix.Component
import Phoenix.HTML.Form, only: [input_value: 2]
attr :id, :string, required: true
attr :form, :any, required: true
attr :field, :atom, required: true
attr :options, :list, required: true
attr :label, :string, default: nil
attr :prompt, :string, default: "Select an option..."
attr :class, :string, default: nil
attr :disabled, :boolean, default: false
def select(assigns) do
selected_value = input_value(assigns.form, assigns.field)
assigns = assign(assigns, :selected_value, selected_value)
~H"""
<div class="relative" id={@id <> "-container"} phx-hook="SelectHook">
<label :if={@label} for={@id} class="block text-sm font-bold mb-2 text-ctp-overlay1">
<%= @label %>
</label>
<button
type="button"
id={@id <> "-trigger"}
phx-update="ignore"
class={[
"relative w-full cursor-default rounded-md py-1.5 pl-3 pr-10 text-left",
"bg-ctp-base text-ctp-text border-ctp-surface0 border",
"focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ctp-ring-lavender",
"transition-colors duration-200",
"disabled:cursor-not-allowed disabled:opacity-50"
]}
disabled={@disabled}
>
<span class="block truncate" id={@id <> "-selected"}>
<%= selected_option(@options, @selected_value) || @prompt %>
</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<svg class="h-5 w-5 ctp-text-overlay0" viewBox="0 0 20 20" fill="currentColor">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
</span>
</button>
<div
id={@id <> "-dropdown"}
phx-update="ignore"
class={
[
"absolute z-10 mt-1 w-full rounded-md py-1 shadow-lg",
"bg-ctp-base border-ctp-surface0 border",
# Initially hidden, toggled by JS
"hidden"
]
}
>
<div class="px-3 py-2">
<input
type="text"
id={@id <> "-search"}
placeholder="Search..."
class={[
"w-full rounded-md py-1.5 px-3",
"bg-ctp-mantle text-ctp-text placeholder-ctp-overlay0",
"focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ring-ctp-lavender",
"transition-colors duration-200"
]}
/>
</div>
<ul class="max-h-60 overflow-auto" id={@id <> "-options"} role="listbox" tabindex="-1">
<%= for {label, value} <- @options do %>
<li
class={[
"relative cursor-default select-none py-2 pl-3 pr-9",
"text-ctp-text hover:bg-ctp-surface0",
"transition-colors duration-200"
]}
role="option"
data-value={value}
aria-selected={to_string(value) == to_string(@selected_value)}
>
<span class="block truncate"><%= label %></span>
<%= if to_string(value) == to_string(@selected_value) do %>
<span class="absolute inset-y-0 right-0 flex items-center pr-4 ctp-text-lavender">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
</span>
<% end %>
</li>
<% end %>
</ul>
</div>
<input
type="hidden"
name={input_name(@form, @field)}
id={input_id(@form, @field)}
value={@selected_value}
/>
</div>
"""
end
defp selected_option(_options, value) when is_nil(value), do: nil
defp selected_option(options, value) do
case Enum.find(options, fn {_label, val} -> to_string(val) == to_string(value) end) do
{label, _value} -> label
_ -> nil
end
end
defp input_id(%{id: id}, field) when is_atom(field), do: "#{id}_#{field}"
defp input_name(%{name: name}, field), do: "#{name}[#{field}]"
end