diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxCheckbox.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxCheckbox.cs index aba50774e..2f66a1633 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxCheckbox.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxCheckbox.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Localization; +using Havit.Blazor.Components.Web.Bootstrap.Internal; +using Microsoft.Extensions.Localization; namespace Havit.Blazor.Components.Web.Bootstrap; @@ -7,7 +8,7 @@ namespace Havit.Blazor.Components.Web.Bootstrap; /// (Replaces the former HxInputCheckbox component which was dropped in v 4.0.0.) /// Full documentation and demos: https://havit.blazor.eu/components/HxCheckbox /// -public class HxCheckbox : HxInputBase +public class HxCheckbox : HxInputBase, IInputWithToggleButton { /// /// Set of settings to be applied to the component instance. @@ -19,6 +20,11 @@ public class HxCheckbox : HxInputBase /// protected override CheckboxSettings GetSettings() => Settings; + /// + /// Input as toggle or regular. + /// + [Parameter] public InputAsToggle? InputAsToggle { get; set; } + /// /// Text to display next to the checkbox. /// diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxCheckboxList.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxCheckboxList.cs index 52256298e..5eb02729c 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxCheckboxList.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxCheckboxList.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using Havit.Blazor.Components.Web.Bootstrap.Internal; namespace Havit.Blazor.Components.Web.Bootstrap; @@ -6,7 +7,7 @@ namespace Havit.Blazor.Components.Web.Bootstrap; /// Renders a multi-selection list of controls.
/// Full documentation and demos: https://havit.blazor.eu/components/HxCheckboxList /// -public class HxCheckboxList : HxInputBase> // cannot use an array: https://github.com/dotnet/aspnetcore/issues/15014 +public class HxCheckboxList : HxInputBase>, IInputWithToggleButton // cannot use an array: https://github.com/dotnet/aspnetcore/issues/15014 { /// /// Items to display. @@ -19,6 +20,11 @@ public class HxCheckboxList : HxInputBase> // cannot /// [Parameter] public Func ItemTextSelector { get; set; } + /// + /// Input as toggle or regular. + /// + [Parameter] public InputAsToggle? InputAsToggle { get; set; } + /// /// Selects the value from the item. /// Not required when TValue is the same as TItem. @@ -106,6 +112,13 @@ private void RefreshState() } } + /// + /// Bootstrap button style - theme color.
+ /// The default is taken from ( if not customized). + ///
+ [Parameter] public ThemeColor? Color { get; set; } + protected ThemeColor ColorEffective => Color ?? ThemeColor.None; + /// protected override void BuildRenderInput(RenderTreeBuilder builder) { @@ -113,6 +126,8 @@ protected override void BuildRenderInput(RenderTreeBuilder builder) if (_itemsToRender.Count > 0) { + var inputAsToggleEffective = (this as IInputWithToggleButton).InputAsToggleEffective; + UglyHack uglyHack = new UglyHack(); // see comment below foreach (var item in _itemsToRender) @@ -127,8 +142,8 @@ protected override void BuildRenderInput(RenderTreeBuilder builder) builder.AddAttribute(5, nameof(HxCheckbox.Enabled), EnabledEffective); builder.AddAttribute(6, nameof(HxCheckbox.CssClass), CssClassHelper.Combine(ItemCssClass, ItemCssClassSelector?.Invoke(item))); - builder.AddAttribute(7, nameof(HxCheckbox.InputCssClass), CssClassHelper.Combine(ItemInputCssClass, ItemInputCssClassSelector?.Invoke(item))); - builder.AddAttribute(8, nameof(HxCheckbox.TextCssClass), CssClassHelper.Combine(ItemTextCssClass, ItemTextCssClassSelector?.Invoke(item))); + builder.AddAttribute(7, nameof(HxCheckbox.InputCssClass), CssClassHelper.Combine(inputAsToggleEffective == Bootstrap.InputAsToggle.Toggle ? "btn-check" : null, ItemInputCssClass, ItemInputCssClassSelector?.Invoke(item))); + builder.AddAttribute(8, nameof(HxCheckbox.TextCssClass), CssClassHelper.Combine(inputAsToggleEffective == Bootstrap.InputAsToggle.Toggle ? $"btn {ColorEffective.ToButtonColorCss(true)}" : null, ItemTextCssClass, ItemTextCssClassSelector?.Invoke(item))); // We need ValueExpression. Ehm, HxCheckbox needs ValueExpression. Because it is InputBase which needs ValueExpression. // We have nothing to give the HxCheckbox. So we make own class with property which we assign to the ValueExpression. diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxInputBase.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxInputBase.cs index 300f9b8c7..3ba77b9b9 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxInputBase.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxInputBase.cs @@ -108,10 +108,22 @@ public abstract class HxInputBase : InputBase, ICascadeEnabledCo /// /// The CSS class to be rendered with the wrapping div. /// - private protected virtual string CoreCssClass => CssClassHelper.Combine("hx-form-group position-relative", - ((this is IInputWithLabelType inputWithLabelType) && (inputWithLabelType.LabelTypeEffective == LabelType.Floating)) - ? "form-floating" - : null); + private protected virtual string CoreCssClass + { + get + { + var cssClass = ""; + if ((this is IInputWithToggleButton tbutton) && (tbutton.InputAsToggleEffective == InputAsToggle.Toggle)) + { + cssClass = CssClassHelper.Combine(cssClass, "btn-group"); + } + if ((this is IInputWithLabelType inputWithLabelType) && (inputWithLabelType.LabelTypeEffective == LabelType.Floating)) + { + cssClass = CssClassHelper.Combine(cssClass, "form-floating"); + } + return CssClassHelper.Combine("hx-form-group position-relative", cssClass); + } + } /// /// The CSS class to be rendered with the input element. diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxRadioButtonListBase.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxRadioButtonListBase.cs index d5146271c..fc080fe92 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/HxRadioButtonListBase.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/HxRadioButtonListBase.cs @@ -1,4 +1,5 @@ -using Havit.Blazor.Components.Web.Infrastructure; +using Havit.Blazor.Components.Web.Bootstrap.Internal; +using Havit.Blazor.Components.Web.Infrastructure; namespace Havit.Blazor.Components.Web.Bootstrap; @@ -7,13 +8,18 @@ namespace Havit.Blazor.Components.Web.Bootstrap; /// /// Type of value. /// Type of items. -public abstract class HxRadioButtonListBase : HxInputBase +public abstract class HxRadioButtonListBase : HxInputBase, IInputWithToggleButton { /// /// Allows grouping radios on the same horizontal row by rendering them inline. Default is false. /// [Parameter] public bool Inline { get; set; } + /// + /// Input as toggle or regular. + /// + [Parameter] public InputAsToggle? InputAsToggle { get; set; } + /// /// Selects a value from an item. /// Not required when TValueType is the same as TItemTime. @@ -134,6 +140,7 @@ protected void BuildRenderInput_RenderRadioItem(RenderTreeBuilder builder, int i var item = _itemsToRender[index]; if (item != null) { + var inputAsToggleEffective = (this as IInputWithToggleButton).InputAsToggleEffective; bool selected = (index == _selectedItemIndex); if (selected) { @@ -142,13 +149,14 @@ protected void BuildRenderInput_RenderRadioItem(RenderTreeBuilder builder, int i string inputId = GroupName + "_" + index.ToString(); - builder.OpenElement(100, "div"); - - // TODO CoreCssClass - builder.AddAttribute(101, "class", CssClassHelper.Combine("form-check", Inline ? "form-check-inline" : null, ItemCssClassImpl, ItemCssClassSelectorImpl?.Invoke(item))); - + if (inputAsToggleEffective != Bootstrap.InputAsToggle.Toggle) + { + builder.OpenElement(100, "div"); + // TODO CoreCssClass + builder.AddAttribute(101, "class", CssClassHelper.Combine("form-check", Inline ? "form-check-inline" : null, ItemCssClassImpl, ItemCssClassSelectorImpl?.Invoke(item))); + } builder.OpenElement(200, "input"); - builder.AddAttribute(201, "class", CssClassHelper.Combine("form-check-input", ItemInputCssClassImpl, ItemInputCssClassSelectorImpl?.Invoke(item))); + builder.AddAttribute(201, "class", CssClassHelper.Combine(inputAsToggleEffective == Bootstrap.InputAsToggle.Toggle ? "btn-check" : "form-check-input", ItemInputCssClassImpl, ItemInputCssClassSelectorImpl?.Invoke(item))); builder.AddAttribute(202, "type", "radio"); builder.AddAttribute(203, "name", GroupName); builder.AddAttribute(204, "id", inputId); @@ -165,7 +173,7 @@ protected void BuildRenderInput_RenderRadioItem(RenderTreeBuilder builder, int i builder.CloseElement(); // input builder.OpenElement(300, "label"); - builder.AddAttribute(301, "class", CssClassHelper.Combine("form-check-label", ItemTextCssClassImpl, ItemTextCssClassSelectorImpl?.Invoke(item))); + builder.AddAttribute(301, "class", CssClassHelper.Combine(inputAsToggleEffective == Bootstrap.InputAsToggle.Toggle ? "btn" : "form-check-label", ItemTextCssClassImpl, ItemTextCssClassSelectorImpl?.Invoke(item))); builder.AddAttribute(302, "for", inputId); if (ItemTemplateImpl != null) { @@ -177,7 +185,10 @@ protected void BuildRenderInput_RenderRadioItem(RenderTreeBuilder builder, int i } builder.CloseElement(); // label - builder.CloseElement(); // div + if (inputAsToggleEffective != Bootstrap.InputAsToggle.Toggle) + { + builder.CloseElement(); // div + } } } diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/IInputWithToggleButton.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/IInputWithToggleButton.cs new file mode 100644 index 000000000..e9efd2968 --- /dev/null +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/Internal/IInputWithToggleButton.cs @@ -0,0 +1,16 @@ +namespace Havit.Blazor.Components.Web.Bootstrap.Internal; +/// +/// Represents an input with a toggle button option +/// +public interface IInputWithToggleButton +{ + /// + /// Gets or sets the input as toggle or regular. + /// + InputAsToggle? InputAsToggle { get; set; } + + /// + /// Gets the effective input type. + /// + InputAsToggle InputAsToggleEffective => InputAsToggle ?? Bootstrap.InputAsToggle.Regular; +} diff --git a/Havit.Blazor.Components.Web.Bootstrap/InputAsToggle.cs b/Havit.Blazor.Components.Web.Bootstrap/InputAsToggle.cs new file mode 100644 index 000000000..352679b6b --- /dev/null +++ b/Havit.Blazor.Components.Web.Bootstrap/InputAsToggle.cs @@ -0,0 +1,15 @@ +namespace Havit.Blazor.Components.Web.Bootstrap; +/// +/// Input display type. +/// +public enum InputAsToggle +{ + /// + /// Regular + /// + Regular = 0, + /// + /// Toggle buttons. + /// + Toggle = 1 +} diff --git a/Havit.Blazor.Documentation/Pages/Components/HxCheckboxListDoc/HxCheckboxList_Demo_Toggle.razor b/Havit.Blazor.Documentation/Pages/Components/HxCheckboxListDoc/HxCheckboxList_Demo_Toggle.razor new file mode 100644 index 000000000..3c32df05e --- /dev/null +++ b/Havit.Blazor.Documentation/Pages/Components/HxCheckboxListDoc/HxCheckboxList_Demo_Toggle.razor @@ -0,0 +1,26 @@ +@inject IDemoDataService DemoDataService + + + +

