diff --git a/src/BootstrapBlazor.Server/Components/Layout/ComponentLayout.razor b/src/BootstrapBlazor.Server/Components/Layout/ComponentLayout.razor
index 2c6e81b35e4..3fa93db5737 100644
--- a/src/BootstrapBlazor.Server/Components/Layout/ComponentLayout.razor
+++ b/src/BootstrapBlazor.Server/Components/Layout/ComponentLayout.razor
@@ -30,7 +30,7 @@
-
+
@Body
diff --git a/src/BootstrapBlazor.Server/Components/Samples/Tabs.razor b/src/BootstrapBlazor.Server/Components/Samples/Tabs.razor
index de702f52368..486ea83e219 100644
--- a/src/BootstrapBlazor.Server/Components/Samples/Tabs.razor
+++ b/src/BootstrapBlazor.Server/Components/Samples/Tabs.razor
@@ -436,6 +436,23 @@ private void Navigation()
+
+
+
+ @Localizer["TabItem1Content"]
+
+
+ @Localizer["TabItem2Content"]
+
+
+ @Localizer["TabItem3Content"]
+
+
+ @Localizer["TabItem4Content"]
+
+
+
+
diff --git a/src/BootstrapBlazor.Server/Components/Samples/Tabs.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Tabs.razor.cs
index ad0babd0d43..f2931ce1af5 100644
--- a/src/BootstrapBlazor.Server/Components/Samples/Tabs.razor.cs
+++ b/src/BootstrapBlazor.Server/Components/Samples/Tabs.razor.cs
@@ -204,7 +204,7 @@ private AttributeItem[] GetAttributes() =>
new()
{
Name = "IsLazyLoadTabItem",
- Description = Localizer["AttributeIsLazyLoadTabItem"].Value,
+ Description = Localizer["TabAttIsLazyLoadTabItem"].Value,
Type = "boolean",
ValueList = "true/false",
DefaultValue = "false"
@@ -222,21 +222,21 @@ private AttributeItem[] GetAttributes() =>
Name = "ShowExtendButtons",
Description = Localizer["TabAtt5ShowExtendButtons"].Value,
Type = "boolean",
- ValueList = " — ",
+ ValueList = "true|false",
DefaultValue = "false"
},
new()
{
- Name = "ShowExtendButtons",
- Description = Localizer["TabAttrShowNavigatorButtons"].Value,
+ Name = "ShowNavigatorButtons",
+ Description = Localizer["TabAttShowNavigatorButtons"].Value,
Type = "boolean",
ValueList = "true|false",
DefaultValue = "true"
},
new()
{
- Name = "ShowExtendButtons",
- Description = Localizer["TabAttrShowActiveBar"].Value,
+ Name = "ShowActiveBar",
+ Description = Localizer["TabAttShowActiveBar"].Value,
Type = "boolean",
ValueList = "true|false",
DefaultValue = "true"
@@ -250,6 +250,14 @@ private AttributeItem[] GetAttributes() =>
DefaultValue = "false"
},
new()
+ {
+ Name = "TabStyle",
+ Description = Localizer["TabAtt2TabStyle"].Value,
+ Type = "TabStyle",
+ ValueList = "Default|Chrome",
+ DefaultValue = "Default"
+ },
+ new()
{
Name = "Placement",
Description = Localizer["TabAtt7Placement"].Value,
diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json
index 94e5b78975b..983ebb04f82 100644
--- a/src/BootstrapBlazor.Server/Locales/en-US.json
+++ b/src/BootstrapBlazor.Server/Locales/en-US.json
@@ -2083,6 +2083,7 @@
"TabAtt5ShowExtendButtons": "Whether to display the extension button",
"TabAttShowNavigatorButtons": "Whether to display the previous and next navigation buttons",
"TabAttShowActiveBar": "Whether to display active bar",
+ "TabAttIsLazyLoadTabItem": "Whether lazy load tab item",
"TabAtt6ClickTabToNavigation": "Whether to navigate when you click on the title",
"TabAtt7Placement": "Set the label position",
"TabAtt8Height": "Set the label height",
@@ -2116,7 +2117,10 @@
"AttributeExcludeUrls": "Exclude address support for wildcards",
"AttributeButtonTemplate": "The template for Buttons",
"TabsDisabledTitle": "Disabled",
- "TabsDisabledIntro": "Disable the current TabItem by setting IsDisabled=\"true\" to prohibit click, drag, close etc."
+ "TabsDisabledIntro": "Disable the current TabItem by setting IsDisabled=\"true\" to prohibit click, drag, close etc.",
+ "TabsChromeStyleTitle": "Chrome Style",
+ "TabsChromeStyleIntro": "Set the Chrome browser tab style by setting TabStyle=\"TabStyle.Chrome\". Currently only supports Placement=\"Placement.Top\" mode",
+ "TabAtt2TabStyle": "Set the tab style"
},
"BootstrapBlazor.Server.Components.Components.DemoTabItem": {
"Info": "Reset the title of this TabItem by click the button",
diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json
index 9fe32229fa2..d7e6392ffb6 100644
--- a/src/BootstrapBlazor.Server/Locales/zh-CN.json
+++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json
@@ -2083,6 +2083,7 @@
"TabAtt5ShowExtendButtons": "是否显示扩展按钮",
"TabAttShowNavigatorButtons": "是否显示前后导航按钮",
"TabAttShowActiveBar": "是否显示活动标签",
+ "TabAttIsLazyLoadTabItem": "是否延时加载标签内容",
"TabAtt6ClickTabToNavigation": "点击标题时是否导航",
"TabAtt7Placement": "设置标签位置",
"TabAtt8Height": "设置标签高度",
@@ -2116,7 +2117,10 @@
"AttributeExcludeUrls": "排除地址支持通配符",
"AttributeButtonTemplate": "按钮模板",
"TabsDisabledTitle": "禁用",
- "TabsDisabledIntro": "通过设置 IsDisabled=\"true\" 禁用当前 TabItem 禁止点击、拖动、关闭等操作"
+ "TabsDisabledIntro": "通过设置 IsDisabled=\"true\" 禁用当前 TabItem 禁止点击、拖动、关闭等操作",
+ "TabsChromeStyleTitle": "Chrome 样式",
+ "TabsChromeStyleIntro": "通过设置 TabStyle=\"TabStyle.Chrome\" 设置 Chrome 浏览器标签页样式,目前仅支持 Placement=\"Placement.Top\" 模式",
+ "TabAtt2TabStyle": "设置标签页样式"
},
"BootstrapBlazor.Server.Components.Components.DemoTabItem": {
"Info": "点击下方按钮,本 TabItem 标题更改为当前分钟与秒",
diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor b/src/BootstrapBlazor/Components/Tab/Tab.razor
index e17ffd6b6cd..16600bc649d 100644
--- a/src/BootstrapBlazor/Components/Tab/Tab.razor
+++ b/src/BootstrapBlazor/Components/Tab/Tab.razor
@@ -45,39 +45,17 @@ else
}
@foreach (var item in Items)
{
- if (item.HeaderTemplate != null)
+ @if (item.HeaderTemplate != null)
{
@item.HeaderTemplate(item)
}
else if (item.IsDisabled)
{
-
- @if (!string.IsNullOrEmpty(item.Icon))
- {
-
- }
- @item.Text
-
+ @RenderDisabledHeaderByStyle(item)
}
else
{
- OnClickTabItem(item))" @onclick:preventDefault="@(!ClickTabToNavigation)" draggable="@DraggableString">
- @if (!string.IsNullOrEmpty(item.Icon))
- {
-
- }
- @item.Text
- @if (ShowFullScreen && item.ShowFullScreen)
- {
-
- }
- @if (ShowClose && item.Closable)
- {
- RemoveTab(item)">
-
-
- }
-
+ @RenderHeaderByStyle(item)
}
}
@if (IsCard || IsBorderCard)
@@ -145,4 +123,54 @@ else
@
@RenderTabItemContent(item)
;
+
+ RenderFragment RenderChromeDisabledHeader(TabItem item) =>
+ @
+
+ @RenderHeaderContent(item)
+
+
+
+
;
+
+ RenderFragment RenderDefaultDisabledHeader(TabItem item) =>
+ @
+ @RenderHeaderContent(item)
+
;
+
+ RenderFragment RenderChromeHeader(TabItem item) =>
+ @;
+
+ RenderFragment RenderDefaultHeader(TabItem item) =>
+ @ OnClickTabItem(item))" @onclick:preventDefault="@(!ClickTabToNavigation)" draggable="@DraggableString">
+ @RenderHeaderContent(item)
+ ;
+
+ RenderFragment RenderHeaderContent(TabItem item) =>
+ @
+ @if (!string.IsNullOrEmpty(item.Icon))
+ {
+
+ }
+ @item.Text
+ @if (!item.IsDisabled)
+ {
+ @if (ShowFullScreen && item.ShowFullScreen)
+ {
+
+ }
+ @if (ShowClose && item.Closable)
+ {
+ RemoveTab(item)">
+
+
+ }
+ }
+
;
}
diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs
index e3db268a989..87617adb630 100644
--- a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs
+++ b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs
@@ -24,8 +24,12 @@ public partial class Tab : IHandlerException
.AddClass("extend", ShouldShowExtendButtons())
.Build();
+ private static string? GetItemWrapClassString(TabItem item) => CssBuilder.Default("tabs-item-wrap")
+ .AddClass("active", item.IsActive && !item.IsDisabled)
+ .Build();
+
private string? GetClassString(TabItem item) => CssBuilder.Default("tabs-item")
- .AddClass("active", item.IsActive)
+ .AddClass("active", TabStyle == TabStyle.Default && item.IsActive && !item.IsDisabled)
.AddClass("disabled", item.IsDisabled)
.AddClass(item.CssClass)
.AddClass("is-closeable", ShowClose)
@@ -39,7 +43,8 @@ public partial class Tab : IHandlerException
.AddClass("tabs-card", IsCard)
.AddClass("tabs-border-card", IsBorderCard)
.AddClass($"tabs-{Placement.ToDescriptionString()}", Placement == Placement.Top || Placement == Placement.Right || Placement == Placement.Bottom || Placement == Placement.Left)
- .AddClass($"tabs-vertical", Placement == Placement.Left || Placement == Placement.Right)
+ .AddClass("tabs-vertical", Placement == Placement.Left || Placement == Placement.Right)
+ .AddClass("tabs-chrome", TabStyle == TabStyle.Chrome)
.AddClassFromAttributes(AdditionalAttributes)
.Build();
@@ -283,6 +288,12 @@ public partial class Tab : IHandlerException
[Parameter]
public Func? OnDragItemEndAsync { get; set; }
+ ///
+ /// Gets or sets the tab style. Default is .
+ ///
+ [Parameter]
+ public TabStyle TabStyle { get; set; }
+
[CascadingParameter]
private Layout? Layout { get; set; }
@@ -352,6 +363,11 @@ protected override void OnParametersSet()
AdditionalAssemblies ??= new[] { Assembly.GetEntryAssembly()! };
+ if (Placement != Placement.Top && TabStyle == TabStyle.Chrome)
+ {
+ TabStyle = TabStyle.Default;
+ }
+
if (ClickTabToNavigation)
{
if (!HandlerNavigation)
@@ -783,6 +799,14 @@ private void ActiveTabItem(TabItem item)
public void SetDisabledItem(TabItem item, bool disabled)
{
item.SetDisabledWithoutRender(disabled);
+ if (disabled)
+ {
+ item.SetActive(false);
+ }
+ if (TabItems.Any(i => i.IsActive) == false)
+ {
+ TabItems.Where(i => !i.IsDisabled).FirstOrDefault()?.SetActive(true);
+ }
StateHasChanged();
}
@@ -854,6 +878,10 @@ public async Task DragItemCallback(int originIndex, int currentIndex)
private string? GetIdByTabItem(TabItem item) => (ShowFullScreen && item.ShowFullScreen) ? ComponentIdGenerator.Generate(item) : null;
+ private RenderFragment RenderDisabledHeaderByStyle(TabItem item) => TabStyle == TabStyle.Chrome ? RenderChromeDisabledHeader(item) : RenderDefaultDisabledHeader(item);
+
+ private RenderFragment RenderHeaderByStyle(TabItem item) => TabStyle == TabStyle.Chrome ? RenderChromeHeader(item) : RenderDefaultHeader(item);
+
///
///
///
diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor.scss b/src/BootstrapBlazor/Components/Tab/Tab.razor.scss
index 087a5eb29da..0eeeb16514c 100644
--- a/src/BootstrapBlazor/Components/Tab/Tab.razor.scss
+++ b/src/BootstrapBlazor/Components/Tab/Tab.razor.scss
@@ -487,3 +487,119 @@
background-color: var(--bs-primary);
}
}
+
+.tabs-chrome {
+ --bb-tabs-header-bg-color: var(--bs-border-color);
+ --bb-tabs-item-hover-bg-color: rgba(var(--bs-body-color-rgb), 0.1);
+ --bb-tabs-item-active-bg-color: var(--bs-body-bg);
+ --bb-tabs-item-active-color: var(--bs-body-color);
+ --bb-tabs-item-hover-color: var(--bs-body-color);
+
+ .tabs-item-wrap {
+ overflow: hidden;
+ position: relative;
+ background-color: var(--bb-tabs-header-bg-color);
+ display: flex;
+ align-items: flex-end;
+ flex-shrink: 0;
+ padding: 0 1rem;
+ z-index: 1;
+
+ &.active {
+ z-index: 5;
+
+ .tab-corner {
+ background-color: var(--bs-body-bg);
+ }
+
+ .tabs-item {
+ background-color: var(--bb-tabs-item-active-bg-color);
+ }
+ }
+
+ &:not(.active) {
+ .tabs-item:not(.disabled) .tabs-item-body {
+ &:hover {
+ border-radius: 20px;
+ background-color: var(--bb-tabs-item-hover-bg-color);
+ }
+ }
+ }
+
+ &:not(:first-child) {
+ margin-left: -2rem;
+ }
+
+ .tabs-item {
+ background-color: var(--bb-tabs-header-bg-color);
+ border: none !important;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+ height: 36px !important;
+
+ .tabs-item-body {
+ padding: 4px 10px;
+ display: flex;
+ align-items: center;
+ flex-wrap: nowrap;
+ margin-bottom: 4px;
+
+ .tabs-item-text {
+ padding: 0 .5rem;
+ }
+
+ .tabs-item-close {
+ display: flex;
+ position: unset;
+ width: 21px;
+ height: 21px;
+ border-radius: 50%;
+ }
+ }
+ }
+
+ .tab-corner {
+ height: 2rem;
+ width: 2rem;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+
+ &::after {
+ content: '';
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ background-color: var(--bb-tabs-header-bg-color);
+ }
+ }
+
+ .tab-corner-left {
+ bottom: 0;
+ left: -1rem;
+
+ &::after {
+ border-bottom-right-radius: 50%;
+ }
+ }
+
+ .tab-corner-right {
+ bottom: 0;
+ right: -1rem;
+
+ &::after {
+ border-bottom-left-radius: 50%;
+ }
+ }
+
+ .btn-fs {
+ padding: 0;
+ }
+ }
+
+ > .tabs-header .tabs-item-fix {
+ background-color: var(--bb-tabs-header-bg-color);
+ border: none;
+ }
+}
diff --git a/src/BootstrapBlazor/Enums/TabStyle.cs b/src/BootstrapBlazor/Enums/TabStyle.cs
new file mode 100644
index 00000000000..8b49a7bfe53
--- /dev/null
+++ b/src/BootstrapBlazor/Enums/TabStyle.cs
@@ -0,0 +1,22 @@
+// 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;
+
+///
+/// Tab style emnu
+///
+public enum TabStyle
+{
+ ///
+ /// The default style
+ ///
+ Default,
+
+ ///
+ /// The Chrome style
+ ///
+ Chrome
+}
diff --git a/test/UnitTest/Components/TabTest.cs b/test/UnitTest/Components/TabTest.cs
index 7f5c782871e..3f1783e941b 100644
--- a/test/UnitTest/Components/TabTest.cs
+++ b/test/UnitTest/Components/TabTest.cs
@@ -406,13 +406,13 @@ public async Task IsDisabled_Ok()
pb.AddChildContent();
});
});
- Assert.Contains("Text1
", cut.Markup);
+ Assert.Contains("", cut.Markup);
var button = cut.FindComponent();
Assert.NotNull(button);
await cut.InvokeAsync(() => button.Instance.OnDisabledTabItem());
- Assert.Contains("Text2
", cut.Markup);
+ Assert.Contains("", cut.Markup);
}
[Fact]
@@ -422,6 +422,54 @@ public void SetDisabled_Ok()
cut.Instance.SetDisabled(true);
}
+ [Fact]
+ public async Task TabStyle_Chrome_Ok()
+ {
+ var clicked = false;
+ var cut = Context.RenderComponent(pb =>
+ {
+ pb.Add(a => a.TabStyle, TabStyle.Chrome);
+ pb.Add(a => a.OnClickTabItemAsync, item =>
+ {
+ clicked = true;
+ return Task.CompletedTask;
+ });
+ pb.AddChildContent(pb =>
+ {
+ pb.Add(a => a.Text, "Text1");
+ pb.Add(a => a.ChildContent, builder => builder.AddContent(0, "Test1"));
+ pb.Add(a => a.Icon, "fa fa-fa");
+ });
+ pb.AddChildContent(pb =>
+ {
+ pb.Add(a => a.IsActive, true);
+ pb.Add(a => a.Text, "Text2");
+ pb.AddChildContent();
+ });
+ });
+ cut.Contains("tabs tabs-top tabs-chrome");
+ cut.Contains("tabs-item-wrap active");
+ cut.Contains("");
+ cut.Contains("");
+
+ var button = cut.FindComponent();
+ Assert.NotNull(button);
+ await cut.InvokeAsync(() => button.Instance.OnDisabledTabItem());
+ cut.Contains("");
+
+ // trigger click
+ var link = cut.Find("a");
+ await cut.InvokeAsync(() => link.Click());
+ Assert.True(clicked);
+
+ // placement top and chrome style
+ cut.SetParametersAndRender(pb =>
+ {
+ pb.Add(a => a.Placement, Placement.Left);
+ });
+ Assert.Equal(TabStyle.Default, cut.Instance.TabStyle);
+ }
+
[Fact]
public void MenuItem_Null()
{