Here is the working fiddle: https://blazorfiddle.com/s/d04xl2zi
I started a new project where they are using .net 8 blazor server interactivity. (I'm a bit new to blazor) they were interested in a select (dropdown) but one that allows type ahead (searching). I created a reusable component.
I created a reusable component called SelectWithAutoComplete. It accepts a list and accepts a callback function for onselected. I was hoping for a code review to tell me if I'm missing any functionality or if something could be improved.
@typeparam TItem
<div class="dropdown position-relative" @onfocusout="HandleFocusOut" style="max-width: 100%;">
<input type="text"
@bind="searchText"
@bind:event="oninput"
@onfocus="()=> dropdownOpen = true"
placeholder="@Placeholder"
class="form-control"
style="cursor: pointer; padding-right: 2.5rem; max-width: 100%;" />
<!-- Clear button -->
@if (!string.IsNullOrWhiteSpace(searchText) || SelectedItem != null)
{
<span style="
position: absolute;
top: 50%;
right: 2rem;
transform: translateY(-50%);
cursor: pointer;
color: gray;
"
@onclick="ClearSelection">
✖
</span>
}
<!-- Dropdown arrow -->
<span style="
position: absolute;
top: 50%;
right: 0.5rem;
transform: translateY(-50%);
cursor: pointer;
color: gray;
"
@onclick="ToggleDropdown">
▼
</span>
@if (dropdownOpen && GetFilteredItems().Any())
{
<ul class="dropdown-menu show w-100 mt-0" style="max-height: 200px; overflow-y: auto;">
@foreach (var item in GetFilteredItems())
{
<li>
<button type="button" class="dropdown-item" @onmousedown="() => SelectItem(item)">
@ItemText(item)
</button>
</li>
}
</ul>
}
</div>
@code {
[Parameter] public List<TItem> Items { get; set; } = new();
[Parameter] public Func<TItem, string> ItemText { get; set; }
[Parameter] public string Placeholder { get; set; } = "Select or type...";
[Parameter] public EventCallback<TItem> SelectedItemChanged { get; set; }
[Parameter] public TItem SelectedItem { get; set; }
private string searchText = string.Empty;
private bool dropdownOpen = false;
private List<TItem> GetFilteredItems() =>
string.IsNullOrWhiteSpace(searchText)
? Items
: Items.Where(x => ItemText(x).Contains(searchText, StringComparison.InvariantCultureIgnoreCase))
.ToList();
private async Task SelectItem(TItem item)
{
SelectedItem = item;
searchText = ItemText(item);
dropdownOpen = false;
await SelectedItemChanged.InvokeAsync(item);
}
private async void HandleFocusOut(FocusEventArgs e)
{
dropdownOpen = false;
var match = Items.FirstOrDefault(x => string.Equals(ItemText(x), searchText, StringComparison.InvariantCultureIgnoreCase));
if (match != null)
{
SelectedItem = match;
searchText = ItemText(match);
await SelectedItemChanged.InvokeAsync(match);
}
else
{
SelectedItem = default;
searchText = string.Empty;
await SelectedItemChanged.InvokeAsync(default);
}
}
private async void ClearSelection()
{
SelectedItem = default;
searchText = string.Empty;
dropdownOpen = false;
await SelectedItemChanged.InvokeAsync(default);
}
private void ToggleDropdown()
{
if (!dropdownOpen)
{
searchText = string.Empty;
}
dropdownOpen = !dropdownOpen;
}
}