+ Selected employee IDs: @String.Join(", ", selectedEmployeeIds.Select(i => i.ToString()) ?? Enumerable.Empty()) +

+ +@code +{ + private IEnumerable data; + private List selectedEmployeeIds { get; set; } = new(); + + protected override async Task OnParametersSetAsync() + { + data = await DemoDataService.GetPreferredEmployeesAsync(count: 5); + } +} diff --git a/Havit.Blazor.Documentation/Pages/Components/HxCheckboxListDoc/HxCheckboxList_Documentation.razor b/Havit.Blazor.Documentation/Pages/Components/HxCheckboxListDoc/HxCheckboxList_Documentation.razor index f3f5df6b2..46e4813f5 100644 --- a/Havit.Blazor.Documentation/Pages/Components/HxCheckboxListDoc/HxCheckboxList_Documentation.razor +++ b/Havit.Blazor.Documentation/Pages/Components/HxCheckboxListDoc/HxCheckboxList_Documentation.razor @@ -8,4 +8,9 @@

Group checkboxes on the same horizontal row by adding Inline="true".

+ + +

Change the look of checkboxes by adding InputAsToggle="InputAsToggle.Toggle"

+ + diff --git a/Havit.Blazor.Documentation/Pages/Components/HxRadioButtonListDoc/HxRadioButtonList_Demo_Toggle.razor b/Havit.Blazor.Documentation/Pages/Components/HxRadioButtonListDoc/HxRadioButtonList_Demo_Toggle.razor new file mode 100644 index 000000000..409a4e7a7 --- /dev/null +++ b/Havit.Blazor.Documentation/Pages/Components/HxRadioButtonListDoc/HxRadioButtonList_Demo_Toggle.razor @@ -0,0 +1,22 @@ +@inject IDemoDataService DemoDataService + + + +

