Skip to content
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

feat(Tab): add IsDisabled parameter #5200

Merged
merged 16 commits into from
Jan 24, 2025
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
<Button Text="Disabled" OnClickWithoutRender="OnToggleDisable"></Button>
</div>
Original file line number Diff line number Diff line change
@@ -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([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Server.Components.Components;

/// <summary>
/// TabItemContentDemo 组件
/// </summary>
public partial class TabItemContentDemo
{
[CascadingParameter]
[NotNull]
private TabItem? TabItem { get; set; }

private Task OnToggleDisable()
{
TabItem.SetDisabled(true);
return Task.CompletedTask;
}
}
21 changes: 21 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/Tabs.razor
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,27 @@ private void Navigation()
</Tab>
</DemoBlock>

<DemoBlock Title="@Localizer["TabsDisabledTitle"]" Introduction="@Localizer["TabsDisabledIntro"]" Name="IsDisabled">
<section ignore>
<Button Text="@DisableText" OnClick="OnToggleDisable"></Button>
</section>
<Tab IsCard="true" ShowClose="true">
<TabItem Text="@Localizer["TabItem1Text"]" Icon="fa-solid fa-user" IsDisabled="@Disabled">
<div>@Localizer["TabItem1Content"]</div>
</TabItem>
<TabItem Text="@Localizer["TabItem2Text"]" Icon="fa-solid fa-gauge-high">
<div>@Localizer["TabItem2Content"]</div>
<TabItemContentDemo></TabItemContentDemo>
</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>

<DemoBlock Title="@Localizer["TabsPlacementTitle"]" Introduction="@Localizer["TabsPlacementIntro"]" Name="Placement">
<p class="text-center">
<div class="btn-group">
Expand Down
11 changes: 11 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/Tabs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public sealed partial class Tabs

private string TabItemText { get; set; } = "Test";

private bool Disabled { get; set; } = true;

private string DisableText { get; set; } = "Enable";

private void SetPlacement(Placement placement)
{
BindPlacement = placement;
Expand Down Expand Up @@ -70,6 +74,13 @@ private static async Task RemoveTab(Tab tabset)
}
}

private void OnToggleDisable()
{
Disabled = !Disabled;

DisableText = Disabled ? "Enable" : "Disable";
}

/// <summary>
/// OnAfterRenderAsync
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2100,7 +2100,9 @@
"AttributeNotAuthorized": "The template for NotAuthorized",
"AttributeNotFound": "The template for NotFound",
"AttributeExcludeUrls": "Exclude address support for wildcards",
"AttributeButtonTemplate": "The template for Buttons"
"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."
},
"BootstrapBlazor.Server.Components.Components.DemoTabItem": {
"Info": "Reset the title of this <code>TabItem</code> by click the button",
Expand Down
4 changes: 3 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2100,7 +2100,9 @@
"AttributeNotAuthorized": "NotAuthorized 模板",
"AttributeNotFound": "NotFound 模板",
"AttributeExcludeUrls": "排除地址支持通配符",
"AttributeButtonTemplate": "按钮模板"
"AttributeButtonTemplate": "按钮模板",
"TabsDisabledTitle": "禁用",
"TabsDisabledIntro": "通过设置 <code>IsDisabled=\"true\"</code> 禁用当前 <code>TabItem</code> 禁止点击、拖动、关闭等操作"
},
"BootstrapBlazor.Server.Components.Components.DemoTabItem": {
"Info": "点击下方按钮,本 <code>TabItem</code> 标题更改为当前分钟与秒",
Expand Down
12 changes: 11 additions & 1 deletion src/BootstrapBlazor/Components/Tab/Tab.razor
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ else
{
if (!Items.Any(t => t.IsActive))
{
Items.FirstOrDefault()?.SetActive(true);
Items.FirstOrDefault(i => i.IsDisabled == false)?.SetActive(true);
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
}
}
@foreach (var item in Items)
Expand All @@ -42,6 +42,16 @@ else
{
@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>
}
else
{
<a @key="item" href="@item.Url" role="tab" tabindex="-1" class="@GetClassString(item)" @onclick="@(() => OnClickTabItem(item))" @onclick:preventDefault="@(!ClickTabToNavigation)" draggable="@DraggableString">
Expand Down
17 changes: 17 additions & 0 deletions src/BootstrapBlazor/Components/Tab/Tab.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public partial class Tab : IHandlerException

private string? GetClassString(TabItem item) => CssBuilder.Default("tabs-item")
.AddClass("active", item.IsActive)
.AddClass("disabled", item.IsDisabled)
.AddClass(item.CssClass)
.AddClass("is-closeable", ShowClose)
.Build();
Expand Down Expand Up @@ -745,8 +746,24 @@ private void ActiveTabItem(TabItem item)
item.SetActive(true);
}

/// <summary>
/// 设置 TabItem 禁用状态
/// </summary>
/// <param name="item"></param>
/// <param name="disabled"></param>
public void SetDisabledItem(TabItem item, bool disabled)
{
item.SetDisabledWithoutRender(disabled);
StateHasChanged();
}

private RenderFragment RenderTabItemContent(TabItem item) => builder =>
{
if (item.IsDisabled)
{
return;
}

if (item.IsActive)
{
var content = _errorContent ?? item.ChildContent;
Expand Down
7 changes: 6 additions & 1 deletion src/BootstrapBlazor/Components/Tab/Tab.razor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
--bb-tabs-item-height: #{$bb-tabs-item-height};
--bb-tabs-item-active-color: #{$bb-tabs-item-active-color};
--bb-tabs-item-hover-color: #{$bb-tabs-item-hover-color};
--bb-tabs-item-disabled-opacity: #{$bb-tabs-item-disabled-opacity};
--bb-tabs-border-card-top-item-margin-top: #{$bb-tabs-border-card-top-item-margin-top};
--bb-tabs-bar-width: #{$bb-tabs-bar-width};
--bb-tabs-bar-height: #{$bb-tabs-bar-height};
Expand Down Expand Up @@ -170,10 +171,14 @@
color: var(--bb-tabs-item-active-color);
}

.tabs-item:hover {
.tabs-item:not(.disabled):hover {
color: var(--bb-tabs-item-hover-color);
}

.tabs-item.disabled {
opacity: var(--bb-tabs-item-disabled-opacity);
}

.tabs-item .tabs-item-text {
padding: 0 0.25rem;
pointer-events: none;
Expand Down
23 changes: 22 additions & 1 deletion src/BootstrapBlazor/Components/Tab/TabItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public class TabItem : ComponentBase
[Parameter]
public bool IsActive { get; set; }

/// <summary>
/// 获得/设置 当前状态是否 禁用 默认 false
/// </summary>
[Parameter]
public bool IsDisabled { get; set; }

/// <summary>
/// 获得/设置 当前 TabItem 是否可关闭 默认为 true 可关闭
/// </summary>
Expand Down Expand Up @@ -107,7 +113,22 @@ protected override void OnParametersSet()
/// 设置是否被选中方法
/// </summary>
/// <param name="active"></param>
public virtual void SetActive(bool active) => IsActive = active;
public void SetActive(bool active) => IsActive = active;

/// <summary>
/// 设置是否被禁用
/// </summary>
/// <param name="disabled"></param>
public void SetDisabled(bool disabled)
{
TabSet?.SetDisabledItem(this, disabled);
}

/// <summary>
/// 设置是否被禁用
/// </summary>
/// <param name="disabled"></param>
internal void SetDisabledWithoutRender(bool disabled) => IsDisabled = disabled;

/// <summary>
/// 重新设置标签文字等参数
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ $bb-tabs-item-padding: 0 1rem;
$bb-tabs-item-height: 40px;
$bb-tabs-item-active-color: #409eff;
$bb-tabs-item-hover-color: #409eff;
$bb-tabs-item-disabled-opacity: .5;
$bb-tabs-border-card-top-item-margin-top: -1px;
$bb-tabs-bar-width: 40px;
$bb-tabs-bar-height: 40px;
Expand Down
56 changes: 56 additions & 0 deletions test/UnitTest/Components/TabTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using AngleSharp.Dom;
using Bunit.TestDoubles;
using Microsoft.AspNetCore.Components.Rendering;
using System.Reflection;
using UnitTest.Misc;

Expand Down Expand Up @@ -385,6 +386,42 @@ public void MenuItem_Menu()
});
}

[Fact]
public async Task IsDisabled_Ok()
{
var cut = Context.RenderComponent<Tab>(pb =>
{
pb.Add(a => a.ClickTabToNavigation, false);

pb.AddChildContent<TabItem>(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.Add(a => a.IsDisabled, true);
});
pb.AddChildContent<TabItem>(pb =>
{
pb.Add(a => a.Text, "Text2");
pb.AddChildContent<DisableTabItemButton>();
});
});
Assert.Contains("<div role=\"tab\" class=\"tabs-item disabled\"><i class=\"fa fa-fa\"></i><span class=\"tabs-item-text\">Text1</span></div>", cut.Markup);

var button = cut.FindComponent<DisableTabItemButton>();
Assert.NotNull(button);

await cut.InvokeAsync(() => button.Instance.OnDisabledTabItem());
Assert.Contains("<div role=\"tab\" class=\"tabs-item active disabled\"><span class=\"tabs-item-text\">Text2</span></div>", cut.Markup);
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void SetDisabled_Ok()
{
var cut = Context.RenderComponent<TabItem>();
cut.Instance.SetDisabled(true);
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void MenuItem_Null()
{
Expand Down Expand Up @@ -792,4 +829,23 @@ public async Task FullScreen_Ok()
var button = cut.Find(".btn-fs");
await cut.InvokeAsync(() => button.Click());
}

class DisableTabItemButton : ComponentBase
{
[CascadingParameter, NotNull]
private TabItem? TabItem { get; set; }

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenComponent<Button>(0);
builder.AddAttribute(1, nameof(Button.OnClickWithoutRender), OnDisabledTabItem);
builder.CloseComponent();
}

public Task OnDisabledTabItem()
{
TabItem.SetDisabled(true);
return Task.CompletedTask;
}
}
}
Loading