diff --git a/src/BootstrapBlazor.Server/Components/Samples/Buttons.razor b/src/BootstrapBlazor.Server/Components/Samples/Buttons.razor
index a05b93c438b..d27622df83e 100644
--- a/src/BootstrapBlazor.Server/Components/Samples/Buttons.razor
+++ b/src/BootstrapBlazor.Server/Components/Samples/Buttons.razor
@@ -152,6 +152,10 @@
+
+
+
+
diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json
index 6ed2b91ae0e..40a61e31d2c 100644
--- a/src/BootstrapBlazor.Server/Locales/en-US.json
+++ b/src/BootstrapBlazor.Server/Locales/en-US.json
@@ -2211,7 +2211,9 @@
"TooltipText": "Button",
"TooltipTitle": "Tooltip",
"TooltipIntro": "Set TooltipText TooltipPlacement TooltipTrigger shortcut button prompt bar information, position, and triggering method. For more functions, please use the Tooltip component to implement. In this example, the second button is in the disabled state, and the prompt bar is still available",
- "TooltipDisabledText": "Disabled"
+ "TooltipDisabledText": "Disabled",
+ "ToggleButton": "Toggle",
+ "ToggleIntroduction": "Get the current button Toggle state by setting the IsActiveChanged or OnToggleAsync callback method"
},
"BootstrapBlazor.Server.Components.Samples.PulseButtons": {
"Block1Title": "Basic usage",
diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json
index aa6be40dab3..a6b81af3885 100644
--- a/src/BootstrapBlazor.Server/Locales/zh-CN.json
+++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json
@@ -2211,7 +2211,9 @@
"TooltipText": "按钮",
"TooltipTitle": "按钮提示栏",
"TooltipIntro": "通过设置 TooltipText TooltipPlacement TooltipTrigger 快捷设置按钮提示栏信息、位置、触发方式,更多功能请使用 Tooltip 组件实现,本例中第二个按钮是 禁用 状态,提示栏仍然可用",
- "TooltipDisabledText": "禁用按钮"
+ "TooltipDisabledText": "禁用按钮",
+ "ToggleButton": "状态切换按钮",
+ "ToggleIntroduction": "通过设置 IsActiveChanged 或者 OnToggleAsync 回调方法获得当前按钮 Toggle 状态"
},
"BootstrapBlazor.Server.Components.Samples.PulseButtons": {
"Block1Title": "基础用法",
diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj
index c4d91628639..e1f6e6bd936 100644
--- a/src/BootstrapBlazor/BootstrapBlazor.csproj
+++ b/src/BootstrapBlazor/BootstrapBlazor.csproj
@@ -1,7 +1,7 @@
- 9.9.1-beta04
+ 9.9.1-beta05
diff --git a/src/BootstrapBlazor/Components/Button/ToggleButton.razor b/src/BootstrapBlazor/Components/Button/ToggleButton.razor
new file mode 100644
index 00000000000..adfd361ccc4
--- /dev/null
+++ b/src/BootstrapBlazor/Components/Button/ToggleButton.razor
@@ -0,0 +1,23 @@
+@namespace BootstrapBlazor.Components
+@inherits ButtonBase
+
+
diff --git a/src/BootstrapBlazor/Components/Button/ToggleButton.razor.cs b/src/BootstrapBlazor/Components/Button/ToggleButton.razor.cs
new file mode 100644
index 00000000000..3400c7f1246
--- /dev/null
+++ b/src/BootstrapBlazor/Components/Button/ToggleButton.razor.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+namespace BootstrapBlazor.Components;
+
+///
+/// Toggle Button 按钮组件
+///
+public partial class ToggleButton
+{
+ ///
+ /// 获得/设置 状态切换回调方法
+ ///
+ [Parameter]
+ public Func? OnToggleAsync { get; set; }
+
+ ///
+ /// 获得/设置 当前状态是否为激活状态 默认 false
+ ///
+ [Parameter]
+ public bool IsActive { get; set; }
+
+ ///
+ /// 获得/设置 激活状态回调方法
+ ///
+ [Parameter]
+ public EventCallback IsActiveChanged { get; set; }
+
+ private string? ToggleClassName => CssBuilder.Default(ClassName)
+ .AddClass("active", IsActive)
+ .Build();
+
+ private async Task OnClickButton()
+ {
+ if (IsAsync)
+ {
+ IsAsyncLoading = true;
+ IsDisabled = true;
+ }
+
+ await HandlerClick();
+
+ // 恢复按钮
+ if (IsAsync)
+ {
+ IsDisabled = IsKeepDisabled;
+ IsAsyncLoading = false;
+ }
+ }
+
+ private async Task HandlerClick()
+ {
+ IsActive = !IsActive;
+ if (OnClickWithoutRender != null)
+ {
+ if (!IsAsync)
+ {
+ IsNotRender = true;
+ }
+
+ await OnClickWithoutRender();
+ }
+
+ if (OnClick.HasDelegate)
+ {
+ await OnClick.InvokeAsync();
+ }
+
+ if (IsActiveChanged.HasDelegate)
+ {
+ await IsActiveChanged.InvokeAsync(IsActive);
+ }
+
+ if (OnToggleAsync != null)
+ {
+ await OnToggleAsync(IsActive);
+ }
+ }
+}
diff --git a/test/UnitTest/Components/ButtonTest.cs b/test/UnitTest/Components/ButtonTest.cs
index 3f1b3c6bfad..05b26f83dec 100644
--- a/test/UnitTest/Components/ButtonTest.cs
+++ b/test/UnitTest/Components/ButtonTest.cs
@@ -452,4 +452,45 @@ public void ShareButton_Ok()
Assert.Equal("test-share-title", cut.Instance.ShareContext?.Title);
Assert.Equal("www.blazor.zone", cut.Instance.ShareContext?.Url);
}
+
+ [Fact]
+ public async Task ToogleButton_Ok()
+ {
+ var active = false;
+ var bindActive = false;
+ var clickWithoutRender = false;
+ var clicked = false;
+ var tcs = new TaskCompletionSource();
+ var cut = Context.RenderComponent(pb =>
+ {
+ pb.Add(a => a.IsActive, false);
+ pb.Add(a => a.IsActiveChanged, EventCallback.Factory.Create(this, b =>
+ {
+ active = b;
+ bindActive = true;
+ }));
+ pb.Add(a => a.OnClickWithoutRender, () =>
+ {
+ clickWithoutRender = true;
+ return Task.CompletedTask;
+ });
+ pb.Add(a => a.OnClick, () =>
+ {
+ clicked = true;
+ return Task.CompletedTask;
+ });
+ pb.Add(a => a.OnToggleAsync, async isActive =>
+ {
+ await Task.Delay(10);
+ active = isActive;
+ tcs.TrySetResult();
+ });
+ });
+ var button = cut.Find("button");
+ await cut.InvokeAsync(() => button.Click());
+ await tcs.Task;
+ Assert.True(active);
+ Assert.True(clickWithoutRender);
+ Assert.True(clicked);
+ }
}
diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs
index 82cf8ecef31..d0db643bf8d 100644
--- a/test/UnitTest/Components/TableTest.cs
+++ b/test/UnitTest/Components/TableTest.cs
@@ -8720,6 +8720,27 @@ public void Modify_Ok()
Assert.True(ProhibitDelete(cut.Instance));
}
+ [Fact]
+ public void Table_Sortable()
+ {
+ var localizer = Context.Services.GetRequiredService>();
+ var cut = Context.RenderComponent>(pb =>
+ {
+ pb.AddCascadingValue(new SortableList());
+ pb.Add(a => a.TableColumns, foo => builder =>
+ {
+ builder.OpenComponent>(0);
+ builder.AddAttribute(1, "Field", "Name");
+ builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string)));
+ builder.CloseComponent();
+ });
+ pb.Add(a => a.RenderMode, TableRenderMode.Table);
+ pb.Add(a => a.Items, Foo.GenerateFoo(localizer));
+ });
+ }
+
+ class SortableList : ISortableList { }
+
static bool ProhibitEdit(Table @this)
{
var ret = false;