defmodule ToweropsWeb.Live.Components.GlobalSearchComponent do @moduledoc """ Global search overlay (Cmd+K / Ctrl+K) that searches across devices, sites, accounts, and alerts. """ use ToweropsWeb, :live_component alias Towerops.Search @impl true def mount(socket) do {:ok, assign(socket, query: "", results: %{}, selected_index: 0, open: false)} end @impl true def update(assigns, socket) do {:ok, assign(socket, assigns)} end @impl true def handle_event("open", _params, socket) do {:noreply, assign(socket, open: true, query: "", results: %{}, selected_index: 0)} end @impl true def handle_event("close", _params, socket) do {:noreply, assign(socket, open: false, query: "", results: %{}, selected_index: 0)} end @impl true def handle_event("search", %{"value" => query}, socket) do results = if String.length(String.trim(query)) >= 2 do Search.search(socket.assigns.organization_id, String.trim(query)) else %{} end {:noreply, assign(socket, query: query, results: results, selected_index: 0)} end @impl true def handle_event("keydown", %{"key" => "ArrowDown"}, socket) do max = flat_results_count(socket.assigns.results) - 1 idx = min(socket.assigns.selected_index + 1, max(max, 0)) {:noreply, assign(socket, selected_index: idx)} end @impl true def handle_event("keydown", %{"key" => "ArrowUp"}, socket) do idx = max(socket.assigns.selected_index - 1, 0) {:noreply, assign(socket, selected_index: idx)} end @impl true def handle_event("keydown", %{"key" => "Enter"}, socket) do flat = flat_results(socket.assigns.results) case Enum.at(flat, socket.assigns.selected_index) do nil -> {:noreply, socket} result -> {:noreply, push_navigate(socket, to: result.url)} end end @impl true def handle_event("keydown", _params, socket) do {:noreply, socket} end @impl true def render(assigns) do flat = flat_results(assigns.results) assigns = assign(assigns, flat_results: flat) ~H"""
No results found.
<% end %> <%= for {category, items} <- @results do %>{category_label(category)}
<%= for {item, idx} <- Enum.with_index(items) do %> <% global_idx = global_index(@results, category, idx) %> <.link navigate={item.url} class={[ "flex items-center gap-3 rounded-lg px-3 py-2 text-sm cursor-pointer", if(global_idx == @selected_index, do: "bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300", else: "text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5" ) ]} > <.icon name={category_icon(category)} class="h-4 w-4 flex-shrink-0 opacity-60" />{item.label}
{item.sublabel}
Start typing to search...
<% end %>