This commit is contained in:
Alex Lion
2025-08-06 21:44:54 +02:00
parent eab2df01d0
commit 25c8d79d57
4 changed files with 190 additions and 6 deletions

View File

@@ -268,6 +268,19 @@ Hooks.EmptyNickname = {
},
};
Hooks.SearchableSelect = {
mounted() {
this.handleEvent("update_hidden_field", (payload) => {
if (payload.id === this.el.id) {
this.el.value = payload.value;
// Trigger a change event to update the form
const event = new Event('input', { bubbles: true });
this.el.dispatchEvent(event);
}
});
}
};
Hooks.PostForm = {
onPress(e, submitBtn, TA) {
if (e.key == "Enter" && !e.shiftKey) {

View File

@@ -61,17 +61,16 @@ defmodule ClaperWeb.AdminLive.EventLive.FormComponent do
/>
<.live_component
module={ClaperWeb.AdminLive.FormFieldComponent}
module={ClaperWeb.AdminLive.SearchableSelectComponent}
id="event-user-id"
form={@form}
field={:user_id}
type="select"
label="Assigned User"
select_options={@user_options}
prompt="Select a user"
options={@user_options}
placeholder="Search for a user..."
required={true}
width_class="sm:col-span-6"
description="The user who owns this event"
description="The user who owns this event (required)"
/>
</div>

View File

@@ -80,7 +80,9 @@ defmodule ClaperWeb.AdminLive.FormFieldComponent do
@field,
[
class: "checkbox checkbox-primary",
checked: Phoenix.HTML.Form.input_value(@form, @field) == true || Phoenix.HTML.Form.input_value(@form, @field) == "true"
checked:
Phoenix.HTML.Form.input_value(@form, @field) == true ||
Phoenix.HTML.Form.input_value(@form, @field) == "true"
] ++ @extra_attrs
)}
<span class="label-text ml-2">{@checkbox_label || @label}</span>

View File

@@ -0,0 +1,170 @@
defmodule ClaperWeb.AdminLive.SearchableSelectComponent do
use ClaperWeb, :live_component
@impl true
def render(assigns) do
~H"""
<div class={if @width_class, do: @width_class, else: "sm:col-span-6"}>
<div class="form-control w-full">
<label class="label">
<span class="label-text">{@label}</span>
</label>
<div class="dropdown dropdown-bottom w-full">
<input
type="text"
value={@search_term}
placeholder={@placeholder}
class="input input-bordered w-full"
phx-keyup="search"
phx-focus="open_dropdown"
phx-target={@myself}
phx-debounce="300"
autocomplete="off"
tabindex="0"
/>
<input
type="hidden"
name={"#{@form.name}[#{@field}]"}
value={@selected_value || ""}
id={"#{@form.name}_#{@field}"}
/>
<%= if @show_dropdown and length(@filtered_options) > 0 do %>
<ul
tabindex="0"
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-full p-2 shadow"
phx-click-away="close_dropdown"
phx-target={@myself}
>
<%= for {label, value} <- @filtered_options do %>
<li class="w-full">
<a
class={if to_string(value) == to_string(@selected_value), do: "active", else: ""}
phx-click="select_option"
phx-value-value={value}
phx-value-label={label}
phx-target={@myself}
>
{label}
</a>
</li>
<% end %>
</ul>
<% end %>
</div>
<label class="label">
{error_tag(@form, @field)}
<%= if @description do %>
<span class="label-text-alt">{@description}</span>
<% end %>
</label>
</div>
</div>
"""
end
@impl true
def mount(socket) do
{:ok,
socket
|> assign(:show_dropdown, false)
|> assign(:search_term, "")
|> assign(:filtered_options, [])
|> assign(:selected_value, nil)
|> assign(:display_value, "")}
end
@impl true
def update(assigns, socket) do
socket =
socket
|> assign(assigns)
|> assign_new(:placeholder, fn -> "Select..." end)
|> assign_new(:required, fn -> false end)
|> assign_new(:description, fn -> nil end)
|> assign_new(:width_class, fn -> nil end)
|> assign_new(:options, fn -> [] end)
|> update_filtered_options()
|> update_display_value()
{:ok, socket}
end
@impl true
def handle_event("search", %{"value" => search_term}, socket) do
socket =
socket
|> assign(:search_term, search_term)
|> assign(:show_dropdown, true)
|> update_filtered_options()
{:noreply, socket}
end
def handle_event("select_option", %{"value" => value, "label" => label}, socket) do
socket =
socket
|> assign(:selected_value, value)
|> assign(:display_value, label)
|> assign(:show_dropdown, false)
|> assign(:search_term, label)
{:noreply, socket}
end
def handle_event("open_dropdown", _params, socket) do
socket =
socket
|> assign(:show_dropdown, true)
|> update_filtered_options()
{:noreply, socket}
end
def handle_event("close_dropdown", _params, socket) do
{:noreply, assign(socket, :show_dropdown, false)}
end
defp update_filtered_options(socket) do
search_term = String.downcase(socket.assigns[:search_term] || "")
filtered =
if search_term == "" do
socket.assigns.options
else
Enum.filter(socket.assigns.options, fn {label, _value} ->
String.contains?(String.downcase(label), search_term)
end)
end
assign(socket, :filtered_options, filtered)
end
defp update_display_value(socket) do
current_value = get_field_value(socket.assigns.form, socket.assigns.field)
display_value =
if current_value do
case Enum.find(socket.assigns.options, fn {_label, value} ->
to_string(value) == to_string(current_value)
end) do
{label, _value} -> label
nil -> ""
end
else
""
end
socket
|> assign(:selected_value, current_value)
|> assign(:display_value, display_value)
|> assign(:search_term, display_value)
end
defp get_field_value(form, field) do
Map.get(form.data, field) || Map.get(form.params, to_string(field))
end
end