mirror of
https://github.com/dotnetcore/BootstrapBlazor.git
synced 2025-12-20 10:26:41 +08:00
feat(Tab): add TabHeader parameter (#5787)
* refactor: 重构 Tab 组件增加 TabHeader 参数代替 Layout * refactor: 移除 _tab 变量 * test: 更新单元测试 * test: 更新单元测试
This commit is contained in:
@@ -81,7 +81,7 @@
|
||||
}
|
||||
@if (ShowTabInHeader)
|
||||
{
|
||||
<div class="tabs tabs-chrome">
|
||||
<div class="tabs tabs-chrome" id="@GetId()">
|
||||
@RenderTabHeader()
|
||||
</div>
|
||||
}
|
||||
@@ -131,7 +131,7 @@
|
||||
</main>;
|
||||
|
||||
RenderFragment RenderTab =>
|
||||
@<Tab ClickTabToNavigation="ClickTabToNavigation" AdditionalAssemblies="@AdditionalAssemblies" @ref="_tab"
|
||||
@<Tab ClickTabToNavigation="ClickTabToNavigation" AdditionalAssemblies="@AdditionalAssemblies"
|
||||
ShowExtendButtons="ShowTabExtendButtons" ShowClose="ShowTabItemClose" AllowDrag="AllowDragTab"
|
||||
DefaultUrl="@TabDefaultUrl" ExcludeUrls="@ExcludeUrls" IsOnlyRenderActiveTab="IsOnlyRenderActiveTab"
|
||||
TabStyle="TabStyle" ShowToolbar="@ShowToolbar" ToolbarTemplate="@ToolbarTemplate"
|
||||
@@ -143,7 +143,7 @@
|
||||
ShowRefreshToolbarButton="ShowRefreshToolbarButton" ShowFullscreenToolbarButton="ShowFullscreenToolbarButton"
|
||||
RefreshToolbarButtonIcon="@RefreshToolbarButtonIcon" FullscreenToolbarButtonIcon="@FullscreenToolbarButtonIcon"
|
||||
RefreshToolbarTooltipText="@RefreshToolbarTooltipText" FullscreenToolbarTooltipText="@FullscreenToolbarTooltipText"
|
||||
OnToolbarRefreshCallback="OnToolbarRefreshCallback" Layout="this"
|
||||
OnToolbarRefreshCallback="OnToolbarRefreshCallback" TabHeader="TabHeader"
|
||||
Body="@Main" NotAuthorized="NotAuthorized!" NotFound="NotFound!" NotFoundTabText="@NotFoundTabText">
|
||||
</Tab>;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace BootstrapBlazor.Components;
|
||||
/// <summary>
|
||||
/// Layout 组件
|
||||
/// </summary>
|
||||
public partial class Layout : IHandlerException
|
||||
public partial class Layout : IHandlerException, ITabHeader
|
||||
{
|
||||
private bool IsSmallScreen { get; set; }
|
||||
|
||||
@@ -457,8 +457,9 @@ public partial class Layout : IHandlerException
|
||||
private IStringLocalizer<Layout>? Localizer { get; set; }
|
||||
|
||||
private bool _init;
|
||||
private Tab? _tab = null;
|
||||
private ITabHeader? _tabHeader = null;
|
||||
private LayoutHeader? _layoutHeader = null;
|
||||
|
||||
private ITabHeader? TabHeader => ShowTabInHeader ? this : null;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
@@ -637,15 +638,26 @@ public partial class Layout : IHandlerException
|
||||
private RenderFragment RenderTabHeader() => builder =>
|
||||
{
|
||||
builder.OpenComponent<LayoutHeader>(0);
|
||||
builder.AddComponentReferenceCapture(1, instance => _tabHeader = (ITabHeader)instance);
|
||||
builder.AddComponentReferenceCapture(1, instance => _layoutHeader = (LayoutHeader)instance);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
|
||||
internal void RegisterTab(Tab tab)
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="renderFragment"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public void Render(RenderFragment renderFragment)
|
||||
{
|
||||
tab.TabHeader = _tabHeader;
|
||||
_layoutHeader?.Render(renderFragment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetId() => $"{Id}_tab_header";
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
internal class LayoutHeader : IComponent, ITabHeader
|
||||
internal class LayoutHeader : IComponent
|
||||
{
|
||||
private RenderHandle _renderHandle;
|
||||
|
||||
|
||||
@@ -15,4 +15,10 @@ public interface ITabHeader
|
||||
/// </summary>
|
||||
/// <param name="renderFragment"></param>
|
||||
void Render(RenderFragment renderFragment);
|
||||
|
||||
/// <summary>
|
||||
/// Get the id of the tab header
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetId();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div @attributes="@AdditionalAttributes" id="@Id" class="@ClassString" style="@StyleString">
|
||||
<div @attributes="@AdditionalAttributes" id="@Id" data-bb-header-id="@HeaderId" class="@ClassString" style="@StyleString">
|
||||
@if (TabHeader != null)
|
||||
{
|
||||
TabHeader.Render(RenderTabHeader);
|
||||
|
||||
@@ -423,10 +423,10 @@ public partial class Tab : IHandlerException
|
||||
/// Gets or sets the <see cref="ITabHeader"/> instance. Default is null.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Layout? Layout { get; set; }
|
||||
public ITabHeader? TabHeader { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
private Layout? CascadeLayout { get; set; }
|
||||
private Layout? Layout { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
@@ -471,8 +471,6 @@ public partial class Tab : IHandlerException
|
||||
|
||||
private bool IsPreventDefault => _contextMenuZone != null;
|
||||
|
||||
internal ITabHeader? TabHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
@@ -494,15 +492,6 @@ public partial class Tab : IHandlerException
|
||||
IsBorderCard = true;
|
||||
}
|
||||
|
||||
if (Layout is { ShowTabInHeader: true })
|
||||
{
|
||||
Layout.RegisterTab(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
TabHeader = null;
|
||||
}
|
||||
|
||||
CloseOtherTabsText ??= Localizer[nameof(CloseOtherTabsText)];
|
||||
CloseAllTabsText ??= Localizer[nameof(CloseAllTabsText)];
|
||||
CloseCurrentTabText ??= Localizer[nameof(CloseCurrentTabText)];
|
||||
@@ -584,9 +573,7 @@ public partial class Tab : IHandlerException
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(DragItemCallback), LayoutId);
|
||||
|
||||
private string? LayoutId => Layout is { ShowTabInHeader: true } ? Layout.Id : null;
|
||||
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(DragItemCallback));
|
||||
|
||||
private void RemoveLocationChanged()
|
||||
{
|
||||
@@ -798,8 +785,6 @@ public partial class Tab : IHandlerException
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private Layout? LayoutInstance => Layout ?? CascadeLayout;
|
||||
|
||||
private void AddTabItem(string url)
|
||||
{
|
||||
var parameters = new Dictionary<string, object?>
|
||||
@@ -837,7 +822,7 @@ public partial class Tab : IHandlerException
|
||||
builder.AddAttribute(1, nameof(BootstrapBlazorAuthorizeView.Type), context.Handler);
|
||||
builder.AddAttribute(2, nameof(BootstrapBlazorAuthorizeView.Parameters), context.Parameters);
|
||||
builder.AddAttribute(3, nameof(BootstrapBlazorAuthorizeView.NotAuthorized), NotAuthorized);
|
||||
builder.AddAttribute(4, nameof(BootstrapBlazorAuthorizeView.Resource), LayoutInstance?.Resource);
|
||||
builder.AddAttribute(4, nameof(BootstrapBlazorAuthorizeView.Resource), Layout?.Resource);
|
||||
builder.CloseComponent();
|
||||
}));
|
||||
}
|
||||
@@ -1019,7 +1004,7 @@ public partial class Tab : IHandlerException
|
||||
private IEnumerable<MenuItem>? _menuItems;
|
||||
private MenuItem? GetMenuItem(string url)
|
||||
{
|
||||
_menuItems ??= (Menus ?? LayoutInstance?.Menus).GetAllItems();
|
||||
_menuItems ??= (Menus ?? Layout?.Menus).GetAllItems();
|
||||
return _menuItems?.FirstOrDefault(i => !string.IsNullOrEmpty(i.Url) && (i.Url.TrimStart('/').Equals(url.TrimStart('/'), StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
@@ -1188,30 +1173,36 @@ public partial class Tab : IHandlerException
|
||||
|
||||
private RenderFragment RenderTabItems() => builder =>
|
||||
{
|
||||
for (var index = 0; index < _items.Count; index++)
|
||||
foreach (var item in Items)
|
||||
{
|
||||
var item = _items[index];
|
||||
var sequence = (index + 1) * 100;
|
||||
if (item.HeaderTemplate != null)
|
||||
{
|
||||
builder.OpenElement(sequence, "div");
|
||||
builder.OpenElement(0, "div");
|
||||
builder.SetKey(item);
|
||||
builder.AddAttribute(sequence + 10, "class", GetItemWrapClassString(item));
|
||||
builder.AddAttribute(sequence + 20, "draggable", DraggableString);
|
||||
builder.AddContent(sequence + 30, item.HeaderTemplate(item));
|
||||
builder.AddAttribute(10, "class", GetItemWrapClassString(item));
|
||||
builder.AddAttribute(20, "draggable", DraggableString);
|
||||
builder.AddContent(30, item.HeaderTemplate(item));
|
||||
builder.CloseElement();
|
||||
}
|
||||
else if (item.IsDisabled)
|
||||
{
|
||||
builder.AddContent(sequence + 40, RenderDisabledHeaderItem(item));
|
||||
builder.AddContent(40, RenderDisabledHeaderItem(item));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddContent(sequence + 50, RenderHeaderItem(item));
|
||||
builder.AddContent(50, RenderHeaderItem(item));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="ITabHeader"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="tabHeader"></param>
|
||||
public void SetTabHeader(ITabHeader tabHeader) => TabHeader = tabHeader;
|
||||
|
||||
private string? HeaderId => TabHeader?.GetId();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
|
||||
@@ -171,7 +171,7 @@ const disposeDragItems = items => {
|
||||
})
|
||||
}
|
||||
|
||||
export function init(id, invoke, method, layoutId) {
|
||||
export function init(id, invoke, method) {
|
||||
const el = document.getElementById(id)
|
||||
if (el === null) {
|
||||
return
|
||||
@@ -180,9 +180,10 @@ export function init(id, invoke, method, layoutId) {
|
||||
const tab = { el, invoke, method }
|
||||
Data.set(id, tab)
|
||||
|
||||
if (layoutId) {
|
||||
const layout = document.getElementById(layoutId)
|
||||
tab.header = layout.querySelector('.layout-header .tabs > .tabs-header');
|
||||
const headerId = el.getAttribute("data-bb-header-id");
|
||||
if (headerId) {
|
||||
const header = document.getElementById(headerId)
|
||||
tab.header = header.querySelector('.tabs > .tabs-header');
|
||||
}
|
||||
else {
|
||||
tab.header = el.firstChild
|
||||
|
||||
@@ -93,15 +93,30 @@ public class LayoutTest : BootstrapBlazorTestBase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowTabInHeader_Ok()
|
||||
public async Task ShowTabInHeader_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Layout>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Id, "LayoutId");
|
||||
pb.Add(a => a.UseTabSet, true);
|
||||
pb.Add(a => a.ShowTabInHeader, true);
|
||||
pb.Add(a => a.ShowTabInHeader, false);
|
||||
pb.Add(a => a.Header, CreateHeader());
|
||||
});
|
||||
await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, "")));
|
||||
|
||||
cut.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.ShowTabInHeader, true);
|
||||
});
|
||||
cut.Contains("data-bb-header-id=\"LayoutId_tab_header\"");
|
||||
cut.Contains("tabs tabs-chrome");
|
||||
await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, "")));
|
||||
|
||||
cut.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.ShowTabInHeader, false);
|
||||
});
|
||||
await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, "")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1104,6 +1104,33 @@ public class TabTest : BootstrapBlazorTestBase
|
||||
cut.DoesNotContain("tabs-nav-toolbar-fs");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TabHeader_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
|
||||
{
|
||||
pb.AddChildContent<MockTabHeader>();
|
||||
pb.AddChildContent<Tab>(pb =>
|
||||
{
|
||||
pb.Add(a => a.ShowToolbar, false);
|
||||
pb.AddChildContent<TabItem>(pb =>
|
||||
{
|
||||
pb.Add(a => a.ShowFullScreen, true);
|
||||
pb.Add(a => a.Text, "Text1");
|
||||
pb.Add(a => a.ChildContent, builder => builder.AddContent(0, "Test1"));
|
||||
});
|
||||
});
|
||||
});
|
||||
var header = cut.FindComponent<MockTabHeader>();
|
||||
var tab = cut.FindComponent<Tab>();
|
||||
var headerElement = cut.Find(".tabs-header");
|
||||
Assert.NotNull(headerElement);
|
||||
|
||||
tab.Instance.SetTabHeader(header.Instance);
|
||||
tab.SetParametersAndRender();
|
||||
tab.DoesNotContain("tabs-header");
|
||||
}
|
||||
|
||||
class DisableTabItemButton : ComponentBase
|
||||
{
|
||||
[CascadingParameter, NotNull]
|
||||
@@ -1122,4 +1149,15 @@ public class TabTest : BootstrapBlazorTestBase
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
class MockTabHeader : ComponentBase, ITabHeader
|
||||
{
|
||||
public string GetId() => "MockTabHeader";
|
||||
|
||||
private RenderFragment? _renderFragment;
|
||||
|
||||
public void Render(RenderFragment renderFragment) => _renderFragment = renderFragment;
|
||||
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder) => builder.AddContent(0, _renderFragment);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user