diff --git a/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs b/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs index 64f809290..d2f9c7d53 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs +++ b/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs @@ -22,6 +22,7 @@ internal override IEnumerable<NavItem> GetNavItems() new (){ Id = "401", Text = "Currency Input", Href = RouteConstants.Demos_CurrencyInput_Documentation, IconName = IconName.CurrencyDollar, ParentId = "4" }, new (){ Id = "402", Text = "Date Input", Href = RouteConstants.Demos_DateInput_Documentation, IconName = IconName.CalendarDate, ParentId = "4" }, new (){ Id = "403", Text = "Number Input", Href = RouteConstants.Demos_NumberInput_Documentation, IconName = IconName.InputCursor, ParentId = "4" }, + new (){ Id = "403", Text = "Password Input", Href = RouteConstants.Demos_PasswordInput_Documentation, IconName = IconName.EyeSlashFill, ParentId = "4" }, new (){ Id = "403", Text = "Radio Input", Href = RouteConstants.Demos_RadioInput_Documentation, IconName = IconName.RecordCircle, ParentId = "4" }, new (){ Id = "404", Text = "Range Input", Href = RouteConstants.Demos_RangeInput_Documentation, IconName = IconName.Sliders, ParentId = "4" }, //new (){ Id = "404", Text = "Select Input", Href = RouteConstants.Demos_SelectInput_Documentation, IconName = IconName.MenuButtonWideFill, ParentId = "4" }, diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInputDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInputDocumentation.razor new file mode 100644 index 000000000..d8dcab10c --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInputDocumentation.razor @@ -0,0 +1,51 @@ +@page "/password-input" + +@attribute [Route(pageUrl)] + +<PageMetaTags PageUrl="@pageUrl" + Title="@metaTitle" + Description="@metaDescription" + ImageUrl="@imageUrl" /> + +<PageHero Heading="@pageTitle"> + <LeadSection>@pageDescription</LeadSection> +</PageHero> + +<CarbonAds /> + +<Section Size="HeadingSize.H2" Name="Basic usage" PageUrl="@pageUrl" Link="basic-usage"> + <Demo Type="typeof(PasswordInput_Demo_01_Basic_Usage)" Tabs="true" /> +</Section> + +<Section Size="HeadingSize.H2" Name="Disable" PageUrl="@pageUrl" Link="disable"> + <div class="mb-3">Use the <code>Disabled</code> parameter to disable the <code>TextInput</code>.</div> + <Demo Type="typeof(PasswordInput_Demo_02_Disable_A)" Tabs="false" /> + <div class="my-3">Also, use <b>Enable()</b> and <b>Disable()</b> methods to enable and disable the <code>TextInput</code>.</div> + <Callout Color="CalloutColor.Warning" Heading="NOTE"> + Do not use both the <b>Disabled</b> parameter and <b>Enable()</b> & <b>Disable()</b> methods. + </Callout> + <Demo Type="typeof(PasswordInput_Demo_02_Disable_B)" Tabs="false" /> +</Section> + +<Section Size="HeadingSize.H2" Name="Valdations" PageUrl="@pageUrl" Link="validations"> + <div class="mb-3"> + Like any other blazor input component, <code>PasswordInput</code> supports validations. + Add the DataAnnotations on the <code>PasswordInput</code> component to validate the user input before submitting the form. + In the below example, we used <b>Required</b> attribute. + </div> + <Demo Type="typeof(PasswordInput_Demo_03_Validations)" Tabs="true" /> +</Section> + +<Section Size="HeadingSize.H2" Name="Events: ValueChanged" PageUrl="@pageUrl" Link="events-value-changed"> + <div class="mb-3">This event fires when the <code>PasswordInput</code> value changes, but not on every keystroke.</div> + <Demo Type="typeof(PasswordInput_Demo_04_Events_ValueChanged)" Tabs="true" /> +</Section> + +@code { + private const string pageUrl = RouteConstants.Demos_PasswordInput_Documentation; + private const string pageTitle = "Blazor PasswordInput"; + private const string pageDescription = "The Blazor Bootstrap PasswordInput component is constructed using an HTML input of type 'password'."; + private const string metaTitle = "Blazor PasswordInput Component"; + private const string metaDescription = "The Blazor Bootstrap PasswordInput component is constructed using an HTML input of type 'password'."; + private const string imageUrl = "https://i.imgur.com/1mVjqQv.png"; // TODO: Update image URL +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_01_Basic_Usage.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_01_Basic_Usage.razor new file mode 100644 index 000000000..4b0c05b09 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_01_Basic_Usage.razor @@ -0,0 +1,8 @@ +<div class="mb-3"> + <PasswordInput @bind-Value="@enteredPassword" /> +</div> +<div class="mb-3">Entered password: @enteredPassword</div> + +@code { + private string? enteredPassword = null; +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_02_Disable_A.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_02_Disable_A.razor new file mode 100644 index 000000000..d47989563 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_02_Disable_A.razor @@ -0,0 +1,20 @@ +<div class="mb-3"> + <PasswordInput @bind-Value="@enteredPassword" Disabled="@disabled" /> +</div> +<div class="mb-3">Entered password: @enteredPassword</div> + +<Button Color="ButtonColor.Primary" @onclick="Enable"> Enable </Button> +<Button Color="ButtonColor.Secondary" @onclick="Disable"> Disable </Button> +<Button Color="ButtonColor.Warning" @onclick="Toggle"> Toggle </Button> + +@code { + private string? enteredPassword = null; + + private bool disabled = true; + + private void Enable() => disabled = false; + + private void Disable() => disabled = true; + + private void Toggle() => disabled = !disabled; +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_02_Disable_B.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_02_Disable_B.razor new file mode 100644 index 000000000..367c8bbc8 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_02_Disable_B.razor @@ -0,0 +1,17 @@ +<div class="mb-3"> + <PasswordInput @ref="passwordInputRef" @bind-Value="@enteredPassword" /> +</div> +<div class="mb-3">Entered text: @enteredPassword</div> + +<Button Color="ButtonColor.Secondary" @onclick="Disable"> Disable </Button> +<Button Color="ButtonColor.Primary" @onclick="Enable"> Enable </Button> + +@code { + private PasswordInput? passwordInputRef; + + private string? enteredPassword = null; + + private void Disable() => passwordInputRef.Disable(); + + private void Enable() => passwordInputRef.Enable(); +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_03_Validations.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_03_Validations.razor new file mode 100644 index 000000000..92dd7d08b --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_03_Validations.razor @@ -0,0 +1,80 @@ +@using System.ComponentModel.DataAnnotations + +<style> + .valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; + } + + .invalid { + outline: 1px solid red; + } + + .validation-message { + color: red; + } +</style> + +<EditForm EditContext="@editContext" OnValidSubmit="HandleOnValidSubmit"> + <DataAnnotationsValidator /> + + <div class="form-group row mb-3"> + <label class="col-md-2 col-form-label">User name: <span class="text-danger">*</span></label> + <div class="col-md-10"> + <TextInput @bind-Value="@userLogin.UserName" Placeholder="Enter user name" /> + <ValidationMessage For="@(() => userLogin.UserName)" /> + </div> + </div> + + <div class="form-group row mb-3"> + <label class="col-md-2 col-form-label">Password: <span class="text-danger">*</span></label> + <div class="col-md-10"> + <PasswordInput @bind-Value="@userLogin.Password" /> + <ValidationMessage For="@(() => userLogin.Password)" /> + </div> + </div> + + <div class="row"> + <div class="col-md-12 text-right"> + <Button Type="ButtonType.Button" Color="ButtonColor.Secondary" Class="float-end" @onclick="ResetForm">Reset</Button> + <Button Type="ButtonType.Submit" Color="ButtonColor.Success" Class="float-end me-2">Login</Button> + </div> + </div> + +</EditForm> + +@code { + private UserLogin userLogin = new(); + private EditContext? editContext; + + protected override void OnInitialized() + { + editContext = new EditContext(userLogin); + base.OnInitialized(); + } + + public void HandleOnValidSubmit() + { + // additional check + if (editContext.Validate()) + { + // do something + // submit the form + Console.WriteLine("Login successful"); + } + } + + private void ResetForm() + { + userLogin = new(); + editContext = new EditContext(userLogin); + } + + public class UserLogin + { + [Required(ErrorMessage = "User name required.")] + public string? UserName { get; set; } + + [Required(ErrorMessage = "Password required.")] + public string? Password { get; set; } + } +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_04_Events_ValueChanged.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_04_Events_ValueChanged.razor new file mode 100644 index 000000000..1aab225aa --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Form/PasswordInput/PasswordInput_Demo_04_Events_ValueChanged.razor @@ -0,0 +1,15 @@ +<div class="mb-3"> + <PasswordInput Value="@enteredPassword" ValueExpression="() => enteredPassword" ValueChanged="(value) => PasswordChanged(value)" /> +</div> +<div class="mb-3">Entered password: @enteredPassword</div> + +@code { + private string? enteredPassword = null; + + private void PasswordChanged(string? value) + { + enteredPassword = value; + + // do something + } +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor index feb8706c4..17dc506dc 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor @@ -150,6 +150,11 @@ <h4 class="mb-0 fs-5 fw-semibold"><Icon Name="IconName.InputCursor" class="me-2" /> Number Input</h4> </a> </div> + <div class="col-sm-4 mb-2"> + <a class="d-block pe-lg-4 text-decoration-none lh-sm" href="/form/password-input"> + <h4 class="mb-0 fs-5 fw-semibold"><Icon Name="IconName.EyeSlashFill" class="me-2" /> Password Input <Badge Color="BadgeColor.Danger">New</Badge></h4> + </a> + </div> <div class="col-sm-4 mb-2"> <a class="d-block pe-lg-4 text-decoration-none lh-sm" href="/offcanvas"> <h4 class="mb-0 fs-5 fw-semibold"><Icon Name="IconName.LayoutSidebarReverse" class="me-2" /> Offcanvas</h4> @@ -299,6 +304,11 @@ <h4 class="mb-0 fs-5 fw-semibold"><Icon Name="IconName.InputCursor" class="me-2" /> Number Input</h4> </a> </div> + <div class="col-sm-4 mb-2"> + <a class="d-block pe-lg-4 text-decoration-none lh-sm" href="/form/password-input"> + <h4 class="mb-0 fs-5 fw-semibold"><Icon Name="IconName.EyeSlashFill" class="me-2" /> Password Input <Badge Color="BadgeColor.Danger">New</Badge></h4> + </a> + </div> <div class="col-sm-4 mb-2"> <a class="d-block pe-lg-4 text-decoration-none lh-sm" href="@RouteConstants.Demos_RadioInput_Documentation"> <h4 class="mb-0 fs-5 fw-semibold"><Icon Name="IconName.RecordCircle" class="me-2" /> Radio Input <Badge Color="BadgeColor.Danger">New</Badge></h4> diff --git a/BlazorBootstrap.Demo.RCL/Constants/RouteConstants.cs b/BlazorBootstrap.Demo.RCL/Constants/RouteConstants.cs index 874eb6775..b6459f054 100644 --- a/BlazorBootstrap.Demo.RCL/Constants/RouteConstants.cs +++ b/BlazorBootstrap.Demo.RCL/Constants/RouteConstants.cs @@ -26,6 +26,7 @@ public static class RouteConstants public const string Demos_CurrencyInput_Documentation = Demos_Forms_Prefix + "/currency-input"; public const string Demos_DateInput_Documentation = Demos_Forms_Prefix + "/date-input"; public const string Demos_NumberInput_Documentation = Demos_Forms_Prefix + "/number-input"; + public const string Demos_PasswordInput_Documentation = Demos_Forms_Prefix + "/password-input"; public const string Demos_RadioInput_Documentation = Demos_Forms_Prefix + "/radio-input"; public const string Demos_RangeInput_Documentation = Demos_Forms_Prefix + "/range-input"; public const string Demos_SelectInput_Documentation = Demos_Forms_Prefix + "/select-input"; diff --git a/blazorbootstrap/Components/Form/PasswordInput/PasswordInput.razor b/blazorbootstrap/Components/Form/PasswordInput/PasswordInput.razor index 4123d7373..8980ff17c 100644 --- a/blazorbootstrap/Components/Form/PasswordInput/PasswordInput.razor +++ b/blazorbootstrap/Components/Form/PasswordInput/PasswordInput.razor @@ -3,14 +3,17 @@ @preservewhitespace true <div class="input-group mb-3"> - <input - @ref="@Element" - type="@InputTextType" - id="@Id" - class="@BootstrapClass.FormControl" - disabled="@Disabled" - value="@Value" - @attributes="@AdditionalAttributes" - @onchange="OnChange"> - <button type="button" class="btn btn-primary btn-sm" @onclick="OnShowHidePasswordButtonClick"><i class="bi bi-eye-fill" /></button> + <input @ref="@Element" + type="@InputTextType" + id="@Id" + class="@ClassNames" + style="@StyleNames" + value="@Value" + disabled="@Disabled" + @attributes="@AdditionalAttributes" + @onchange="OnChange"> + + <button type="button" class="@ShowHidePasswordButtonCssClass" @onclick="ShowHidePassword"> + <i class="@ShowHidePasswordButtonIcon"></i> + </button> </div> diff --git a/blazorbootstrap/Components/Form/PasswordInput/PasswordInput.razor.cs b/blazorbootstrap/Components/Form/PasswordInput/PasswordInput.razor.cs index e69698788..24d050c3d 100644 --- a/blazorbootstrap/Components/Form/PasswordInput/PasswordInput.razor.cs +++ b/blazorbootstrap/Components/Form/PasswordInput/PasswordInput.razor.cs @@ -1,118 +1,124 @@ -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components; -using System.Linq.Expressions; +namespace BlazorBootstrap; -namespace BlazorBootstrap +public partial class PasswordInput : BlazorBootstrapComponentBase { - public partial class PasswordInput - { - #region Fields and Constants - - private FieldIdentifier fieldIdentifier; + #region Fields and Constants - private string? oldValue; + private FieldIdentifier fieldIdentifier; - #endregion + private string? oldValue; - #region Methods + private bool showPassword = false; - protected override async Task OnInitializedAsync() - { - oldValue = Value; + #endregion - AdditionalAttributes ??= new Dictionary<string, object>(); - - fieldIdentifier = FieldIdentifier.Create(ValueExpression); - - await base.OnInitializedAsync(); - } + #region Methods - protected override async Task OnParametersSetAsync() - { - if (oldValue != Value) - { - await ValueChanged.InvokeAsync(Value); - - EditContext?.NotifyFieldChanged(fieldIdentifier); + protected override async Task OnInitializedAsync() + { + oldValue = Value; - oldValue = Value; - } - } + AdditionalAttributes ??= new Dictionary<string, object>(); - public string InputTextType = "password"; + fieldIdentifier = FieldIdentifier.Create(ValueExpression); - void OnShowHidePasswordButtonClick() - { - if (this.InputTextType == "password") - this.InputTextType = "text"; - else - this.InputTextType = "password"; - } + await base.OnInitializedAsync(); + } - /// <summary> - /// Disables InputPassword. - /// </summary> - public void Disable() => Disabled = true; - - /// <summary> - /// Enables InputPassword. - /// </summary> - public void Enable() => Disabled = false; - - /// <summary> - /// This event is triggered only when the user changes the selection from the UI. - /// </summary> - /// <param name="args"></param> - private async Task OnChange(ChangeEventArgs args) + protected override async Task OnParametersSetAsync() + { + if (oldValue != Value) { - Value = args.Value?.ToString(); - await ValueChanged.InvokeAsync(Value); EditContext?.NotifyFieldChanged(fieldIdentifier); oldValue = Value; } + } - #endregion - - #region Properties, Indexers - - protected override string? ClassNames => - BuildClassNames(Class, - (BootstrapClass.FormControl, true)); - - /// <summary> - /// Gets or sets the disabled state. - /// </summary> - /// <remarks> - /// Default value is false. - /// </remarks> - [Parameter] - public bool Disabled { get; set; } - - [CascadingParameter] private EditContext EditContext { get; set; } = default!; - - private string fieldCssClasses => EditContext?.FieldCssClass(fieldIdentifier) ?? ""; - - - /// <summary> - /// Gets or sets the value. - /// </summary> - /// <remarks> - /// Default value is null. - /// </remarks> - [Parameter] - public string? Value { get; set; } = default!; + /// <summary> + /// Disables InputPassword. + /// </summary> + public void Disable() => Disabled = true; + + /// <summary> + /// Enables InputPassword. + /// </summary> + public void Enable() => Disabled = false; + + /// <summary> + /// This event is triggered only when the user changes the selection from the UI. + /// </summary> + /// <param name="args"></param> + private async Task OnChange(ChangeEventArgs args) + { + oldValue = Value; - /// <summary> - /// This event is fired when the inputpassword value changes. - /// </summary> - [Parameter] - public EventCallback<string?> ValueChanged { get; set; } = default!; + Value = args.Value?.ToString() ?? string.Empty; // object - [Parameter] public Expression<Func<string?>> ValueExpression { get; set; } = default!; + await ValueChanged.InvokeAsync(Value); - #endregion + EditContext?.NotifyFieldChanged(fieldIdentifier); } + + private void ShowHidePassword() => showPassword = !showPassword; + + #endregion + + #region Properties, Indexers + + protected override string? ClassNames => + BuildClassNames( + Class, + (BootstrapClass.FormControl, true), + (EditContext?.FieldCssClass(fieldIdentifier) ?? string.Empty, true) + ); + + /// <summary> + /// Gets or sets the disabled state. + /// </summary> + /// <remarks> + /// Default value is false. + /// </remarks> + [Parameter] + public bool Disabled { get; set; } + + /// <summary> + /// Gets the associated <see cref="Microsoft.AspNetCore.Components.Forms.EditContext" />. + /// </summary> + [CascadingParameter] + private EditContext EditContext { get; set; } = default!; + + private string InputTextType => showPassword ? "text" : "password"; + + /// <summary> + /// Gets or sets the show/hide password button CSS class. + /// </summary> + /// <remarks> + /// Default value is `btn btn-primary btn-sm`. + /// </remarks> + [Parameter] + public string? ShowHidePasswordButtonCssClass { get; set; } = "btn border-top border-end border-bottom border border-start-0"; //""btn btn-light border"; + + private string ShowHidePasswordButtonIcon => showPassword ? "bi bi-eye-fill" : "bi bi-eye-slash-fill"; + + /// <summary> + /// Gets or sets the value. + /// </summary> + /// <remarks> + /// Default value is null. + /// </remarks> + [Parameter] + public string? Value { get; set; } + + /// <summary> + /// This event is fired when the PasswordInput value changes. + /// </summary> + [Parameter] + public EventCallback<string?> ValueChanged { get; set; } + + [Parameter] public Expression<Func<string?>> ValueExpression { get; set; } = default!; + + #endregion } diff --git a/blazorbootstrap/Components/Form/RadioInput/RadioInput.razor.cs b/blazorbootstrap/Components/Form/RadioInput/RadioInput.razor.cs index 902ddf906..a5fc7f966 100644 --- a/blazorbootstrap/Components/Form/RadioInput/RadioInput.razor.cs +++ b/blazorbootstrap/Components/Form/RadioInput/RadioInput.razor.cs @@ -32,12 +32,11 @@ protected override void OnInitialized() private async Task OnChange(ChangeEventArgs e) { var oldValue = Value; + var newValue = string.Equals(e.Value?.ToString(), "on"); await ValueChanged.InvokeAsync(newValue); - Console.WriteLine($"Old value: {oldValue}, New value: {e.Value}"); - EditContext?.NotifyFieldChanged(fieldIdentifier); }