diff --git a/src/BootstrapBlazor.Server/Components/Samples/SelectTables.razor b/src/BootstrapBlazor.Server/Components/Samples/SelectTables.razor index d4441c599ee..cbbee8d624a 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/SelectTables.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/SelectTables.razor @@ -6,7 +6,10 @@

@Localizer["Intro"]

- +
+ @((MarkupString)Localizer["NormalDesc"].Value) +
+ diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 88533511ab6..04430971451 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -6567,6 +6567,7 @@ "Intro": "The dropdown box is a table used to display the selection requirements for complex types", "NormalTitle": "Basic usage", "NormalIntro": "Suitable for candidates with a relatively large amount of information, presenting information using Table", + "NormalDesc": "You can use IsClearable to control whether to display the clear button. The default value is false", "ColorTitle": "Color", "ColorIntro": "Change component border color by setting Color", "IsDisabledTitle": "Disabled", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index cb00161bb74..30571a9b950 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -6567,6 +6567,7 @@ "Intro": "下拉框为表格用于展示复杂类型的选择需求", "NormalTitle": "基本功能", "NormalIntro": "适用于候选项信息量比较大,用 Table 呈现信息量", + "NormalDesc": "可通过 IsClearable 控制是否显示清除小按钮,默认值 false", "ColorTitle": "颜色", "ColorIntro": "通过设置 Color 改变组件边框颜色", "IsDisabledTitle": "禁用", diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 6321aca3961..0ea84b0f811 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@ - 9.2.6 + 9.2.7-beta01 diff --git a/src/BootstrapBlazor/Components/Select/Select.razor.scss b/src/BootstrapBlazor/Components/Select/Select.razor.scss index c5fd9966955..e13e4a08a6d 100644 --- a/src/BootstrapBlazor/Components/Select/Select.razor.scss +++ b/src/BootstrapBlazor/Components/Select/Select.razor.scss @@ -147,25 +147,27 @@ transform: rotate(0); } -.select .clear-icon { - position: absolute; - height: 100%; - width: var(--bb-select-append-width); - right: 0; - top: 0; - color: var(--bb-select-append-color); - align-items: center; - justify-content: center; - cursor: pointer; - display: none; -} +.select { + .clear-icon { + position: absolute; + height: 100%; + width: var(--bb-select-append-width); + right: 0; + top: 0; + color: var(--bb-select-append-color); + align-items: center; + justify-content: center; + cursor: pointer; + display: none; + } -.select:hover .clear-icon { - display: flex; -} + &:hover .clear-icon { + display: flex; + } -.select.cls:hover .form-select-append { - display: none; + &.cls:hover .form-select-append { + display: none; + } } .form-select.is-valid:focus, diff --git a/src/BootstrapBlazor/Components/Select/SelectTable.razor b/src/BootstrapBlazor/Components/Select/SelectTable.razor index 18c4a975094..4b795476538 100644 --- a/src/BootstrapBlazor/Components/Select/SelectTable.razor +++ b/src/BootstrapBlazor/Components/Select/SelectTable.razor @@ -31,6 +31,10 @@ } + @if (GetClearable()) + { + + } @TableColumns?.Invoke(new TItem()) diff --git a/src/BootstrapBlazor/Components/Select/SelectTable.razor.cs b/src/BootstrapBlazor/Components/Select/SelectTable.razor.cs index 1a537b1380d..b870da27d9f 100644 --- a/src/BootstrapBlazor/Components/Select/SelectTable.razor.cs +++ b/src/BootstrapBlazor/Components/Select/SelectTable.razor.cs @@ -61,6 +61,12 @@ namespace BootstrapBlazor.Components; [NotNull] public string? DropdownIcon { get; set; } + /// + /// 获得/设置 是否可清除 默认 false + /// + [Parameter] + public bool IsClearable { get; set; } + /// /// 获得/设置 IIconTheme 服务实例 /// @@ -78,6 +84,7 @@ namespace BootstrapBlazor.Components; /// private string? ClassName => CssBuilder.Default("select select-table dropdown") .AddClass("disabled", IsDisabled) + .AddClass("cls", IsClearable) .AddClassFromAttributes(AdditionalAttributes) .Build(); @@ -101,6 +108,15 @@ namespace BootstrapBlazor.Components; .AddClass($"text-danger", IsValid.HasValue && !IsValid.Value) .Build(); + private bool GetClearable() => IsClearable && !IsDisabled; + + /// + /// 获得/设置 右侧清除图标 默认 fa-solid fa-angle-up + /// + [Parameter] + [NotNull] + public string? ClearIcon { get; set; } + /// /// 获得 PlaceHolder 属性 /// @@ -174,6 +190,12 @@ namespace BootstrapBlazor.Components; [Parameter] public bool AutoGenerateColumns { get; set; } + /// + /// 获得/设置 清除文本内容 OnClear 回调方法 默认 null + /// + [Parameter] + public Func? OnClearAsync { get; set; } + [Inject] [NotNull] private IStringLocalizer>? Localizer { get; set; } @@ -191,6 +213,12 @@ namespace BootstrapBlazor.Components; private string GetStyleString => $"height: {Height}px;"; + private string? ClearClassString => CssBuilder.Default("clear-icon") + .AddClass($"text-{Color.ToDescriptionString()}", Color != Color.None) + .AddClass($"text-success", IsValid.HasValue && IsValid.Value) + .AddClass($"text-danger", IsValid.HasValue && !IsValid.Value) + .Build(); + /// /// /// @@ -220,6 +248,7 @@ protected override void OnParametersSet() PlaceHolder ??= Localizer[nameof(PlaceHolder)]; DropdownIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectDropdownIcon); + ClearIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectClearIcon); } /// @@ -239,4 +268,14 @@ private async Task OnClickRowCallback(TItem item) CurrentValue = item; await InvokeVoidAsync("close", Id); } + + private async Task OnClearValue() + { + if (OnClearAsync != null) + { + await OnClearAsync(); + } + + await OnClickRowCallback(default!); + } } diff --git a/test/UnitTest/Components/SelectTableTest.cs b/test/UnitTest/Components/SelectTableTest.cs index 5577ad910db..7fe6564b7cb 100644 --- a/test/UnitTest/Components/SelectTableTest.cs +++ b/test/UnitTest/Components/SelectTableTest.cs @@ -36,6 +36,44 @@ public void Items_Ok() }); } + [Fact] + public async Task IsClearable_Ok() + { + var localizer = Context.Services.GetRequiredService>(); + var items = Foo.GenerateFoo(localizer, 4); + var cut = Context.RenderComponent(pb => + { + pb.AddChildContent>(pb => + { + pb.Add(a => a.OnQueryAsync, options => OnFilterQueryAsync(options, items)); + pb.Add(a => a.GetTextCallback, foo => foo.Name); + }); + }); + var table = cut.FindComponent>(); + Assert.DoesNotContain("clear-icon", table.Markup); + + var isClear = false; + table.SetParametersAndRender(pb => + { + pb.Add(a => a.IsClearable, true); + pb.Add(a => a.Value, items[0]); + pb.Add(a => a.OnClearAsync, () => + { + isClear = true; + return Task.CompletedTask; + }); + }); + Assert.Contains("clear-icon", table.Markup); + var input = table.Find(".form-select"); + Assert.Equal("张三 0001", input.GetAttribute("value")); + + var span = table.Find(".clear-icon"); + await table.InvokeAsync(() => span.Click()); + input = table.Find(".form-select"); + Assert.Null(input.GetAttribute("value")); + Assert.True(isClear); + } + [Fact] public void TableMinWidth_Ok() { @@ -65,9 +103,13 @@ public void Color_Ok() pb.Add(a => a.Color, Color.Danger); pb.Add(a => a.GetTextCallback, foo => foo.Name); pb.Add(a => a.OnQueryAsync, options => OnFilterQueryAsync(options, items)); + pb.Add(a => a.IsClearable, true); }); }); cut.Contains("border-danger"); + + var span = cut.Find(".clear-icon"); + Assert.True(span.ClassList.Contains("text-danger")); } [Fact] @@ -271,6 +313,7 @@ public async Task Validate_Ok() builder.Add(a => a.Model, model); builder.AddChildContent>(pb => { + pb.Add(a => a.IsClearable, true); pb.Add(a => a.Value, model.Foo); pb.Add(a => a.ValueExpression, Utility.GenerateValueExpression(model, "Foo", typeof(Foo))); pb.Add(a => a.OnValueChanged, v => @@ -302,6 +345,9 @@ await cut.InvokeAsync(() => }); Assert.True(valid); + var span = cut.Find(".clear-icon"); + Assert.True(span.ClassList.Contains("text-success")); + model.Foo = null; var table = cut.FindComponent>(); table.SetParametersAndRender(); @@ -311,6 +357,9 @@ await cut.InvokeAsync(() => form.Submit(); }); Assert.True(invalid); + + span = cut.Find(".clear-icon"); + Assert.True(span.ClassList.Contains("text-danger")); } [Fact]