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) { - + @RenderDisabledHeaderByStyle(item) } else { - - @if (!string.IsNullOrEmpty(item.Icon)) - { - - } - @item.Text - @if (ShowFullScreen && item.ShowFullScreen) - { - - } - @if (ShowClose && item.Closable) - { - - - - } - + @RenderHeaderByStyle(item) } } @if (IsCard || IsBorderCard) @@ -145,4 +123,54 @@ else @ @RenderTabItemContent(item) ; + + RenderFragment RenderChromeDisabledHeader(TabItem item) => + @
+ + + +
; + + RenderFragment RenderDefaultDisabledHeader(TabItem item) => + @; + + RenderFragment RenderChromeHeader(TabItem item) => + @; + + RenderFragment RenderDefaultHeader(TabItem item) => + @ + @RenderHeaderContent(item) + ; + + RenderFragment RenderHeaderContent(TabItem item) => + @
+ @if (!string.IsNullOrEmpty(item.Icon)) + { + + } + @item.Text + @if (!item.IsDisabled) + { + @if (ShowFullScreen && item.ShowFullScreen) + { + + } + @if (ShowClose && item.Closable) + { + + + + } + } +
; } 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("
Text1
", cut.Markup); var button = cut.FindComponent(); Assert.NotNull(button); await cut.InvokeAsync(() => button.Instance.OnDisabledTabItem()); - Assert.Contains("
Text2
", cut.Markup); + Assert.Contains("
Text2
", 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("
Text2
"); + + // 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() {