From 0c99e76ffd20f6d5305396e0201a28ad6062beec Mon Sep 17 00:00:00 2001 From: Manuel Monteagudo <manuel@homelifeacademy.com> Date: Mon, 25 Nov 2024 20:28:41 -0400 Subject: [PATCH] [HxMultiSelect] Adds Floating Label type to MultiSelect component (#899) * Adds Floating label type to MultiSelect * Adds floating label type to MultiSelect * reverted basic demo + separate testing page * CSS class moved to outermost element, CssClassHelper usage * RenderOrder override consolidation * HxFormValueComponentRenderer_Label usage (instead of direct <label> rendering) * doc - add HxMultiSelect to list of components supporting floating labels --------- Co-authored-by: Robert Haken <haken@havit.cz> --- .../Forms/HxMultiSelect.cs | 24 +++++++++- .../Internal/HxMultiSelectInternal.razor | 33 ++++++++------ .../Internal/HxMultiSelectInternal.razor.cs | 4 ++ .../Forms/MultiSelectSettings.cs | 5 +++ .../FormInputs/FormInputs_Documentation.razor | 4 +- .../Havit.Blazor.Components.Web.Bootstrap.xml | 8 ++++ .../HxMultiSelect_FloatingLabel_Test.razor | 45 +++++++++++++++++++ 7 files changed, 108 insertions(+), 15 deletions(-) create mode 100644 Havit.Blazor.TestApp/Havit.Blazor.TestApp.Client/HxMultiSelectTests/HxMultiSelect_FloatingLabel_Test.razor diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxMultiSelect.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxMultiSelect.cs index 1271cf2f6..d5db76184 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxMultiSelect.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxMultiSelect.cs @@ -8,7 +8,7 @@ namespace Havit.Blazor.Components.Web.Bootstrap; /// </summary> /// <typeparam name="TValue">Type of values.</typeparam> /// <typeparam name="TItem">Type of items.</typeparam> -public class HxMultiSelect<TValue, TItem> : HxInputBase<List<TValue>>, IInputWithSize +public class HxMultiSelect<TValue, TItem> : HxInputBase<List<TValue>>, IInputWithSize, IInputWithLabelType { /// <summary> /// Return <see cref="HxMultiSelect{TValue, TItem}"/> defaults. @@ -161,6 +161,26 @@ public class HxMultiSelect<TValue, TItem> : HxInputBase<List<TValue>>, IInputWit [Parameter] public IconBase FilterClearIcon { get; set; } protected IconBase FilterClearIconEffective => FilterClearIcon ?? GetSettings()?.FilterClearIcon ?? GetDefaults().FilterClearIcon; + /// <inheritdoc cref="Bootstrap.LabelType" /> + [Parameter] public LabelType? LabelType { get; set; } + protected LabelType LabelTypeEffective => LabelType ?? GetSettings()?.LabelType ?? GetDefaults()?.LabelType ?? HxSetup.Defaults.LabelType; + LabelType IInputWithLabelType.LabelTypeEffective => LabelTypeEffective; + protected override LabelValueRenderOrder RenderOrder + { + get + { + if (LabelTypeEffective == Bootstrap.LabelType.Floating) + { + // Floating label type renders the label in HxMultiSelectInternal component. + return LabelValueRenderOrder.ValueOnly; + } + else + { + return LabelValueRenderOrder.LabelValue; + } + } + } + private List<TItem> _itemsToRender; private HxMultiSelectInternal<TValue, TItem> _hxMultiSelectInternalComponent; @@ -212,6 +232,8 @@ protected override void BuildRenderInput(RenderTreeBuilder builder) builder.AddAttribute(102, nameof(HxMultiSelectInternal<TValue, TItem>.InputCssClass), GetInputCssClassToRender()); builder.AddAttribute(103, nameof(HxMultiSelectInternal<TValue, TItem>.InputText), GetInputText()); builder.AddAttribute(104, nameof(HxMultiSelectInternal<TValue, TItem>.EnabledEffective), EnabledEffective); + builder.AddAttribute(125, nameof(HxMultiSelectInternal<TValue, TItem>.LabelTypeEffective), LabelTypeEffective); + builder.AddAttribute(126, nameof(HxMultiSelectInternal<TValue, TItem>.FormValueComponent), this); builder.AddAttribute(105, nameof(HxMultiSelectInternal<TValue, TItem>.ItemsToRender), _itemsToRender); builder.AddAttribute(106, nameof(HxMultiSelectInternal<TValue, TItem>.TextSelector), TextSelector); builder.AddAttribute(107, nameof(HxMultiSelectInternal<TValue, TItem>.ValueSelector), ValueSelector); diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/HxMultiSelectInternal.razor b/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/HxMultiSelectInternal.razor index ce92eccce..056c2f1ed 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/HxMultiSelectInternal.razor +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/HxMultiSelectInternal.razor @@ -6,7 +6,7 @@ bool enabled = EnabledEffective && (ItemsToRender != null); } <div class="@CssClassHelper.Combine("hx-multi-select", - HasInputGroups ? "input-group" : null, + HasInputGroups ? "input-group" : null, HasInputGroupEnd ? "input-group-end" : null, HasInputGroupStart ? "input-group-start" : null, InputGroupCssClass)"> @@ -16,19 +16,26 @@ } @InputGroupStartTemplate - <div @ref="_elementReference" class="dropdown" role="listbox"> + <div @ref="_elementReference" + class="@CssClassHelper.Combine("dropdown", (LabelTypeEffective == LabelType.Floating) ? "form-floating": null)" + role="listbox"> <input @ref="_inputElementReference" - type="text" - id="@InputId" - class="@InputCssClass" - value="@(((ItemsToRender == null) && !String.IsNullOrEmpty(NullDataText)) ? NullDataText : InputText)" - disabled="@(!enabled)" - readonly="true" - aria-expanded="@_isShown" - data-bs-toggle="@(enabled ? "dropdown" : null)" - data-bs-auto-close="outside" - @attributes="this.AdditionalAttributes" /> + type="text" + id="@InputId" + class="@InputCssClass" + value="@(((ItemsToRender == null) && !String.IsNullOrEmpty(NullDataText)) ? NullDataText : InputText)" + disabled="@(!enabled)" + readonly="true" + aria-expanded="@_isShown" + data-bs-toggle="@(enabled ? "dropdown" : null)" + data-bs-auto-close="outside" + @attributes="this.AdditionalAttributes" /> + + @if (LabelTypeEffective == LabelType.Floating) + { + <HxFormValueComponentRenderer_Label FormValueComponent="@FormValueComponent" /> + } @* Must be always rendered otherwise does not work after disable->enabled scenario *@ <ul class="@CssClassHelper.Combine("dropdown-menu", _isShown ? "show" : null)"> @@ -45,7 +52,7 @@ autocomplete="off" value="@_filterText" @oninput="HandleFilterInputChanged" - @onclick:stopPropagation + @onclick:stopPropagation onfocusin="this.select()" /> <div class="hx-multi-select-filter-input-icon"> diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/HxMultiSelectInternal.razor.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/HxMultiSelectInternal.razor.cs index b8fce3638..0ecf31d96 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/HxMultiSelectInternal.razor.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/HxMultiSelectInternal.razor.cs @@ -13,8 +13,12 @@ public partial class HxMultiSelectInternal<TValue, TItem> : IAsyncDisposable [Parameter] public string InputText { get; set; } + [Parameter] public IFormValueComponent FormValueComponent { get; set; } + [Parameter] public bool EnabledEffective { get; set; } + [Parameter] public LabelType LabelTypeEffective { get; set; } + [Parameter] public List<TItem> ItemsToRender { get; set; } [Parameter] public List<TValue> SelectedValues { get; set; } diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/MultiSelectSettings.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/MultiSelectSettings.cs index 27a621314..4bdca09f7 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/MultiSelectSettings.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/MultiSelectSettings.cs @@ -12,6 +12,11 @@ public record MultiSelectSettings : InputSettings /// </summary> public InputSize? InputSize { get; set; } + /// <summary> + /// The label type. + /// </summary> + public LabelType? LabelType { get; set; } + /// <summary> /// Enables filtering capabilities. /// </summary> diff --git a/Havit.Blazor.Documentation/Pages/Components/FormInputs/FormInputs_Documentation.razor b/Havit.Blazor.Documentation/Pages/Components/FormInputs/FormInputs_Documentation.razor index c4e80608a..da14d6141 100644 --- a/Havit.Blazor.Documentation/Pages/Components/FormInputs/FormInputs_Documentation.razor +++ b/Havit.Blazor.Documentation/Pages/Components/FormInputs/FormInputs_Documentation.razor @@ -37,7 +37,9 @@ <p> Floating labels provide a sleek and simple design, floating elegantly over your input fields. See <a href="https://getbootstrap.com/docs/5.3/forms/floating-labels/">Bootstrap 5 documentation on Floating labels</a>.<br /> - They are supported by <code>HxInputText</code>, <code>HxInputTextArea</code>, <code>HxInputNumber</code>, <code>HxInputDate</code>, <code>HxAutosuggest</code>, <code>HxSelect</code>, and <code>HxInputTags</code>. + They are supported by <code>HxInputText</code>, <code>HxInputTextArea</code>, + <code>HxInputNumber</code>, <code>HxInputDate</code>, <code>HxAutosuggest</code>, + <code>HxSelect</code>, <code>HxMultiSelect</code> and <code>HxInputTags</code>. </p> <DocAlert Type="DocAlertType.Warning"> Inputs with floating labels can't have the <code>Placeholder</code> parameter set. diff --git a/Havit.Blazor.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml b/Havit.Blazor.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml index 337ca541a..f720632d4 100644 --- a/Havit.Blazor.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml +++ b/Havit.Blazor.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml @@ -4699,6 +4699,9 @@ Icon displayed in filter input for clearing the filter. </summary> </member> + <member name="P:Havit.Blazor.Components.Web.Bootstrap.HxMultiSelect`2.LabelType"> + <inheritdoc cref="T:Havit.Blazor.Components.Web.Bootstrap.LabelType" /> + </member> <member name="M:Havit.Blazor.Components.Web.Bootstrap.HxMultiSelect`2.FocusAsync"> <inheritdoc cref="M:Havit.Blazor.Components.Web.Bootstrap.HxInputBase`1.FocusAsync"/> </member> @@ -5429,6 +5432,11 @@ Input size. </summary> </member> + <member name="P:Havit.Blazor.Components.Web.Bootstrap.MultiSelectSettings.LabelType"> + <summary> + The label type. + </summary> + </member> <member name="P:Havit.Blazor.Components.Web.Bootstrap.MultiSelectSettings.AllowFiltering"> <summary> Enables filtering capabilities. diff --git a/Havit.Blazor.TestApp/Havit.Blazor.TestApp.Client/HxMultiSelectTests/HxMultiSelect_FloatingLabel_Test.razor b/Havit.Blazor.TestApp/Havit.Blazor.TestApp.Client/HxMultiSelectTests/HxMultiSelect_FloatingLabel_Test.razor new file mode 100644 index 000000000..49ff2a4da --- /dev/null +++ b/Havit.Blazor.TestApp/Havit.Blazor.TestApp.Client/HxMultiSelectTests/HxMultiSelect_FloatingLabel_Test.razor @@ -0,0 +1,45 @@ +@page "/HxMultiSelect_FloatingLabel" +@rendermode InteractiveServer +@inject IDemoDataService DemoDataService + +<div class="m-3"> + + <HxMultiSelect Label="HxMultiSelect" + TItem="EmployeeDto" + TValue="int" + Data="@employees" + LabelType="LabelType.Floating" + @bind-Value="selectedEmployeeIds" + TextSelector="@(p => p.Name)" + ValueSelector="@(p => p.Id)" + NullDataText="Loading employees..." + EmptyText="-select employees-" /> + + For visual reference: + <HxSelect TItem="EmployeeDto" + TValue="int?" + Label="HxSelect" + LabelType="LabelType.Floating" + Data="employees" + @bind-Value="selectedEmployeeId" + TextSelector="@(employee => employee.Name)" + ValueSelector="@(employee => employee.Id)" + Nullable="true" + NullText="-select employee-" + NullDataText="Loading employees..." /> + +</div> + + +<p class="mt-3">Selected employees (IDs): @String.Join(", ", selectedEmployeeIds.Select(e => e.ToString()))</p> + +@code { + private IEnumerable<EmployeeDto> employees; + private List<int> selectedEmployeeIds = new(); + private int? selectedEmployeeId; + + protected override async Task OnInitializedAsync() + { + employees = await DemoDataService.GetAllEmployeesAsync(); + } +} \ No newline at end of file