diff --git a/src/BootstrapBlazor/Components/Layout/Layout.razor b/src/BootstrapBlazor/Components/Layout/Layout.razor index 75d823a6a9e..137af65e4b1 100644 --- a/src/BootstrapBlazor/Components/Layout/Layout.razor +++ b/src/BootstrapBlazor/Components/Layout/Layout.razor @@ -7,7 +7,7 @@ @if (IsAuthenticated) { -
+
@if (Side == null) { if (Header != null) @@ -79,6 +79,12 @@ @CollapseBarTemplate } } + @if (ShowTabInHeader) + { +
+ @RenderTabHeader() +
+ } @Header ; @@ -137,7 +143,7 @@ ShowRefreshToolbarButton="ShowRefreshToolbarButton" ShowFullscreenToolbarButton="ShowFullscreenToolbarButton" RefreshToolbarButtonIcon="@RefreshToolbarButtonIcon" FullscreenToolbarButtonIcon="@FullscreenToolbarButtonIcon" RefreshToolbarTooltipText="@RefreshToolbarTooltipText" FullscreenToolbarTooltipText="@FullscreenToolbarTooltipText" - OnToolbarRefreshCallback="OnToolbarRefreshCallback" + OnToolbarRefreshCallback="OnToolbarRefreshCallback" Layout="this" Body="@Main" NotAuthorized="NotAuthorized!" NotFound="NotFound!" NotFoundTabText="@NotFoundTabText"> ; diff --git a/src/BootstrapBlazor/Components/Layout/Layout.razor.cs b/src/BootstrapBlazor/Components/Layout/Layout.razor.cs index dfa088ec917..617254755b1 100644 --- a/src/BootstrapBlazor/Components/Layout/Layout.razor.cs +++ b/src/BootstrapBlazor/Components/Layout/Layout.razor.cs @@ -345,6 +345,12 @@ public partial class Layout : IHandlerException [Parameter] public Func>? OnBeforeShowContextMenu { get; set; } + /// + /// Gets or sets whether show the tab in header. Default is false. + /// + [Parameter] + public bool ShowTabInHeader { get; set; } + [Inject] [NotNull] private NavigationManager? Navigation { get; set; } @@ -451,7 +457,8 @@ public partial class Layout : IHandlerException private IStringLocalizer? Localizer { get; set; } private bool _init; - private Tab _tab = null!; + private Tab? _tab = null; + private ITabHeader? _tabHeader = null; /// /// @@ -627,6 +634,18 @@ public virtual Task HandlerException(Exception ex, RenderFragment err private string? GetTargetString() => IsFixedTabHeader ? ".tabs-body" : null; + private RenderFragment RenderTabHeader() => builder => + { + builder.OpenComponent(0); + builder.AddComponentReferenceCapture(1, instance => _tabHeader = (ITabHeader)instance); + builder.CloseComponent(); + }; + + internal void RegisterTab(Tab tab) + { + tab.TabHeader = _tabHeader; + } + /// /// /// diff --git a/src/BootstrapBlazor/Components/Layout/LayoutHeader.cs b/src/BootstrapBlazor/Components/Layout/LayoutHeader.cs new file mode 100644 index 00000000000..3d2d97f4b4d --- /dev/null +++ b/src/BootstrapBlazor/Components/Layout/LayoutHeader.cs @@ -0,0 +1,37 @@ +// 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; + +internal class LayoutHeader : IComponent, ITabHeader +{ + private RenderHandle _renderHandle; + + void IComponent.Attach(RenderHandle renderHandle) + { + _renderHandle = renderHandle; + } + + /// + /// + /// + /// + /// + Task IComponent.SetParametersAsync(ParameterView parameters) + { + return Task.CompletedTask; + } + + /// + /// render tab header method. + /// + public void Render(RenderFragment renderFragment) + { + _renderHandle.Render(builder => + { + builder.AddContent(0, renderFragment); + }); + } +} diff --git a/src/BootstrapBlazor/Components/Tab/ITabHeader.cs b/src/BootstrapBlazor/Components/Tab/ITabHeader.cs new file mode 100644 index 00000000000..5b4ec19111f --- /dev/null +++ b/src/BootstrapBlazor/Components/Tab/ITabHeader.cs @@ -0,0 +1,18 @@ +// 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; + +/// +/// ITabHeader interface +/// +public interface ITabHeader +{ + /// + /// Render method + /// + /// + void Render(RenderFragment renderFragment); +} diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor b/src/BootstrapBlazor/Components/Tab/Tab.razor index a503659557a..d86840e722c 100644 --- a/src/BootstrapBlazor/Components/Tab/Tab.razor +++ b/src/BootstrapBlazor/Components/Tab/Tab.razor @@ -40,151 +40,161 @@ else @code { RenderFragment RenderTab => @
-
-
- @if (BeforeNavigatorTemplate != null) + @if (TabHeader != null) + { + TabHeader.Render(RenderTabHeader); + } + else + { + @RenderTabHeader + } +
+ + @if (IsOnlyRenderActiveTab) { - - @BeforeNavigatorTemplate - + var item = Items.FirstOrDefault(i => i.IsActive); + if (item != null) + { + @RenderTabItem(item) + } } - @if (ShowNavigatorButtons) + else { - + foreach (var item in Items) + { + @RenderTabItem(item) + } } -
-
- - @ChildContent - - - @if (!Items.Any() && !string.IsNullOrEmpty(DefaultUrl)) - { - if (ClickTabToNavigation) - { - Navigator.NavigateTo(DefaultUrl); - } - else - { - AddTabItem(DefaultUrl); - } - } - @if (FirstRender) + +
+
; + + RenderFragment RenderTabHeader => + @
+
+ @if (BeforeNavigatorTemplate != null) + { + + @BeforeNavigatorTemplate + + } + @if (ShowNavigatorButtons) + { + + } +
+
+ + @ChildContent + + + @if (!Items.Any() && !string.IsNullOrEmpty(DefaultUrl)) + { + if (ClickTabToNavigation) { - if (!Items.Any(t => t.IsActive)) - { - Items.FirstOrDefault(i => i.IsDisabled == false)?.SetActive(true); - } + Navigator.NavigateTo(DefaultUrl); } - @foreach (var item in Items) + else { - @if (item.HeaderTemplate != null) - { -
- @item.HeaderTemplate(item) -
- } - else if (item.IsDisabled) - { - @RenderDisabledHeaderItem(item) - } - else - { - @RenderHeaderItem(item) - } + AddTabItem(DefaultUrl); } - @if (IsCard || IsBorderCard) + } + @if (FirstRender) + { + if (!Items.Any(t => t.IsActive)) { -
+ Items.FirstOrDefault(i => i.IsDisabled == false)?.SetActive(true); } -
- @if (!IsCard && !IsBorderCard && ShowActiveBar) - { -
} -
-
- - @if (ButtonTemplate != null) - { - @ButtonTemplate - } - @if (ShowToolbar) - { -
- @if (ShowRefreshToolbarButton) + @foreach (var item in Items) + { + @if (item.HeaderTemplate != null) { - +
+ @item.HeaderTemplate(item) +
} - @if (ShowFullscreenToolbarButton) + else if (item.IsDisabled) { -
- -
+ @RenderDisabledHeaderItem(item) } - @if (ToolbarTemplate != null) + else { - @ToolbarTemplate + @RenderHeaderItem(item) } -
- } - @if (ShowNavigatorButtons) - { - - } - @if (ShouldShowExtendButtons()) - { - - - } - @if (AfterNavigatorTemplate != null) + } + @if (IsCard || IsBorderCard) + { +
+ } + + @if (!IsCard && !IsBorderCard && ShowActiveBar) { - @AfterNavigatorTemplate +
} -
+
-
-
- @if (IsOnlyRenderActiveTab) + @if (ButtonTemplate != null) { - var item = Items.FirstOrDefault(i => i.IsActive); - if (item != null) - { - @RenderTabItem(item) - } + @ButtonTemplate } - else + @if (ShowToolbar) { - foreach (var item in Items) - { - @RenderTabItem(item) - } +
+ @if (ShowRefreshToolbarButton) + { + + } + @if (ShowFullscreenToolbarButton) + { +
+ +
+ } + @if (ToolbarTemplate != null) + { + @ToolbarTemplate + } +
+ } + @if (ShowNavigatorButtons) + { + + } + @if (ShouldShowExtendButtons()) + { + + + } + @if (AfterNavigatorTemplate != null) + { + @AfterNavigatorTemplate }
diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs index 13f511925f1..ce030c1cbbb 100644 --- a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs +++ b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs @@ -42,7 +42,7 @@ public partial class Tab : IHandlerException .AddClass("tabs-vertical", Placement == Placement.Left || Placement == Placement.Right) .AddClass("tabs-chrome", TabStyle == TabStyle.Chrome) .AddClass("tabs-capsule", TabStyle == TabStyle.Capsule) - .AddClassFromAttributes(AdditionalAttributes) + .AddClassFromAttributes(AdditionalAttributes) .Build(); private string? StyleString => CssBuilder.Default() @@ -419,8 +419,14 @@ public partial class Tab : IHandlerException [Parameter] public Func>? OnBeforeShowContextMenu { get; set; } + /// + /// Gets or sets the instance. Default is null. + /// + [Parameter] + public Layout? Layout { get; set; } + [CascadingParameter] - private Layout? Layout { get; set; } + private Layout? CascadeLayout { get; set; } [Inject] [NotNull] @@ -465,6 +471,8 @@ public partial class Tab : IHandlerException private bool IsPreventDefault => _contextMenuZone != null; + internal ITabHeader? TabHeader { get; set; } + /// /// /// @@ -473,6 +481,11 @@ protected override void OnInitialized() base.OnInitialized(); ErrorLogger?.Register(this); + + if (Layout is { ShowTabInHeader: true }) + { + Layout.RegisterTab(this); + } } /// @@ -567,7 +580,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) /// /// /// - protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(DragItemCallback)); + protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(DragItemCallback), Layout?.Id); private void RemoveLocationChanged() { @@ -779,6 +792,8 @@ public void AddTab(string url, string text, string? icon = null, bool active = t StateHasChanged(); } + private Layout? LayoutInstance => Layout ?? CascadeLayout; + private void AddTabItem(string url) { var parameters = new Dictionary @@ -816,7 +831,7 @@ private void AddTabItem(string url) 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), Layout?.Resource); + builder.AddAttribute(4, nameof(BootstrapBlazorAuthorizeView.Resource), LayoutInstance?.Resource); builder.CloseComponent(); })); } @@ -998,7 +1013,7 @@ private RenderFragment RenderTabItemContent(TabItem item) => builder => private IEnumerable? _menuItems; private MenuItem? GetMenuItem(string url) { - _menuItems ??= (Menus ?? Layout?.Menus).GetAllItems(); + _menuItems ??= (Menus ?? LayoutInstance?.Menus).GetAllItems(); return _menuItems?.FirstOrDefault(i => !string.IsNullOrEmpty(i.Url) && (i.Url.TrimStart('/').Equals(url.TrimStart('/'), StringComparison.OrdinalIgnoreCase))); } diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor.js b/src/BootstrapBlazor/Components/Tab/Tab.razor.js index fe95d87df5a..cd20c1a4fd6 100644 --- a/src/BootstrapBlazor/Components/Tab/Tab.razor.js +++ b/src/BootstrapBlazor/Components/Tab/Tab.razor.js @@ -171,7 +171,7 @@ const disposeDragItems = items => { }) } -export function init(id, invoke, method) { +export function init(id, invoke, method, layoutId) { const el = document.getElementById(id) if (el === null) { return @@ -180,11 +180,17 @@ export function init(id, invoke, method) { const tab = { el, invoke, method } Data.set(id, tab) - tab.header = el.firstChild + if (layoutId) { + const layout = document.getElementById(layoutId) + tab.header = layout.querySelector('.layout-header .tabs > .tabs-header'); + } + else { + tab.header = el.firstChild + } + tab.wrap = tab.header.firstChild tab.scroll = tab.wrap.querySelector('.tabs-nav-scroll') tab.tabNav = tab.scroll.firstChild - tab.resizeHandler = () => { resize(tab) } diff --git a/test/UnitTest/Components/LayoutTest.cs b/test/UnitTest/Components/LayoutTest.cs index 0d129c591a8..2238326bf5e 100644 --- a/test/UnitTest/Components/LayoutTest.cs +++ b/test/UnitTest/Components/LayoutTest.cs @@ -92,6 +92,18 @@ public async Task TabStyle_Ok() Assert.True(show); } + [Fact] + public void ShowTabInHeader_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.UseTabSet, true); + pb.Add(a => a.ShowTabInHeader, true); + pb.Add(a => a.Header, CreateHeader()); + }); + cut.Contains("tabs tabs-chrome"); + } + [Fact] public void ShowFooter_OK() {