@* Global resource search — header-level palette. Keyboard: ↑/↓ move the highlight, Enter navigates, Esc clears. Blur: Clicking anywhere outside the input closes the dropdown. *@ @using Microsoft.AspNetCore.Components.Web @using RackPeek.Domain.Persistence @using RackPeek.Domain.Resources @using Shared.Rcl.Services @inject IResourceCollection Repo @inject NavigationManager Nav
@if (!string.IsNullOrWhiteSpace(_query)) {
@if (_results.Count == 0) {
No matches.
} else { @for (var i = 0; i < _results.Count; i++) { var r = _results[i]; var highlighted = i == _highlightedIndex; var rowClass = highlighted ? "bg-zinc-800 border-l-2 border-emerald-400" : "hover:bg-zinc-800 border-l-2 border-transparent"; } }
}
@code { // Brief delay on focus-out so a click on a result button can fire first. // Browser event order is mousedown → blur → click, so the focus-out fires // before the click handler on the button. The delay lets the click register // before we close the dropdown. private const int _blurCloseDelayMs = 150; private string _query = string.Empty; private IReadOnlyList _results = []; private int _highlightedIndex; private async Task RunSearchAsync() { // Reload every search so newly added / edited / deleted resources show up // without a hard page refresh. The repo layer is already in-memory, so // this is a cheap dictionary lookup, not a disk read. var resources = await Repo.GetAllOfTypeAsync(); _results = GlobalSearchService.Search(resources, _query); _highlightedIndex = 0; } private void OnKeyDown(KeyboardEventArgs e) { switch (e.Key) { case "ArrowDown": if (_results.Count > 0) _highlightedIndex = Math.Min(_highlightedIndex + 1, _results.Count - 1); break; case "ArrowUp": if (_results.Count > 0) _highlightedIndex = Math.Max(_highlightedIndex - 1, 0); break; case "Enter": if (_highlightedIndex >= 0 && _highlightedIndex < _results.Count) OnResultSelected(_results[_highlightedIndex]); break; case "Escape": Close(); break; } } private async Task OnFocusOut() { await Task.Delay(_blurCloseDelayMs); if (!string.IsNullOrEmpty(_query)) { Close(); StateHasChanged(); } } private void OnResultSelected(SearchResult r) { Close(); Nav.NavigateTo(r.Url); } private void Close() { _query = string.Empty; _results = []; _highlightedIndex = 0; } private static string Sanitize(string value) => value.Replace(" ", "-").ToLowerInvariant(); }