From 2831b09080198aa76be50d6d8b7711b1f16c629d Mon Sep 17 00:00:00 2001 From: albx Date: Thu, 29 Jan 2026 16:11:01 +0100 Subject: [PATCH 01/13] #38 - start working on select component --- BitBlazor.sln | 4 ++-- src/BitBlazor/Form/BitFormComponentBase.cs | 2 +- .../Form/SelectField/BitSelectField.razor | 16 +++++++++++++ .../Form/SelectField/BitSelectField.razor.cs | 10 ++++++++ .../BitSelectFieldTest.Rendering.razor | 24 +++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/BitBlazor/Form/SelectField/BitSelectField.razor create mode 100644 src/BitBlazor/Form/SelectField/BitSelectField.razor.cs create mode 100644 tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor diff --git a/BitBlazor.sln b/BitBlazor.sln index 240f987..dfbfcc0 100644 --- a/BitBlazor.sln +++ b/BitBlazor.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.12.35527.113 d17.12 +# Visual Studio Version 18 +VisualStudioVersion = 18.2.11415.280 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" EndProject diff --git a/src/BitBlazor/Form/BitFormComponentBase.cs b/src/BitBlazor/Form/BitFormComponentBase.cs index f42eb17..7fe2657 100644 --- a/src/BitBlazor/Form/BitFormComponentBase.cs +++ b/src/BitBlazor/Form/BitFormComponentBase.cs @@ -102,7 +102,7 @@ public abstract class BitFormComponentBase : BitComponentBase /// protected BitFormComponentBase() { - if (!SupportedTypes.Contains(ComponentType)) + if (SupportedTypes.Length > 0 && !SupportedTypes.Contains(ComponentType)) { throw new NotSupportedException("Type not supported"); } diff --git a/src/BitBlazor/Form/SelectField/BitSelectField.razor b/src/BitBlazor/Form/SelectField/BitSelectField.razor new file mode 100644 index 0000000..2999b30 --- /dev/null +++ b/src/BitBlazor/Form/SelectField/BitSelectField.razor @@ -0,0 +1,16 @@ +@namespace BitBlazor.Form + +@typeparam T +@inherits BitFormComponentBase + +
+ + + + +
\ No newline at end of file diff --git a/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs new file mode 100644 index 0000000..e377f3a --- /dev/null +++ b/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs @@ -0,0 +1,10 @@ +namespace BitBlazor.Form; + +public partial class BitSelectField : BitFormComponentBase +{ + /// + protected override string FieldIdPrefix { get; } = "select"; + + /// + protected override Type[] SupportedTypes { get; } = []; +} diff --git a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor new file mode 100644 index 0000000..790b508 --- /dev/null +++ b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor @@ -0,0 +1,24 @@ +@inherits BunitContext + +@code { + [Fact] + public void BitSelectField_Should_Render_Default_Markup_Correctly() + { + string? value = null; + + var component = Render( + @); + + component.MarkupMatches( + @
+ + +
); + } +} From d6901ad142f2b8ecb7fbb49df6ae9a02e3d6afb7 Mon Sep 17 00:00:00 2001 From: Alberto Mori Date: Fri, 30 Jan 2026 22:52:33 +0100 Subject: [PATCH 02/13] #38 - add select item component and item group --- BitBlazor.sln | 2 +- .../Form/SelectField/BitSelectField.razor | 5 +- .../Form/SelectField/BitSelectField.razor.cs | 16 ++++ .../Form/SelectField/BitSelectItem.razor | 7 ++ .../Form/SelectField/BitSelectItem.razor.cs | 39 ++++++++++ .../Form/SelectField/BitSelectItemGroup.razor | 5 ++ .../SelectField/BitSelectItemGroup.razor.cs | 17 +++++ .../BitSelectFieldTest.Rendering.razor | 74 ++++++++++++++++++- 8 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 src/BitBlazor/Form/SelectField/BitSelectItem.razor create mode 100644 src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs create mode 100644 src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor create mode 100644 src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs diff --git a/BitBlazor.sln b/BitBlazor.sln index dfbfcc0..bac8e94 100644 --- a/BitBlazor.sln +++ b/BitBlazor.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.2.11415.280 d18.0 +VisualStudioVersion = 18.2.11415.280 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" EndProject diff --git a/src/BitBlazor/Form/SelectField/BitSelectField.razor b/src/BitBlazor/Form/SelectField/BitSelectField.razor index 2999b30..599e044 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectField.razor +++ b/src/BitBlazor/Form/SelectField/BitSelectField.razor @@ -10,7 +10,10 @@ ValueChanged="ValueChanged" ValueExpression="ValueExpression" name="@Id" + disabled="@Disabled" @attributes="AdditionalAttributes"> - + + @ChildContent + \ No newline at end of file diff --git a/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs index e377f3a..4b7c998 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs +++ b/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs @@ -1,5 +1,14 @@ +using Microsoft.AspNetCore.Components; + namespace BitBlazor.Form; +/// +/// Represents a select field component. +/// +/// +/// Use to define the selectable options for the field. +/// This component is intended for use within Bit form layouts and supports custom rendering of option content. +/// The type of the value represented and selected by the field. public partial class BitSelectField : BitFormComponentBase { /// @@ -7,4 +16,11 @@ public partial class BitSelectField : BitFormComponentBase /// protected override Type[] SupportedTypes { get; } = []; + + /// + /// Gets or sets the content to be rendered inside the component. + /// + [Parameter] + [EditorRequired] + public RenderFragment ChildContent { get; set; } } diff --git a/src/BitBlazor/Form/SelectField/BitSelectItem.razor b/src/BitBlazor/Form/SelectField/BitSelectItem.razor new file mode 100644 index 0000000..e3d63da --- /dev/null +++ b/src/BitBlazor/Form/SelectField/BitSelectItem.razor @@ -0,0 +1,7 @@ +@namespace BitBlazor.Form + +@typeparam TValue + + diff --git a/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs new file mode 100644 index 0000000..3e1281b --- /dev/null +++ b/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Components; + +namespace BitBlazor.Form; + +/// +/// Represents an individual selectable option within a BitSelectField component. +/// +/// +/// Use BitSelectItem as a child of BitSelectFieldto define selectable options. +/// The Value property specifies the underlying value for the option, and ChildContent defines the display content shown to the user. +/// +/// The type of the value associated with the select item. +public partial class BitSelectItem +{ + [CascadingParameter] + BitSelectField Parent { get; set; } = default!; + + /// + /// Gets or sets the value of the option. + /// + [Parameter] + public TValue? Value { get; set; } + + /// + /// Gets or sets the content to be rendered inside the component. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// Gets or sets a collection of additional attributes to be applied to the component's rendered HTML element. + /// + /// + /// Attributes in this dictionary are rendered as HTML attributes on the component's root element. + /// This allows you to specify custom attributes such as data-* or aria-* values that are not explicitly defined by the component. + /// + [Parameter(CaptureUnmatchedValues = true)] + public IDictionary AdditionalAttributes { get; set; } = new Dictionary(); +} diff --git a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor new file mode 100644 index 0000000..74a6927 --- /dev/null +++ b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor @@ -0,0 +1,5 @@ +@namespace BitBlazor.Form + + + @ChildContent + diff --git a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs new file mode 100644 index 0000000..68545ca --- /dev/null +++ b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Components; + +namespace BitBlazor.Form; + +public partial class BitSelectItemGroup +{ + [Parameter] + [EditorRequired] + public string Label { get; set; } = string.Empty; + + [Parameter] + [EditorRequired] + public RenderFragment ChildContent { get; set; } + + [Parameter(CaptureUnmatchedValues = true)] + public IDictionary AdditionalAttributes { get; set; } = new Dictionary(); +} diff --git a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor index 790b508..065daea 100644 --- a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor +++ b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor @@ -4,21 +4,89 @@ [Fact] public void BitSelectField_Should_Render_Default_Markup_Correctly() { - string? value = null; + string value = string.Empty; var component = Render( @); + @bind-Value="value"> + Choose your option + value 1 + value 2 + ); component.MarkupMatches( @
-
); } + + [Fact] + public void BitSelectField_Should_Render_Disabled_Select_Correctly() + { + string value = string.Empty; + + var component = Render( + @ + Choose your option + value 1 + value 2 + ); + + component.MarkupMatches( + @
+ + +
); + } + + [Fact] + public void BitSelectField_Should_Render_Option_Group_Correctly() + { + string value = string.Empty; + + var component = Render( + @ + Choose your option + + value 1 + value 2 + + + value 3 + value 4 + + ); + + component.MarkupMatches( + @
+ + +
); + } } From 16b6420c0ba2994636eadbf6e6b70659868f253c Mon Sep 17 00:00:00 2001 From: Alberto Mori Date: Thu, 5 Feb 2026 23:06:48 +0100 Subject: [PATCH 03/13] #38 - add stories for select field --- src/BitBlazor/Core/BitComponentBase.cs | 5 +- .../Form/SelectField/BitSelectField.razor | 6 +- .../Form/SelectField/BitSelectField.razor.cs | 11 ++- .../Form/SelectField/BitSelectItem.razor.cs | 6 +- .../SelectField/BitSelectItemGroup.razor.cs | 24 ++++++ .../Form/SelectField/IBitSelectField.cs | 5 ++ .../Stories/Form/BitSelectField.stories.razor | 77 +++++++++++++++++++ 7 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 src/BitBlazor/Form/SelectField/IBitSelectField.cs create mode 100644 stories/BitBlazor.Stories/Components/Stories/Form/BitSelectField.stories.razor diff --git a/src/BitBlazor/Core/BitComponentBase.cs b/src/BitBlazor/Core/BitComponentBase.cs index 4078678..4ad3ae4 100644 --- a/src/BitBlazor/Core/BitComponentBase.cs +++ b/src/BitBlazor/Core/BitComponentBase.cs @@ -23,9 +23,8 @@ public abstract class BitComponentBase : ComponentBase /// Gets or sets additional attributes that do not match any of the explicitly defined parameters. /// /// - /// This property is typically used to capture arbitrary HTML attributes or other key-value pairs - /// that are not explicitly defined in the component's parameters. The keys represent attribute names, and the - /// values represent their corresponding values. + /// This property is typically used to capture arbitrary HTML attributes or other key-value pairs that are not explicitly defined in the component's parameters. + /// The keys represent attribute names, and the values represent their corresponding values. /// [Parameter(CaptureUnmatchedValues = true)] public IDictionary AdditionalAttributes { get; set; } = new Dictionary(); diff --git a/src/BitBlazor/Form/SelectField/BitSelectField.razor b/src/BitBlazor/Form/SelectField/BitSelectField.razor index 599e044..25715c6 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectField.razor +++ b/src/BitBlazor/Form/SelectField/BitSelectField.razor @@ -3,7 +3,7 @@ @typeparam T @inherits BitFormComponentBase -
+
+ + @RenderValidationMessage() + + @RenderAdditionalText()
\ No newline at end of file diff --git a/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs index 4b7c998..74fa681 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs +++ b/src/BitBlazor/Form/SelectField/BitSelectField.razor.cs @@ -1,3 +1,4 @@ +using BitBlazor.Core; using Microsoft.AspNetCore.Components; namespace BitBlazor.Form; @@ -9,7 +10,7 @@ namespace BitBlazor.Form; /// Use to define the selectable options for the field. /// This component is intended for use within Bit form layouts and supports custom rendering of option content. /// The type of the value represented and selected by the field. -public partial class BitSelectField : BitFormComponentBase +public partial class BitSelectField : BitFormComponentBase, IBitSelectField { /// protected override string FieldIdPrefix { get; } = "select"; @@ -23,4 +24,12 @@ public partial class BitSelectField : BitFormComponentBase [Parameter] [EditorRequired] public RenderFragment ChildContent { get; set; } + + private string ComputeContainerCssClass() + { + var builder = new CssClassBuilder("select-wrapper"); + AddCustomCssClass(builder); + + return builder.Build(); + } } diff --git a/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs index 3e1281b..1968733 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs +++ b/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs @@ -28,11 +28,11 @@ public partial class BitSelectItem public RenderFragment? ChildContent { get; set; } /// - /// Gets or sets a collection of additional attributes to be applied to the component's rendered HTML element. + /// Gets or sets additional attributes that do not match any of the explicitly defined parameters. /// /// - /// Attributes in this dictionary are rendered as HTML attributes on the component's root element. - /// This allows you to specify custom attributes such as data-* or aria-* values that are not explicitly defined by the component. + /// This property is typically used to capture arbitrary HTML attributes or other key-value pairs that are not explicitly defined in the component's parameters. + /// The keys represent attribute names, and the values represent their corresponding values. /// [Parameter(CaptureUnmatchedValues = true)] public IDictionary AdditionalAttributes { get; set; } = new Dictionary(); diff --git a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs index 68545ca..e896b29 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs +++ b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs @@ -2,16 +2,40 @@ namespace BitBlazor.Form; +/// +/// Represents a group of selectable items within a BitSelect component. +/// +/// +/// Use BitSelectItemGroup to organize related BitSelectItem components under a common label within a BitSelect field. +/// This enhances accessibility and user experience by grouping options logically. +/// The group label is typically rendered as an optgroup label in the resulting markup. +/// public partial class BitSelectItemGroup { + [CascadingParameter] + IBitSelectField Parent { get; set; } = default!; + + /// + /// Gets or sets the text label displayed for the component. + /// [Parameter] [EditorRequired] public string Label { get; set; } = string.Empty; + /// + /// Gets or sets the content to be rendered inside the component. + /// [Parameter] [EditorRequired] public RenderFragment ChildContent { get; set; } + /// + /// Gets or sets additional attributes that do not match any of the explicitly defined parameters. + /// + /// + /// This property is typically used to capture arbitrary HTML attributes or other key-value pairs that are not explicitly defined in the component's parameters. + /// The keys represent attribute names, and the values represent their corresponding values. + /// [Parameter(CaptureUnmatchedValues = true)] public IDictionary AdditionalAttributes { get; set; } = new Dictionary(); } diff --git a/src/BitBlazor/Form/SelectField/IBitSelectField.cs b/src/BitBlazor/Form/SelectField/IBitSelectField.cs new file mode 100644 index 0000000..24a3a2e --- /dev/null +++ b/src/BitBlazor/Form/SelectField/IBitSelectField.cs @@ -0,0 +1,5 @@ +namespace BitBlazor.Form; + +internal interface IBitSelectField +{ +} \ No newline at end of file diff --git a/stories/BitBlazor.Stories/Components/Stories/Form/BitSelectField.stories.razor b/stories/BitBlazor.Stories/Components/Stories/Form/BitSelectField.stories.razor new file mode 100644 index 0000000..0b2e2bd --- /dev/null +++ b/stories/BitBlazor.Stories/Components/Stories/Form/BitSelectField.stories.razor @@ -0,0 +1,77 @@ +@attribute [Stories("Form/BitSelectField")] + + + + + + + + + + + + + + + + + + + + + + + + +@code { + private ViewModel model = new(); + + class ViewModel + { + public string DefaultStoryValue { get; set; } = string.Empty; + + public string DisabledStoryValue { get; set; } = string.Empty; + + public string GroupedStoryValue { get; set; } = string.Empty; + } +} From 2e67b69cf706c124ade4f10bd9e326620b12f69e Mon Sep 17 00:00:00 2001 From: albx Date: Sun, 8 Feb 2026 18:00:37 +0100 Subject: [PATCH 04/13] #38 - add more test on bitselectfield, add disabled parameter on option and optiongroup --- .../Form/SelectField/BitSelectItem.razor | 2 +- .../Form/SelectField/BitSelectItem.razor.cs | 6 + .../Form/SelectField/BitSelectItemGroup.razor | 2 +- .../SelectField/BitSelectItemGroup.razor.cs | 6 + .../Form/SelectField/IBitSelectField.cs | 6 + .../BitBreadcrumbTest.Rendering.razor | 132 +++++++++--------- .../BitSelectFieldTest.Rendering.razor | 109 ++++++++++++++- 7 files changed, 193 insertions(+), 70 deletions(-) diff --git a/src/BitBlazor/Form/SelectField/BitSelectItem.razor b/src/BitBlazor/Form/SelectField/BitSelectItem.razor index e3d63da..c054565 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectItem.razor +++ b/src/BitBlazor/Form/SelectField/BitSelectItem.razor @@ -2,6 +2,6 @@ @typeparam TValue - diff --git a/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs index 1968733..25f79f6 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs +++ b/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs @@ -27,6 +27,12 @@ public partial class BitSelectItem [Parameter] public RenderFragment? ChildContent { get; set; } + /// + /// Gets or sets whether the option is disabled + /// + [Parameter] + public bool Disabled { get; set; } + /// /// Gets or sets additional attributes that do not match any of the explicitly defined parameters. /// diff --git a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor index 74a6927..2e2c663 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor +++ b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor @@ -1,5 +1,5 @@ @namespace BitBlazor.Form - + @ChildContent diff --git a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs index e896b29..8b31310 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs +++ b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs @@ -29,6 +29,12 @@ public partial class BitSelectItemGroup [EditorRequired] public RenderFragment ChildContent { get; set; } + /// + /// Gets or sets whether the option group is disabled + /// + [Parameter] + public bool Disabled { get; set; } + /// /// Gets or sets additional attributes that do not match any of the explicitly defined parameters. /// diff --git a/src/BitBlazor/Form/SelectField/IBitSelectField.cs b/src/BitBlazor/Form/SelectField/IBitSelectField.cs index 24a3a2e..a5ca674 100644 --- a/src/BitBlazor/Form/SelectField/IBitSelectField.cs +++ b/src/BitBlazor/Form/SelectField/IBitSelectField.cs @@ -1,5 +1,11 @@ namespace BitBlazor.Form; +/// +/// Represents a generic select field. +/// +/// +/// This is a marker interface and it's intended for internal use and is not intended to be implemented or used directly in application code. +/// internal interface IBitSelectField { } \ No newline at end of file diff --git a/tests/BitBlazor.Test/Components/Breadcrumb/BitBreadcrumbTest.Rendering.razor b/tests/BitBlazor.Test/Components/Breadcrumb/BitBreadcrumbTest.Rendering.razor index 6c1d565..9e160ec 100644 --- a/tests/BitBlazor.Test/Components/Breadcrumb/BitBreadcrumbTest.Rendering.razor +++ b/tests/BitBlazor.Test/Components/Breadcrumb/BitBreadcrumbTest.Rendering.razor @@ -1,4 +1,4 @@ -@inherits TestContext +@inherits BunitContext @code { [Theory] @@ -17,25 +17,25 @@ var component = Render(@); component.MarkupMatches( - @); + @); } [Theory] @@ -54,29 +54,29 @@ var component = Render(@); component.MarkupMatches( - @); + @); } [Theory] @@ -95,28 +95,28 @@ var component = Render(@); component.MarkupMatches( - @); + @); } } diff --git a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor index 065daea..52df6e3 100644 --- a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor +++ b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor @@ -57,11 +57,116 @@ { string value = string.Empty; + var component = Render( + @ + Choose your option + + value 1 + value 2 + + + value 3 + value 4 + + ); + + component.MarkupMatches( + @
+ + +
); + } + + [Fact] + public void BitSelectField_Should_Render_Addtional_Text_Correctly() + { + string value = string.Empty; + var component = Render( @ + AdditionalTextId="help-text"> + + Choose your option + + value 1 + value 2 + + + value 3 + value 4 + + + + This is an help text + + ); + + component.MarkupMatches( + @
+ + + This is an help text +
); + } + + [Fact] + public void BitSelectField_Should_Render_Option_As_Disabled_If_Option_Disabled_Property_Is_Set_To_True() + { + string value = string.Empty; + + var component = Render( + @ + Choose your option + value 1 + value 2 + ); + + component.MarkupMatches( + @
+ + +
); + } + + [Fact] + public void BitSelectField_Should_Render_Option_Group_Disabled_If_Disabled_Property_Is_Set_To_True() + { + string value = string.Empty; + + var component = Render( + @ Choose your option value 1 @@ -76,7 +181,7 @@ component.MarkupMatches( @
- From da3964e05c352f6184e987540d4838c86397bf8c Mon Sep 17 00:00:00 2001 From: albx Date: Sun, 8 Feb 2026 18:18:04 +0100 Subject: [PATCH 05/13] #38 - add documentation for select component --- docs/README.md | 8 + docs/form/form-components.md | 16 + docs/form/select-field.md | 382 ++++++++++++++++++ docs/quick-reference.md | 43 ++ .../BitBlazor.Stories.csproj | 6 +- 5 files changed, 452 insertions(+), 3 deletions(-) create mode 100644 docs/form/select-field.md diff --git a/docs/README.md b/docs/README.md index 028988e..1d2699e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -97,6 +97,14 @@ Numeric input field component with increment/decrement controls. - Min/max value constraints - Custom step values and symbol content +#### [BitSelectField](form/select-field.md) +Dropdown select input field component. +- Generic type support for any value type +- Support for option grouping +- Disabled options and groups +- Form validation integration +- Accessible and responsive + #### [BitCheckbox](form/checkbox.md) Checkbox component for binary choices. - Boolean value selection diff --git a/docs/form/form-components.md b/docs/form/form-components.md index 900a82f..3888378 100644 --- a/docs/form/form-components.md +++ b/docs/form/form-components.md @@ -21,6 +21,7 @@ BitBlazor provides a comprehensive set of form components that integrate with: | [`BitPasswordField`](password-field.md) | Password input with toggle | Show/hide functionality, secure input | | [`BitTextAreaField`](text-area-field.md) | Multi-line text input | Configurable rows, auto-sizing | | [`BitNumberField`](number-field.md) | Numeric input with controls | Increment/decrement buttons, type safety, min/max | +| [`BitSelectField`](select-field.md) | Dropdown select input | Generic type support, option grouping, form validation | | [`BitDatepicker`](datepicker.md) | Date input with picker | DateTime/DateOnly support, native browser UI, validation | | [`BitTimepicker`](timepicker.md) | Time input with picker | TimeOnly support, native browser UI, validation | | [`BitCheckbox`](checkbox.md) | Boolean checkbox input | Inline/grouped layouts, form validation | @@ -152,6 +153,18 @@ All input components support three sizes: @bind-Value="registrationModel.Phone" For="@(() => registrationModel.Phone)" />
+
+ + Select your country... + Italy + United States + United Kingdom + France + Germany + +
` component is designed to handle selection from a list of options and provides built-in support for form integration and validation. It is a generic component that can work with any data type and supports option grouping, disabled options, and accessibility attributes. + +## Parameters + +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `Value` | `T?` | ✗ | `null` | The current value of the select field | +| `ValueChanged` | `EventCallback` | ✗ | - | Callback fired when the value changes | +| `ValueExpression` | `Expression>` | ✗ | - | Expression for model binding and validation | +| `Label` | `string` | ✓ | - | The label text for the select field | +| `Disabled` | `bool` | ✗ | `false` | Whether the select field is disabled | +| `Placeholder` | `string?` | ✗ | `null` | Placeholder text displayed when no option is selected | +| `ChildContent` | `RenderFragment` | ✓ | - | Content defining the selectable options (BitSelectItem or BitSelectItemGroup) | +| `AdditionalText` | `RenderFragment?` | ✗ | `null` | Additional descriptive text displayed below the select | +| `AdditionalTextId` | `string?` | ✗ | `null` | ID for the additional text (used for aria-describedby) | +| `AdditionalAttributes` | `Dictionary` | ✗ | `{}` | Additional HTML attributes | + +## Child Components + +### BitSelectItem + +Represents an individual selectable option within a BitSelectField component. + +**Namespace**: `BitBlazor.Form` + +#### Parameters + +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `Value` | `TValue?` | ✗ | `null` | The value of the option | +| `ChildContent` | `RenderFragment?` | ✗ | `null` | Content to be rendered as the option text | +| `Disabled` | `bool` | ✗ | `false` | Whether the option is disabled | +| `AdditionalAttributes` | `Dictionary` | ✗ | `{}` | Additional HTML attributes | + +### BitSelectItemGroup + +Represents a group of selectable items within a BitSelectField component. + +**Namespace**: `BitBlazor.Form` + +#### Parameters + +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `Label` | `string` | ✓ | - | The label text for the option group | +| `ChildContent` | `RenderFragment` | ✓ | - | Content defining the options in the group | +| `Disabled` | `bool` | ✗ | `false` | Whether the entire option group is disabled | +| `AdditionalAttributes` | `Dictionary` | ✗ | `{}` | Additional HTML attributes | + +## Usage Examples + +### Basic select field + +```razor + + Select a country... + Italy + United States + United Kingdom + France + + +@code { + private FormModel model = new(); + + private class FormModel + { + public string Country { get; set; } = string.Empty; + } +} +``` + +### Select field with validation + +```razor + + + + + Select a role... + Administrator + User + Guest + + + + + + Submit + + + +@code { + private UserModel model = new(); + + private class UserModel + { + [Required(ErrorMessage = "Please select a role")] + public string Role { get; set; } = string.Empty; + } + + private async Task HandleSubmit() + { + // Handle form submission + } +} +``` + +### Select field with grouped options + +```razor + + Choose a browser... + + + Google Chrome + Mozilla Firefox + Microsoft Edge + Apple Safari + + + + Internet Explorer 11 + Opera + + + +@code { + private BrowserModel model = new(); + + private class BrowserModel + { + public string Browser { get; set; } = string.Empty; + } +} +``` + +### Select field with disabled options + +```razor + + Select a plan... + Free - Basic features + Pro - Advanced features + + Enterprise - Contact sales + + + +@code { + private SubscriptionModel model = new(); + + private class SubscriptionModel + { + public string Plan { get; set; } = string.Empty; + } +} +``` + +### Select field with numeric values + +```razor + + Select priority... + Low + Medium + High + Critical + + +@code { + private TaskModel model = new(); + + private class TaskModel + { + public int Priority { get; set; } + } +} +``` + +### Select field with enum values + +```razor + + Pending + Processing + Shipped + Delivered + Cancelled + + +@code { + private OrderModel model = new() { Status = OrderStatus.Pending }; + + private class OrderModel + { + public OrderStatus Status { get; set; } + } + + private enum OrderStatus + { + Pending, + Processing, + Shipped, + Delivered, + Cancelled + } +} +``` + +### Select field with additional text + +```razor + + + Select your preferred language for the application interface. + + Select a language... + English + Italiano + Español + Français + + +@code { + private SettingsModel model = new(); + + private class SettingsModel + { + public string Language { get; set; } = "en"; + } +} +``` + +### Disabled select field + +```razor + + Select payment method... + Credit Card + PayPal + Bank Transfer + + +@code { + private PaymentModel model = new(); + + private class PaymentModel + { + public string PaymentMethod { get; set; } = string.Empty; + } +} +``` + +## Generated HTML Structure + +### Basic select field + +```html +
+ + +
+``` + +### Select field with grouped options + +```html +
+ + +
+``` + +### Select field with validation error + +```html +
+ + +
+``` + +### Select field with disabled option + +```html +
+ + +
+``` + +## Generated CSS Classes + +### Container classes + +- `select-wrapper` - Container wrapper for the entire field + +## Accessibility + +- Automatically associates labels with select elements using the `for` attribute +- Supports `aria-describedby` when AdditionalText is provided +- Maintains focus management and keyboard navigation +- Compatible with screen readers +- Supports validation message announcements +- Option groups enhance screen reader navigation and organization +- Disabled options are properly announced to assistive technologies + +## Form Integration + +- Full support for `EditForm` and model binding +- Compatible with `DataAnnotationsValidator` +- Integrates with `ValidationSummary` and `ValidationMessage` +- Supports `For` expression for validation binding +- Automatic validation state styling +- Generic type support allows binding to any value type (string, int, enum, etc.) + +## Notes + +- The component automatically generates unique IDs for form fields using the "select" prefix +- BitSelectField is a generic component - you can use it with any type: `BitSelectField`, `BitSelectField`, `BitSelectField`, etc. +- Use BitSelectItem components as direct children to define individual options +- Use BitSelectItemGroup to organize related options under a common label (rendered as optgroup) +- The first option typically serves as a placeholder (with empty or default value) +- Options and groups can be individually disabled +- The component extends `BitFormComponentBase` for consistent form behavior +- All Bootstrap Italia form styling is automatically applied +- The component supports all standard HTML select attributes through `AdditionalAttributes` +- When working with enums, ensure you pass enum values directly to BitSelectItem without converting to string diff --git a/docs/quick-reference.md b/docs/quick-reference.md index 50ebe41..9dbe37c 100644 --- a/docs/quick-reference.md +++ b/docs/quick-reference.md @@ -154,6 +154,34 @@ This guide provides a quick overview of all BitBlazor components with basic exam Step="1" /> ``` +### BitSelectField +```razor + + + Select a country... + Italy + United States + United Kingdom + + + + + Choose a browser... + + Google Chrome + Mozilla Firefox + Microsoft Edge + + + + + + Pending + Processing + Shipped + +``` + ### BitCheckbox ```razor @@ -379,6 +407,18 @@ Size.Large // Large @bind-Value="model.Phone" For="@(() => model.Phone)" />
+
+ + Select a country... + Italy + United States + United Kingdom + France + Germany + +
- - - + + + all runtime; build; native; contentfiles; analyzers From 3899ee8ba368747b787eab826bdd1ef02cc3b600 Mon Sep 17 00:00:00 2001 From: Alberto Date: Mon, 9 Feb 2026 08:15:08 +0100 Subject: [PATCH 06/13] #38 - Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/form/select-field.md | 2 +- src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs | 2 +- src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs | 4 ++-- .../Components/Stories/Form/BitSelectField.stories.razor | 4 ++-- .../Form/SelectField/BitSelectFieldTest.Rendering.razor | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/form/select-field.md b/docs/form/select-field.md index 7031ef3..a34bca0 100644 --- a/docs/form/select-field.md +++ b/docs/form/select-field.md @@ -10,7 +10,7 @@ BitBlazor.Form ## Description -The `BitSelectField` component is designed to handle selection from a list of options and provides built-in support for form integration and validation. It is a generic component that can work with any data type and supports option grouping, disabled options, and accessibility attributes. +The `BitSelectField` component is designed to handle selection from a list of options and provides built-in support for form integration and validation. It is a generic component that can work with any data type supported by Blazor's value converters (or with an appropriate custom converter) and supports option grouping, disabled options, and accessibility attributes. ## Parameters diff --git a/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs index 25f79f6..189f753 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs +++ b/src/BitBlazor/Form/SelectField/BitSelectItem.razor.cs @@ -6,7 +6,7 @@ namespace BitBlazor.Form; /// Represents an individual selectable option within a BitSelectField component. /// /// -/// Use BitSelectItem as a child of BitSelectFieldto define selectable options. +/// Use BitSelectItem as a child of BitSelectField to define selectable options. /// The Value property specifies the underlying value for the option, and ChildContent defines the display content shown to the user. /// /// The type of the value associated with the select item. diff --git a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs index 8b31310..2dab1b6 100644 --- a/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs +++ b/src/BitBlazor/Form/SelectField/BitSelectItemGroup.razor.cs @@ -3,10 +3,10 @@ namespace BitBlazor.Form; /// -/// Represents a group of selectable items within a BitSelect component. +/// Represents a group of selectable items within a BitSelectField component. /// /// -/// Use BitSelectItemGroup to organize related BitSelectItem components under a common label within a BitSelect field. +/// Use BitSelectItemGroup to organize related BitSelectItem components under a common label within a BitSelectField component. /// This enhances accessibility and user experience by grouping options logically. /// The group label is typically rendered as an optgroup label in the resulting markup. /// diff --git a/stories/BitBlazor.Stories/Components/Stories/Form/BitSelectField.stories.razor b/stories/BitBlazor.Stories/Components/Stories/Form/BitSelectField.stories.razor index 0b2e2bd..f4a0a75 100644 --- a/stories/BitBlazor.Stories/Components/Stories/Form/BitSelectField.stories.razor +++ b/stories/BitBlazor.Stories/Components/Stories/Form/BitSelectField.stories.razor @@ -31,7 +31,7 @@
+ @bind-Value="model.DisabledStoryValue"> Choose an option Option 1 Option 2 @@ -64,7 +64,7 @@ @code { - private ViewModel model = new(); + private readonly ViewModel model = new(); class ViewModel { diff --git a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor index 52df6e3..a686f6a 100644 --- a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor +++ b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor @@ -90,7 +90,7 @@ } [Fact] - public void BitSelectField_Should_Render_Addtional_Text_Correctly() + public void BitSelectField_Should_Render_Additional_Text_Correctly() { string value = string.Empty; From 7eaf82e0aa82a5324518df6572f060f72841dfde Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 12:31:48 +0000 Subject: [PATCH 07/13] Remove leading spaces from 'value 2' option text in test expectations Co-authored-by: albx <5121303+albx@users.noreply.github.com> --- .../BitSelectFieldTest.Rendering.razor | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor index a686f6a..92a3848 100644 --- a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor +++ b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor @@ -21,7 +21,7 @@
); } @@ -47,7 +47,7 @@
); } @@ -79,11 +79,11 @@ - + - +
); @@ -122,11 +122,11 @@ - + - + This is an help text @@ -153,7 +153,7 @@ ); } @@ -185,11 +185,11 @@ - + - + ); From cbf61166b31d5c004dd571ccbd9c422394221423 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 12:33:37 +0000 Subject: [PATCH 08/13] Set Disabled=true on first BitSelectItemGroup and update expected markup Co-authored-by: albx <5121303+albx@users.noreply.github.com> --- .../Form/SelectField/BitSelectFieldTest.Rendering.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor index 92a3848..5f28b49 100644 --- a/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor +++ b/tests/BitBlazor.Test/Form/SelectField/BitSelectFieldTest.Rendering.razor @@ -168,7 +168,7 @@ Id="test-selectfield" @bind-Value="value"> Choose your option - + value 1 value 2 @@ -183,7 +183,7 @@ - This is an help text + This is a help text ); }