125 lines
4.4 KiB
Elixir
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
|