using Microsoft.AspNetCore.Components; namespace ScadaLink.CentralUI.Components.Shared; /// /// A compact multi-select control: a Bootstrap dropdown whose toggle button /// summarises the current selection ("All" when empty, the single item's label /// when one is picked, or "N selected" otherwise) over a checkbox menu. /// /// /// It exists to keep multi-value filter controls one row tall instead of a /// wrapped block of chip buttons. The component mutates the caller-owned /// collection in place and raises /// after every toggle so the parent can react /// (re-render, prune dependent selections, …). /// /// /// /// Requires the Bootstrap JS bundle (loaded in App.razor) for the /// dropdown toggle; data-bs-auto-close="outside" keeps the menu open /// while the operator ticks several boxes. /// /// /// The option value type (an enum or string). public partial class MultiSelectDropdown where TValue : notnull { /// The options shown in the menu, in display order. [Parameter, EditorRequired] public IReadOnlyList Items { get; set; } = Array.Empty(); /// /// The caller-owned selection set. Mutated in place by . /// [Parameter, EditorRequired] public ICollection Selected { get; set; } = default!; /// Maps an option to its display label. Defaults to ToString(). [Parameter] public Func Display { get; set; } = static v => v.ToString() ?? string.Empty; /// Raised after each toggle, once has been updated. [Parameter] public EventCallback SelectionChanged { get; set; } /// Summary text shown on the toggle button when nothing is selected. [Parameter] public string AllLabel { get; set; } = "All"; /// Text shown in the menu when there are no options. [Parameter] public string EmptyText { get; set; } = "None available"; /// data-test root for this control, its toggle and its options. [Parameter] public string DataTest { get; set; } = "multi-select"; private async Task Toggle(TValue item) { // ICollection.Remove returns false when the item was absent — that is the // "not currently selected" case, so add it. This is a plain toggle. if (!Selected.Remove(item)) { Selected.Add(item); } await SelectionChanged.InvokeAsync(); } private string Summary() { var count = Selected.Count; if (count == 0) { return AllLabel; } if (count == 1) { // Prefer the single selection's label over a bare "1 selected". foreach (var item in Items) { if (Selected.Contains(item)) { return Display(item); } } // The one selected value is not in the current Items list (e.g. a Kind // narrowed out by a Channel change before the parent pruned it). return "1 selected"; } return $"{count} selected"; } }