Skip to content

feat(Tab): add TabStyle parameter #5640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 16, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/Tabs.razor
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,23 @@ private void Navigation()
</Tab>
</DemoBlock>

<DemoBlock Title="@Localizer["TabsChromeStyleTitle"]" Introduction="@Localizer["TabsChromeStyleIntro"]" Name="TabStyle">
<Tab IsCard="true" ShowClose="true" TabStyle="TabStyle.Chrome">
<TabItem Text="@Localizer["TabItem1Text"]" Icon="fa-solid fa-user">
<div>@Localizer["TabItem1Content"]</div>
</TabItem>
<TabItem Text="@Localizer["TabItem2Text"]" Icon="fa-solid fa-gauge-high">
<div>@Localizer["TabItem2Content"]</div>
</TabItem>
<TabItem Text="@Localizer["TabItem3Text"]" Icon="fa-solid fa-sitemap">
<div>@Localizer["TabItem3Content"]</div>
</TabItem>
<TabItem Text="@Localizer["TabItem4Text"]" Icon="fa-solid fa-building-columns">
<div>@Localizer["TabItem4Content"]</div>
</TabItem>
</Tab>
</DemoBlock>

<AttributeTable Items="@GetAttributes()" Title="@Localizer["AttTitle"]" />

<MethodTable Items="@GetMethods()" Title="@Localizer["MethodTitle"]" />
20 changes: 14 additions & 6 deletions src/BootstrapBlazor.Server/Components/Samples/Tabs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -2116,7 +2117,10 @@
"AttributeExcludeUrls": "Exclude address support for wildcards",
"AttributeButtonTemplate": "The template for Buttons",
"TabsDisabledTitle": "Disabled",
"TabsDisabledIntro": "Disable the current <code>TabItem</code> by setting <code>IsDisabled=\"true\"</code> to prohibit click, drag, close etc."
"TabsDisabledIntro": "Disable the current <code>TabItem</code> by setting <code>IsDisabled=\"true\"</code> to prohibit click, drag, close etc.",
"TabsChromeStyleTitle": "Chrome Style",
"TabsChromeStyleIntro": "Set the Chrome browser tab style by setting <code>TabStyle=\"TabStyle.Chrome\"</code>",
"TabAtt2TabStyle": "Set the tab style"
},
"BootstrapBlazor.Server.Components.Components.DemoTabItem": {
"Info": "Reset the title of this <code>TabItem</code> by click the button",
Expand Down
6 changes: 5 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2083,6 +2083,7 @@
"TabAtt5ShowExtendButtons": "是否显示扩展按钮",
"TabAttShowNavigatorButtons": "是否显示前后导航按钮",
"TabAttShowActiveBar": "是否显示活动标签",
"TabAttIsLazyLoadTabItem": "是否延时加载标签内容",
"TabAtt6ClickTabToNavigation": "点击标题时是否导航",
"TabAtt7Placement": "设置标签位置",
"TabAtt8Height": "设置标签高度",
Expand Down Expand Up @@ -2116,7 +2117,10 @@
"AttributeExcludeUrls": "排除地址支持通配符",
"AttributeButtonTemplate": "按钮模板",
"TabsDisabledTitle": "禁用",
"TabsDisabledIntro": "通过设置 <code>IsDisabled=\"true\"</code> 禁用当前 <code>TabItem</code> 禁止点击、拖动、关闭等操作"
"TabsDisabledIntro": "通过设置 <code>IsDisabled=\"true\"</code> 禁用当前 <code>TabItem</code> 禁止点击、拖动、关闭等操作",
"TabsChromeStyleTitle": "Chrome 样式",
"TabsChromeStyleIntro": "通过设置 <code>TabStyle=\"TabStyle.Chrome\"</code> 设置 Chrome 浏览器标签页样式",
"TabAtt2TabStyle": "设置标签页样式"
},
"BootstrapBlazor.Server.Components.Components.DemoTabItem": {
"Info": "点击下方按钮,本 <code>TabItem</code> 标题更改为当前分钟与秒",
Expand Down
78 changes: 53 additions & 25 deletions src/BootstrapBlazor/Components/Tab/Tab.razor
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
<div role="tab" class="@GetClassString(item)">
@if (!string.IsNullOrEmpty(item.Icon))
{
<i class="@GetIconClassString(item.Icon)"></i>
}
<span class="tabs-item-text">@item.Text</span>
</div>
@RenderDisabledHeaderByStyle(item)
}
else
{
<a @key="item" href="@item.Url" role="tab" tabindex="-1" class="@GetClassString(item)" @onclick="@(() => OnClickTabItem(item))" @onclick:preventDefault="@(!ClickTabToNavigation)" draggable="@DraggableString">
@if (!string.IsNullOrEmpty(item.Icon))
{
<i class="@GetIconClassString(item.Icon)"></i>
}
<span class="tabs-item-text">@item.Text</span>
@if (ShowFullScreen && item.ShowFullScreen)
{
<FullScreenButton TargetId="@GetIdByTabItem(item)"></FullScreenButton>
}
@if (ShowClose && item.Closable)
{
<span class="tabs-item-close" @onclick:stopPropagation @onclick:preventDefault @onclick="() => RemoveTab(item)">
<i class="@CloseIcon"></i>
</span>
}
</a>
@RenderHeaderByStyle(item)
}
}
@if (IsCard || IsBorderCard)
Expand Down Expand Up @@ -145,4 +123,54 @@ else
@<CascadingValue Value="item" IsFixed="true">
@RenderTabItemContent(item)
</CascadingValue>;