Selected employee ID: @selectedEmployeeId

+ +@code { + private IEnumerable data; + private int? selectedEmployeeId; + + protected override async Task OnInitializedAsync() + { + data = await DemoDataService.GetPreferredEmployeesAsync(count: 5); + } +} \ No newline at end of file diff --git a/Havit.Blazor.Documentation/Pages/Components/HxRadioButtonListDoc/HxRadioButtonList_Documentation.razor b/Havit.Blazor.Documentation/Pages/Components/HxRadioButtonListDoc/HxRadioButtonList_Documentation.razor index 0807f85dc..9840d31bb 100644 --- a/Havit.Blazor.Documentation/Pages/Components/HxRadioButtonListDoc/HxRadioButtonList_Documentation.razor +++ b/Havit.Blazor.Documentation/Pages/Components/HxRadioButtonListDoc/HxRadioButtonList_Documentation.razor @@ -1,4 +1,4 @@ -@attribute [Route("/components/" + nameof(HxRadioButtonList))] +@attribute [Route("/components/" + nameof(HxRadioButtonList))] @attribute [Route("/components/" + nameof(HxRadioButtonListBase))] @@ -11,4 +11,7 @@

Group radios on the same horizontal row by adding Inline="true".

+ +

Change the look of radios by adding InputAsToggle="InputAsToggle.Toggle"

+
\ No newline at end of file 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 68c6b01d0..f3cf6da0e 100644 --- a/Havit.Blazor.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml +++ b/Havit.Blazor.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml @@ -3078,6 +3078,21 @@ Default is null (unlimited).
+ + + Represents an input with a toggle button option + + + + + Gets or sets the input as toggle or regular. + + + + + Gets the effective input type. + + Takes as its value a comma-separated list of one or more file types, or unique file type specifiers, describing which file types to allow. @@ -3429,6 +3444,11 @@ Returns an optional set of component settings. + + + Input as toggle or regular. + + Text to display next to the checkbox. @@ -3509,6 +3529,11 @@ When not set, ToString() is used. + + + Input as toggle or regular. + + Selects the value from the item. @@ -3572,6 +3597,12 @@ Returns an optional set of component settings. + + + Bootstrap button style - theme color.
+ The default is taken from ( if not customized). +
+
@@ -4775,6 +4806,11 @@ Allows grouping radios on the same horizontal row by rendering them inline. Default is false.
+ + + Input as toggle or regular. + + Selects a value from an item. @@ -7069,6 +7105,21 @@ + + + Input display type. + + + + + Regular + + + + + Toggle buttons. + + Non-generic API for .