RenderFragment RenderChromeDisabledHeader(TabItem item) =>
@<div @key="@item" class="@GetItemWrapClassString(item)">
<div role="tab" class="@GetClassString(item)">
@RenderHeaderContent(item)
</div>
<i class="tab-corner tab-corner-left"></i>
<i class="tab-corner tab-corner-right"></i>
</div>;

RenderFragment RenderDefaultDisabledHeader(TabItem item) =>
@<div @key="item" role="tab" class="@GetClassString(item)">
@RenderHeaderContent(item)
</div>;

RenderFragment RenderChromeHeader(TabItem item) =>
@<div @key="@item" class="@GetItemWrapClassString(item)">
<a href="@item.Url" role="tab" tabindex="-1" class="@GetClassString(item)" @onclick="@(() => OnClickTabItem(item))" @onclick:preventDefault="@(!ClickTabToNavigation)" draggable="@DraggableString">
@RenderHeaderContent(item)
</a>
<i class="tab-corner tab-corner-left"></i>
<i class="tab-corner tab-corner-right"></i>
</div>;

RenderFragment RenderDefaultHeader(TabItem item) =>
@<a @key="item" href="@item.Url" role="tab" tabindex="-1" class="@GetClassString(item)" @onclick="@(() => OnClickTabItem(item))" @onclick:preventDefault="@(!ClickTabToNavigation)" draggable="@DraggableString">
@RenderHeaderContent(item)
</a>;

RenderFragment RenderHeaderContent(TabItem item) =>
@<div class="tabs-item-body">
@if (!string.IsNullOrEmpty(item.Icon))
{
<i class="@GetIconClassString(item.Icon)"></i>
}
<span class="tabs-item-text">@item.Text</span>
@if (!item.IsDisabled)
{
@if (ShowFullScreen && item.ShowFullScreen)
{
<FullScreenButton TargetId="@GetIdByTabItem(item)"></FullScreenButton>
}
@if (ShowClose && item.Closable)
{
<span class="tabs-item-close" @onclick:stopPropagation @onclick:preventDefault @onclick="() => RemoveTab(item)">
<i class="@CloseIcon"></i>
</span>
}
}
</div>;
}
27 changes: 25 additions & 2 deletions src/BootstrapBlazor/Components/Tab/Tab.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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();

Expand Down Expand Up @@ -283,6 +288,12 @@ public partial class Tab : IHandlerException
[Parameter]
public Func<TabItem, Task>? OnDragItemEndAsync { get; set; }

/// <summary>
/// Gets or sets the tab style. Default is <see cref="TabStyle.Default"/>.
/// </summary>
[Parameter]
public TabStyle TabStyle { get; set; }

[CascadingParameter]
private Layout? Layout { get; set; }

Expand Down Expand Up @@ -783,6 +794,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();
}

Expand Down Expand Up @@ -854,6 +873,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);

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
110 changes: 110 additions & 0 deletions src/BootstrapBlazor/Components/Tab/Tab.razor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -487,3 +487,113 @@
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;
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 .25rem;
}

.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%;
}
}
}

.tabs-item-fix {
background-color: var(--bb-tabs-header-bg-color);
}
}
Loading
Loading