refactor(Layout): enhance layout styles (#4679)

* doc: 代码格式化

* refactor: 移除空属性

* refactor: 重新设计收起样式

* feat: Layout 组件支持 static 渲染模式

* fix(Widget): 修复报错问题

* chore: bump version 9.0.0-beta03

* style: 移除抽屉 padding 间隙

* refactor: 移除宽度计算样式

* doc: 后台示例更新登录信息栏

* doc: 格式化文档

* refactor: 样式独立文件

* doc: 精简样式

* refactor: 更新 Widget 组件与模板一致

* refactor: 修复调整结构组件状态不正确问题

* refactor: 重构方法提高性能

* refactor: 修复布尔值时不触发 OnStateChanged 回调问题

* refactor: 修复布尔值时不触发 OnStateChanged 回调问题

* test: 增加单元测试

* refactor: 精简代码提高效率

* refactor: 调整右侧边框样式

* chore: bump version 9.0.1-beta01

* doc: 移除 2019 文字

* doc: 移除 2019 文字

* refactor: 更改样式名称

* refactor: 重构侧边栏收起样式

* refactor: 更新 Menu 透明样式

* doc: 更新后台管理样式

* style: 兼容 Menu 组件

* style: 增加响应式布局

* refactor: 重构样式

* wip: 临时移除静态支持

* style: 微调样式

* refactor: 精简样式

* style: 微调 Menu 样式

* refactor: 增加层次

* style: 调整 Title 颜色

* style: 调整收起展开菜单宽度样式

* style: 增加菜单右侧边框线

* doc: 调整文档

* doc: 更新文档链接

* style: 合并样式

* style: 合并样式

* style: 整理 header-bar 样式

* refactor: 重构样式

* style: 层次化样式

* style: 层次化样式

* refactor: 移除 IsPage 参数

* refactor: 移除 static 判断逻辑

* style: 合并 Header 样式

* refactor: 移除侧边栏抽屉

* style: 合并样式

* refactor: 移除 SideWidth=“0” 设置

* refactor: 使用 div 元素渲染按钮

* refactor: 恢复脚本

* refactor: 重构代码

* doc: 移除 IsPage 文档

* doc: 更新开发工具名称
This commit is contained in:
Argo Zhang 2024-11-16 07:35:07 -08:00 committed by GitHub
parent f24b4f47b8
commit 1aacef59b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 453 additions and 840 deletions

View File

@ -1,5 +1,5 @@
<DropdownWidget>
<DropdownWidgetItem Icon="fa-regular fa-envelope" BadgeNumber="4" BadgeColor="Color.Primary">
<DropdownWidget class="px-3 d-none d-sm-block">
<DropdownWidgetItem Icon="fa-regular fa-envelope" BadgeNumber="4">
<HeaderTemplate>
<span> 4 </span>
</HeaderTemplate>
@ -8,12 +8,12 @@
{
<a class="dropdown-item d-flex align-items-center" href="#" @onclick:preventDefault>
<div style="width: 40px; height: 40px;">
<Avatar Url="./images/Argo-C.png" IsCircle="true" Size="Size.Small" />
<Avatar Url="images/Argo-C.png" IsCircle="true" Size="Size.Small" />
</div>
<div class="ms-2">
<div class="d-flex position-relative">
<h4>Argo Zhang</h4>
<small><i class="fa-regular fa-clock"></i> @(4 + index) mins</small>
<div class="flex-fill">Argo Zhang</div>
<small><i class="fa fa-clock-o"></i> @(4 + index) mins</small>
</div>
<div class="text-truncate">Why not buy a new awesome theme?</div>
</div>
@ -24,7 +24,7 @@
<a href="#" @onclick:preventDefault></a>
</FooterTemplate>
</DropdownWidgetItem>
<DropdownWidgetItem Icon="fa-regular fa-bell" BadgeNumber="10" HeaderColor="Color.Success" BadgeColor="Color.Success">
<DropdownWidgetItem Icon="fa-regular fa-bell" BadgeNumber="10" HeaderColor="Color.Success" BadgeColor="Color.Warning">
<HeaderTemplate>
<span> 10 </span>
</HeaderTemplate>
@ -32,7 +32,7 @@
@for (var index = 0; index < 10; index++)
{
<a class="dropdown-item d-flex align-items-center" href="#" @onclick:preventDefault>
<i class="fa-solid fa-users text-primary"></i>
<i class="fa fa-users text-primary"></i>
<div class="ms-2">5 new members joined</div>
</a>
}
@ -41,31 +41,31 @@
<a href="#" @onclick:preventDefault></a>
</FooterTemplate>
</DropdownWidgetItem>
<DropdownWidgetItem Icon="fa-solid fa-flag" BadgeNumber="9" HeaderColor="Color.Danger" BadgeColor="Color.Danger">
<DropdownWidgetItem Icon="fa-regular fa-flag" BadgeNumber="9" HeaderColor="Color.Danger" BadgeColor="Color.Danger">
<HeaderTemplate>
<span> 3 </span>
</HeaderTemplate>
<BodyTemplate>
<a href="#" class="dropdown-item" @onclick:preventDefault>
<h3 class="position-relative">
Design some buttons
<small class="pull-right">20%</small>
</h3>
<BootstrapBlazor.Components.Progress IsAnimated="true" IsStriped="true" Value="20" Color="Color.Primary"></BootstrapBlazor.Components.Progress>
<div class="d-flex">
<div class="flex-fill pe-5">Design some buttons</div>
<small>20%</small>
</div>
<Progress IsAnimated="true" IsStriped="true" Value="20" Color="Color.Primary"></Progress>
</a>
<a href="#" class="dropdown-item" @onclick:preventDefault>
<h3 class="position-relative">
Create a nice theme
<small class="pull-right">40%</small>
</h3>
<BootstrapBlazor.Components.Progress Value="40" Color="Color.Success"></BootstrapBlazor.Components.Progress>
<div class="d-flex">
<div class="flex-fill pe-5">Create a nice theme</div>
<small>40%</small>
</div>
<Progress Value="40" Color="Color.Success"></Progress>
</a>
<a href="#" class="dropdown-item" @onclick:preventDefault>
<h3 class="position-relative">
Some task I need to do
<small class="pull-right">60%</small>
</h3>
<BootstrapBlazor.Components.Progress Value="60" Color="Color.Danger"></BootstrapBlazor.Components.Progress>
<div class="d-flex">
<div class="flex-fill pe-5">Some task I need to do</div>
<small>60%</small>
</div>
<Progress Value="60" Color="Color.Danger"></Progress>
</a>
</BodyTemplate>
<FooterTemplate>

View File

@ -1,16 +1,25 @@
@inherits LayoutComponentBase
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
<HeadContent>
<link href="./css/layout.css" rel="stylesheet" />
</HeadContent>
<CascadingValue Value="this" IsFixed="true">
<Layout SideWidth="0" IsPage="true" IsFullSide="@IsFullSide" IsFixedHeader="@IsFixedHeader" IsFixedFooter="@IsFixedFooter"
<Layout IsFullSide="@IsFullSide" IsFixedHeader="@IsFixedHeader" IsFixedFooter="@IsFixedFooter"
ShowFooter="@ShowFooter" ShowGotoTop="true" ShowCollapseBar="true" Menus="@Menus"
UseTabSet="@UseTabSet" TabDefaultUrl="layout-page" AdditionalAssemblies="new[] { GetType().Assembly }" class="@LayoutClassString">
<Header>
<span class="ms-3 flex-fill">Bootstrap of Blazor</span>
<Widget></Widget>
<img alt="avatar" src="./images/Argo-C.png" class="layout-avatar-right" />
<span class="mx-3 d-none d-sm-block"></span>
<div class="layout-drawer" @onclick="@ToggleDrawer"><i class="fa-solid fa-gears"></i></div>
<Logout ImageUrl="images/Argo-C.png" DisplayName="超级管理员" UserName="Admin">
<LinkTemplate>
<a href="#"><i class="fa-solid fa-suitcase"></i></a>
<a href="#"><i class="fa-solid fa-cog"></i></a>
<a href="#"><i class="fa-solid fa-bell"></i><span class="badge badge-pill badge-success"></span></a>
<LogoutLink />
</LinkTemplate>
</Logout>
</Header>
<Side>
<div class="layout-banner">
@ -29,110 +38,4 @@
</div>
</Footer>
</Layout>
<Drawer Placement="Placement.Right" @bind-IsOpen="@IsOpen" IsBackdrop="true">
<div class="layout-drawer-body">
<div class="btn btn-info w-100" @onclick="@(e => IsOpen = false)"></div>
<div class="page-layout-demo-option">
<p></p>
<div class="row">
<div class="col-6">
<Tooltip Title="左右结构">
<div class="layout-item @(IsFullSide ? "active d-flex" : "d-flex")" @onclick="@(e => IsFullSide = true)">
<div class="layout-left d-flex flex-column">
<div class="layout-left-header"></div>
<div class="layout-left-body flex-fill"></div>
</div>
<div class="layout-right d-flex flex-column flex-fill">
<div class="layout-right-header"></div>
<div class="layout-right-body flex-fill"></div>
<div class="layout-right-footer"></div>
</div>
</div>
</Tooltip>
</div>
<div class="col-6">
<Tooltip Title="上下结构">
<div class="layout-item flex-column @(IsFullSide ? "d-flex" : "active d-flex")" @onclick="@(e => IsFullSide = false)">
<div class="layout-top">
</div>
<div class="layout-body d-flex flex-fill">
<div class="layout-left">
</div>
<div class="layout-right flex-fill">
</div>
</div>
<div class="layout-footer">
</div>
</div>
</Tooltip>
</div>
</div>
</div>
<div class="page-layout-demo-option">
<p></p>
<div class="row">
<div class="col-6 d-flex align-items-center">
<Switch @bind-Value="@IsFixedHeader" OnColor="@Color.Success" OffColor="@Color.Secondary"></Switch>
</div>
<div class="col-6 text-end">
<span></span>
</div>
</div>
<div class="row mt-3">
<div class="col-6 d-flex align-items-center">
<Switch @bind-Value="@IsFixedFooter" OnColor="@Color.Success" OffColor="@Color.Secondary"></Switch>
</div>
<div class="col-6 text-end">
<span></span>
</div>
</div>
<div class="row mt-3">
<div class="col-6 d-flex align-items-center">
<Switch @bind-Value="@ShowFooter" OnColor="@Color.Success" OffColor="@Color.Secondary"></Switch>
</div>
<div class="col-6 text-end">
<span></span>
</div>
</div>
</div>
<div class="page-layout-demo-option">
<p></p>
<div class="row">
<div class="col-2">
<span class="color color1" @onclick="@(e => Theme = "color1")"></span>
</div>
<div class="col-2">
<span class="color color2" @onclick="@(e => Theme = "color2")"></span>
</div>
<div class="col-2">
<span class="color color3" @onclick="@(e => Theme = "color3")"></span>
</div>
<div class="col-2">
<span class="color color4" @onclick="@(e => Theme = "color4")"></span>
</div>
<div class="col-2">
<span class="color color5" @onclick="@(e => Theme = "color5")"></span>
</div>
<div class="col-2">
<span class="color color6" @onclick="@(e => Theme = "color6")"></span>
</div>
</div>
</div>
<div class="page-layout-demo-option">
<p></p>
<div class="row">
<div class="col-6 d-flex align-items-center">
<Switch @bind-Value="@UseTabSet" OnColor="@Color.Success" OffColor="@Color.Primary"></Switch>
</div>
<div class="col-6 text-end">
<span>@(UseTabSet ? "多标签" : "单页")</span>
</div>
</div>
</div>
</div>
</Drawer>
</CascadingValue>

View File

@ -24,37 +24,31 @@ public sealed partial class PageLayout
/// <summary>
/// 获得/设置 是否固定 TabHeader
/// </summary>
[Parameter]
public bool IsFixedTab { get; set; }
/// <summary>
/// 获得/设置 是否固定页头
/// </summary>
[Parameter]
public bool IsFixedHeader { get; set; } = true;
/// <summary>
/// 获得/设置 是否固定页脚
/// </summary>
[Parameter]
public bool IsFixedFooter { get; set; } = true;
/// <summary>
/// 获得/设置 侧边栏是否外置
/// </summary>
[Parameter]
public bool IsFullSide { get; set; } = true;
/// <summary>
/// 获得/设置 是否显示页脚
/// </summary>
[Parameter]
public bool ShowFooter { get; set; } = true;
/// <summary>
/// 获得/设置 是否开启多标签模式
/// </summary>
[Parameter]
public bool UseTabSet { get; set; } = true;
/// <summary>
@ -80,9 +74,4 @@ public sealed partial class PageLayout
/// 更新组件方法
/// </summary>
public void Update() => StateHasChanged();
private void ToggleDrawer()
{
IsOpen = !IsOpen;
}
}

View File

@ -1,310 +0,0 @@
.layout-logo {
border: solid 1px #fff;
}
.color {
width: 1.5rem;
height: 1.5rem;
display: block;
cursor: pointer;
border: 2px solid #e9ecef;
border-radius: var(--bs-border-radius);
transition: border .3s linear;
}
.color:hover {
border: 2px solid #28a745;
}
.color1 {
background-color: #409eff;
}
.color2 {
background-color: #28a745;
}
.color3 {
background-color: #e83e8c;
}
.color4 {
background-color: #ffe484;
}
.color5 {
background-color: #17a2b8;
}
.color6 {
background-color: #4a3275;
}
.color1,
.layout.is-page.color1 .layout-header {
background-color: #409eff;
}
.layout.is-page.color1 .layout-side .layout-banner {
background-color: #3e84d0
}
.layout.is-page.color1 .layout-side {
background-color: #212529;
color: var(--bb-disabled-bg);
}
.layout.is-page.color1 .layout-footer {
background-color: #343a40;
}
.layout.is-page.color1 .layout-header-bar {
background-color: #2b7cd0;
border-color: #014186;
}
.layout.is-page.color1 .layout-drawer:hover {
color: #fff;
}
.color2,
.layout.is-page.color2 .layout-header {
background-color: #28a745;
}
.layout.is-page.color2 .layout-side .layout-banner {
background-color: #24903d
}
.layout.is-page.color2 .layout-side {
background-color: #212529;
color: var(--bb-disabled-bg);
}
.layout.is-page.color2 .layout-footer {
background-color: #343a40;
}
.layout.is-page.color2 .layout-header-bar {
background-color: #258c3c;
border-color: #014186;
}
.layout.is-page.color2 .layout-drawer:hover {
background-color: #24903d;
}
.color3,
.layout.is-page.color3 .layout-header {
background-color: #e83e8c;
}
.layout.is-page.color3 .layout-side .layout-banner {
background-color: #c5417e
}
.layout.is-page.color3 .layout-side {
background-color: #212529;
color: var(--bb-disabled-bg);
}
.layout.is-page.color3 .layout-footer {
background-color: #343a40;
}
.layout.is-page.color3 .layout-header-bar {
background-color: #c73477;
border-color: #014186;
}
.layout.is-page.color3 .layout-drawer:hover {
background-color: #c5417e;
}
.color4,
.layout.is-page.color4 .layout-header {
background-color: #ffc107;
}
.layout.is-page.color4 .layout-side .layout-banner {
background-color: #e4af10
}
.layout.is-page.color4 .layout-side {
background-color: #212529;
color: var(--bb-disabled-bg);
}
.layout.is-page.color4 .layout-footer {
background-color: #343a40;
}
.layout.is-page.color4 .layout-header-bar {
background-color: #e2b221;
border-color: #014186;
}
.layout.is-page.color4 .layout-drawer:hover {
background-color: #e4af10;
}
.color5,
.layout.is-page.color5 .layout-header {
background-color: #17a2b8;
}
.color6,
.layout.is-page.color6 .layout-header {
background-color: #6610f2;
}
.layout.is-page.color6 .layout-side .layout-banner {
background-color: #4b0cb3
}
.layout.is-page.color6 .layout-side {
background-color: #212529;
color: var(--bb-disabled-bg);
}
.layout.is-page.color6 .layout-footer {
background-color: #343a40;
}
.layout.is-page.color6 .layout-header-bar {
background-color: #4b0ab5;
border-color: #014186;
}
.layout.is-page.color6 .layout-drawer:hover {
background-color: #4b0cb3;
}
.layout-drawer {
padding: 13px;
margin-inline-end: -1rem;
cursor: pointer;
}
.layout-drawer:hover {
color: #fff;
}
.layout-drawer-body {
padding: 1rem;
}
.layout-item {
cursor: pointer;
border: 2px solid #e9ecef;
padding: 4px;
border-radius: var(--bs-border-radius);
height: 80px;
width: 120px;
transition: border .3s linear;
}
.layout-item:hover,
.layout-item.active {
border: 2px solid #28a745;
}
.layout-item .layout-left {
width: 30%;
}
.layout-item .layout-left .layout-left-header {
height: 16px;
background-color: var(--bb-layout-sidebar-banner-background);
}
.layout-item .layout-left .layout-left-body,
.layout-item .layout-body .layout-left {
background-color: #2f4050;
}
.layout-item .layout-right .layout-right-header,
.layout-item .layout-top {
background-color: #17a2b8;
height: 16px;
}
.layout-item .layout-right .layout-right-footer,
.layout-item .layout-footer {
background-color: #5b6e84;
height: 12px;
}
.layout-item .layout-top,
.layout-item .layout-body,
.layout-item .layout-footer {
width: 100%;
}
.layout.is-page .layout-right {
background-color: #fff;
}
::deep + .widget .dropdown-body h3 {
color: #666666;
font-size: 14px;
margin-bottom: 10px;
}
::deep + .widget .dropdown-body h4 {
color: #444444;
font-size: 15px;
margin: 0;
}
::deep + .widget .dropdown-body small {
color: #999999;
font-size: 10px;
position: absolute;
top: 0;
right: 0;
}
::deep + .widget .dropdown-item > div:not(.progress):last-child {
width: calc(100% - 40px);
}
::deep + .widget .dropdown-item {
padding: 0.5rem 1rem;
}
::deep + .widget .progress {
height: 7px;
}
::deep + .widget .dropdown-item.active,
::deep + .widget .dropdown-item:active {
color: inherit;
}
::deep + .widget .dropdown-item:not(:nth-of-type(odd)):active {
background-color: inherit;
}
.page-layout-demo-option {
margin-top: 1.5rem;
border: 1px solid rgba(0,0,0,.125);
border-radius: var(--bs-border-radius);
padding: 1.5rem 1rem 1rem 1rem;
position: relative;
}
.page-layout-demo-option > p {
position: absolute;
top: -10px;
padding: 0 0.5rem;
background: #fff;
}
.page-layout-demo-option .tabs-body-content {
margin: 0 -1rem -2rem -1rem;
}
.page-layout-demo-footer-link {
color: #fff;
}

View File

@ -11,7 +11,7 @@
<p>@Localizer["P1"] <code>Layout</code> @Localizer["P2"] <code>dotnet new bbapp</code> @Localizer["P3"] <code>Visual Studio</code> @Localizer["P4"] <code>@Localizer["P5"]</code> @Localizer["P6"] <a href="template" target="_blank">[@Localizer["P7"]]</a></p>
<div>
<b class="mb-3">@Localizer["P8"]</b>
<code>Layout</code> @Localizer["P9"] <code>@Localizer["P10"]</code> @Localizer["P11"] <code>@Localizer["P12"]</code> @Localizer["P13"] <code>AdditionalAssemblies</code> @Localizer["P14"] <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-3.1#route-to-components-from-multiple-assemblies" target="_blank">[@Localizer["P15"]]</a>
<code>Layout</code> @Localizer["P9"] <code>@Localizer["P10"]</code> @Localizer["P11"] <code>@Localizer["P12"]</code> @Localizer["P13"] <code>AdditionalAssemblies</code> @Localizer["P14"] <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?#route-to-components-from-multiple-assemblies" target="_blank">[@Localizer["P15"]]</a>
</div>
</Tips>
@ -19,31 +19,31 @@
<p>@Localizer["P16"]</p>
<div class="row">
<div class="col-12">
<RadioList TValue="SelectedItem" Items="@SideBarItems" OnSelectedChanged="@OnSideChanged" />
<RadioList Value="ActiveItem" Items="@SideBarItems" OnSelectedChanged="@OnSideChanged" />
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<Checkbox @bind-Value="ShowFooter" OnStateChanged="OnFooterChanged" ShowAfterLabel="true" DisplayText="@Localizer["P17"]" />
<Checkbox Value="ShowFooter" OnValueChanged="OnFooterChanged" ShowAfterLabel="true" DisplayText="@Localizer["P17"]" />
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<Switch @bind-Value="@UseTabSet" OnText="@Localizer["P18"]" OffText="@Localizer["P19"]" OnColor="@Color.Success" OffColor="@Color.Secondary" style="width: 120px;" OnValueChanged="OnUseTabSetChanged"></Switch>
<Switch Value="@UseTabSet" OnText="@Localizer["P18"]" OffText="@Localizer["P19"]" OnColor="@Color.Success" OffColor="@Color.Secondary" style="width: 120px;" OnValueChanged="OnUseTabSetChanged"></Switch>
</div>
</div>
</div>
<div class="page-layout-demo-option">
<p>@Localizer["P20"]</p>
<Checkbox @bind-Value="@IsFixedTab" OnStateChanged="@OnTabStateChanged" ShowAfterLabel="true" DisplayText="@Localizer["P21"]" />
<Checkbox @bind-Value="@IsFixedHeader" OnStateChanged="@OnHeaderStateChanged" IsDisabled="IsFixedTab" ShowAfterLabel="true" DisplayText="@Localizer["P22"]" class="mx-3" />
<Checkbox @bind-Value="@IsFixedFooter" OnStateChanged="@OnFooterStateChanged" IsDisabled="IsFixedTab" ShowAfterLabel="true" DisplayText="@Localizer["P23"]" />
<Checkbox Value="@IsFixedTab" OnStateChanged="@OnTabStateChanged" ShowAfterLabel="true" DisplayText="@Localizer["P21"]" />
<Checkbox Value="@IsFixedHeader" OnStateChanged="@OnHeaderStateChanged" IsDisabled="IsFixedTab" ShowAfterLabel="true" DisplayText="@Localizer["P22"]" class="mx-3" />
<Checkbox Value="@IsFixedFooter" OnStateChanged="@OnFooterStateChanged" IsDisabled="IsFixedTab" ShowAfterLabel="true" DisplayText="@Localizer["P23"]" />
</div>
<div class="page-layout-demo-option">
<p>@Localizer["P24"]</p>
<div>@Localizer["P25"] <code>Header</code> @Localizer["P26"]</div>
<div>@Localizer["P25"] <code>Header</code> @((MarkupString)Localizer["P26"].Value)</div>
</div>
<div class="page-layout-demo-option">

View File

@ -10,11 +10,14 @@ namespace BootstrapBlazor.Server.Components.Samples;
/// </summary>
public sealed partial class LayoutPages
{
private IEnumerable<SelectedItem> SideBarItems { get; set; } = new SelectedItem[]
{
new SelectedItem("left-right", "左右结构"),
new SelectedItem("top-bottom", "上下结构")
};
private List<SelectedItem> SideBarItems { get; } =
[
new("left-right", "左右结构"),
new("top-bottom", "上下结构")
];
[NotNull]
private SelectedItem? ActiveItem { get; set; }
private string? StyleString => CssBuilder.Default()
.AddClass($"height: {Height * 100}px", Height > 0)
@ -63,9 +66,9 @@ public sealed partial class LayoutPages
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override async Task OnInitializedAsync()
protected override void OnInitialized()
{
await base.OnInitializedAsync();
base.OnInitialized();
IsFullSide = RootPage.IsFullSide;
IsFixedHeader = RootPage.IsFixedHeader;
@ -73,44 +76,67 @@ public sealed partial class LayoutPages
ShowFooter = RootPage.ShowFooter;
UseTabSet = RootPage.UseTabSet;
SideBarItems.ElementAt(IsFullSide ? 0 : 1).Active = true;
ActiveItem = IsFullSide ? SideBarItems[0] : SideBarItems[1];
}
private Task OnFooterChanged(CheckboxState state, bool val) => UpdateAsync();
private Task OnTabStateChanged(CheckboxState state, bool val) => UpdateAsync();
private Task OnHeaderStateChanged(CheckboxState state, bool val) => UpdateAsync();
private Task OnFooterStateChanged(CheckboxState state, bool val) => UpdateAsync();
private async Task OnSideChanged(IEnumerable<SelectedItem> values, SelectedItem item)
private Task OnFooterChanged(bool val)
{
IsFullSide = item.Value == "left-right";
await UpdateAsync();
ShowFooter = val;
Update();
return Task.CompletedTask;
}
private Task OnUseTabSetChanged(bool val) => UpdateAsync();
private Task OnTabStateChanged(CheckboxState state, bool val)
{
IsFixedTab = val;
Update();
return Task.CompletedTask;
}
private Task OnHeaderStateChanged(CheckboxState state, bool val)
{
IsFixedHeader = val;
Update();
return Task.CompletedTask;
}
private Task OnFooterStateChanged(CheckboxState state, bool val)
{
IsFixedFooter = val;
Update();
return Task.CompletedTask;
}
private Task OnSideChanged(IEnumerable<SelectedItem> values, SelectedItem item)
{
ActiveItem.Active = false;
item.Active = true;
ActiveItem = item;
IsFullSide = item.Value == "left-right";
Update();
return Task.CompletedTask;
}
private Task OnUseTabSetChanged(bool val)
{
UseTabSet = val;
Update();
return Task.CompletedTask;
}
/// <summary>
/// UpdateAsync 方法
/// </summary>
/// <returns></returns>
public async Task UpdateAsync()
public void Update()
{
var parameters = new Dictionary<string, object?>()
{
[nameof(RootPage.IsFullSide)] = IsFullSide,
[nameof(RootPage.IsFixedFooter)] = IsFixedFooter && ShowFooter,
[nameof(RootPage.IsFixedHeader)] = IsFixedHeader,
[nameof(RootPage.IsFixedTab)] = IsFixedTab,
[nameof(RootPage.ShowFooter)] = ShowFooter,
[nameof(RootPage.UseTabSet)] = UseTabSet
};
await RootPage.SetParametersAsync(ParameterView.FromDictionary(parameters));
// 获得 Razor 示例代码
RootPage.IsFullSide = IsFullSide;
RootPage.IsFixedFooter = IsFixedFooter && ShowFooter;
RootPage.IsFixedHeader = IsFixedHeader;
RootPage.IsFixedTab = IsFixedTab;
RootPage.ShowFooter = ShowFooter;
RootPage.UseTabSet = UseTabSet;
StateHasChanged();
RootPage.Update();
}

View File

@ -81,14 +81,6 @@ public sealed partial class Layouts
DefaultValue = "false"
},
new()
{
Name = "IsPage",
Description = Localizer["Layouts_IsPage_Description"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = "IsFixedFooter",
Description = Localizer["Layouts_IsFixedFooter_Description"],

View File

@ -62,7 +62,7 @@
</Tips>
<p>@((MarkupString)Localizer["MenusClickShrinkDescription"].Value)</p>
<div class="layout-menu-demo">
<Layout SideWidth="0" IsFullSide="true" ShowFooter="true" ShowCollapseBar="true" Menus="IconSideMenuItems">
<Layout IsFullSide="true" ShowFooter="true" ShowCollapseBar="true" Menus="IconSideMenuItems">
<Header>
<div class="menu-demo-header">
@Localizer["MenusClickShrinkSpanSpan"]

View File

@ -1418,7 +1418,7 @@
"P23": "fixed footer",
"P24": "Shrink adjustment",
"P25": "Please click",
"P26": "small green button in",
"P26": "small <b>button</b>",
"P27": "height adjustment",
"P28": "view height",
"P29": "Tab Test",
@ -1468,7 +1468,6 @@
"Layouts_Footer_Description": "Footer component template",
"Layouts_Menus_Description": "Sidebar menu data collection when the whole page is laid out",
"Layouts_IsFullSide_Description": "Whether the sidebar occupies the entire left",
"Layouts_IsPage_Description": "Whether it is the whole page layout",
"Layouts_IsFixedFooter_Description": "Whether to fix the Footer component",
"Layouts_IsFixedHeader_Description": "Whether to fix the Header component",
"Layouts_ShowCollapseBar_Description": "Whether to show contraction and expansion Bar",
@ -2034,7 +2033,7 @@
"TabsTipsTitle": "<code>Tab</code> components are generally used in two ways:",
"TabsTips1": "Split as data",
"TabsTips2": "Page navigation",
"TabsTips3": "The default behavior of this component is data segmentation,Clicking on the <code>TabItem</code> title does not navigate, and if you need to navigate the address bar, set the <code>ClickTabToNavigation</code> property to <code>true</code>When you click on the <code>TabItem</code> title, the address bar redirects navigation, mostly for the background management system to be used in conjunction with the <code>Menu</code> componentsThe actual combat can refer to the <code>multi-label</code> mode in the <a href='layout-page' target='_blank'>background template simulator</a>, When you have <code>Razor Component</code> in the additional Assemblies, set the <code>AdditionalAssemblies</code> property value correctly so that the route within the label component is resolved correctly, and the relevant documentation <a href='https://docs.microsoft.com/zh-cn/aspnet/core/blazor/fundamentals/routing?WT.mc_id-DT-MVP-5004174-view-aspnetcore-3.1-route-to-components-from-multiple-assemblies' target='_blank'>[Portal]</a>",
"TabsTips3": "The default behavior of this component is data segmentation,Clicking on the <code>TabItem</code> title does not navigate, and if you need to navigate the address bar, set the <code>ClickTabToNavigation</code> property to <code>true</code>When you click on the <code>TabItem</code> title, the address bar redirects navigation, mostly for the background management system to be used in conjunction with the <code>Menu</code> componentsThe actual combat can refer to the <code>multi-label</code> mode in the <a href='layout-page' target='_blank'>background template simulator</a>, When you have <code>Razor Component</code> in the additional Assemblies, set the <code>AdditionalAssemblies</code> property value correctly so that the route within the label component is resolved correctly, and the relevant documentation <a href='https://learn.microsoft.com/aspnet/core/blazor/fundamentals/routing?wt.mc_id=DT-MVP-5004174' target='_blank'>[Portal]</a>",
"TabsTips4": "This component adapts to width height, etc., and scroll arrows can appear left and right or up and down when appropriate",
"TabsInfoTitle": "Set additional information during tab navigation",
"TabsItemMenuTitle": "Configure Tab and Menu linked dictionary, this method can automatically obtain tab page properties without separately setting TabItemOption.",

View File

@ -1418,7 +1418,7 @@
"P23": "固定页脚",
"P24": "收缩调整",
"P25": "请点击",
"P26": "中的绿色小按钮",
"P26": "中的 <b>按钮</b>",
"P27": "高度调整",
"P28": "视图高度",
"P29": "Tab 测试",
@ -1468,7 +1468,6 @@
"Layouts_Footer_Description": "页脚组件模板",
"Layouts_Menus_Description": "整页面布局时侧边栏菜单数据集合",
"Layouts_IsFullSide_Description": "侧边栏是否占满整个左边",
"Layouts_IsPage_Description": "是否为整页面布局",
"Layouts_IsFixedFooter_Description": "是否固定 Footer 组件",
"Layouts_IsFixedHeader_Description": "是否固定 Header 组件",
"Layouts_ShowCollapseBar_Description": "是否显示收缩展开 Bar",
@ -2034,7 +2033,7 @@
"TabsTipsTitle": "<code>Tab</code> 组件一般有两种用法:",
"TabsTips1": "作为数据分割",
"TabsTips2": "页面导航",
"TabsTips3": "本组件默认行为为数据分割,点击 <code>TabItem</code> 标题时并不会进行导航行为,如果需要进行地址栏导航时请设置 <code>ClickTabToNavigation</code> 属性为 <code>true</code>,此时点击 <code>TabItem</code> 标题时地址栏将会重定向导航,多用于后台管理系统与 <code>Menu</code> 组件进行联动使用,实战可参考 <a href='layout-page' target='_blank'>后台模板模拟器</a> 中的 <code>多标签</code> 模式,如果有 <code>Razor 组件</code> 在额外的程序集中时,请正确设置 <code>AdditionalAssemblies</code> 属性值,以便标签组件内路由正确解析,相关文档 <a href='https://docs.microsoft.com/zh-cn/aspnet/core/blazor/fundamentals/routing?WT.mc_id=DT-MVP-5004174&view=aspnetcore-3.1#route-to-components-from-multiple-assemblies' target='_blank'>[传送门]</a>",
"TabsTips3": "本组件默认行为为数据分割,点击 <code>TabItem</code> 标题时并不会进行导航行为,如果需要进行地址栏导航时请设置 <code>ClickTabToNavigation</code> 属性为 <code>true</code>,此时点击 <code>TabItem</code> 标题时地址栏将会重定向导航,多用于后台管理系统与 <code>Menu</code> 组件进行联动使用,实战可参考 <a href='layout-page' target='_blank'>后台模板模拟器</a> 中的 <code>多标签</code> 模式,如果有 <code>Razor 组件</code> 在额外的程序集中时,请正确设置 <code>AdditionalAssemblies</code> 属性值,以便标签组件内路由正确解析,相关文档 <a href='https://learn.microsoft.com/zh-cn/aspnet/core/blazor/fundamentals/routing?wt.mc_id=DT-MVP-5004174' target='_blank'>[传送门]</a>",
"TabsTips4": "本组件会根据宽度高度等进行自适应适配,适当的时候可以出现左右或者上下的滚动箭头",
"TabsInfoTitle": "标签页导航时设置附加信息",
"TabsItemMenuTitle": "配置 Tab 与 Menu 联动字典,该方法无需再单独设置 TabItemOption 即可自动获取标签页属性。",

View File

@ -0,0 +1,80 @@
.layout {
--bb-layout-headerbar-background: var(--bb-primary-color);
--bb-layout-logo-bg: var(--bb-primary-color);
}
.layout-drawer {
padding: 13px;
margin-inline-end: -1rem;
cursor: pointer;
}
.layout-drawer:hover {
color: #fff;
}
.layout-item {
cursor: pointer;
border: 2px solid #e9ecef;
padding: 4px;
border-radius: var(--bs-border-radius);
height: 80px;
width: 120px;
transition: border .3s linear;
}
.layout-item:hover,
.layout-item.active {
border: 2px solid #28a745;
}
.layout-item .layout-left {
width: 30%;
}
.layout-item .layout-left .layout-left-header {
height: 16px;
background-color: var(--bb-layout-sidebar-banner-background);
}
.layout-item .layout-left .layout-left-body,
.layout-item .layout-body .layout-left {
background-color: #2f4050;
}
.layout-item .layout-right .layout-right-header,
.layout-item .layout-top {
background-color: #17a2b8;
height: 16px;
}
.layout-item .layout-right .layout-right-footer,
.layout-item .layout-footer {
background-color: #5b6e84;
height: 12px;
}
.layout-item .layout-top,
.layout-item .layout-body,
.layout-item .layout-footer {
width: 100%;
}
.page-layout-demo-option {
margin-top: 1.5rem;
border: 1px solid rgba(0,0,0,.125);
border-radius: var(--bs-border-radius);
padding: 1.5rem 1rem 1rem 1rem;
position: relative;
}
.page-layout-demo-option > p {
position: absolute;
top: -10px;
padding: 0 0.5rem;
background: #fff;
}
.page-layout-demo-option .tabs-body-content {
margin: 0 -1rem -2rem -1rem;
}

View File

@ -71,6 +71,7 @@ h3, h4, h5 {
.layout {
--bb-footer-height: 0px;
--bb-layout-menu-item-hover-bg: var(--bb-primary-color);
}
.layout.has-footer {

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>9.0.1-beta02</Version>

View File

@ -10,7 +10,7 @@
<section @attributes="AdditionalAttributes" class="@ClassString" style="@StyleString">
@if (Side == null)
{
if(Header != null)
if (Header != null)
{
@RenderHeader(false)
}
@ -23,7 +23,7 @@
else if (IsFullSide)
{
<aside class="@SideClassString" style="@SideStyleString">
@if(Side != null)
@if (Side != null)
{
@Side
}
@ -33,7 +33,7 @@
}
</aside>
<section class="layout-right">
@if(Header != null)
@if (Header != null)
{
@RenderHeader(ShowCollapseBar)
}
@ -88,9 +88,9 @@
{
@if (CollapseBarTemplate == null)
{
<a title="" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-original-title="@TooltipText" class="@CollapseBarClassString" @onclick="CollapseMenu">
<div data-bs-toggle="tooltip" data-bs-placement="right" data-bs-original-title="@TooltipText" class="layout-header-bar" @onclick="ToggleSidebar">
<i class="@MenuBarIcon"></i>
</a>
</div>
}
else
{
@ -105,17 +105,17 @@
@if (hasScroll)
{
<Scroll>
<Menu Items="@Menus" IsVertical="true" IsCollapsed="@IsCollapsed" IsAccordion="@IsAccordion" OnClick="@ClickMenu()"></Menu>
<Menu Items="@Menus" IsVertical="true" IsAccordion="@IsAccordion" OnClick="ClickMenu"></Menu>
</Scroll>
}
else
{
<Menu Items="@Menus" IsVertical="true" IsCollapsed="@IsCollapsed" IsAccordion="@IsAccordion" OnClick="@ClickMenu()"></Menu>
<Menu Items="@Menus" IsVertical="true" IsAccordion="@IsAccordion" OnClick="ClickMenu"></Menu>
}
</div>;
RenderFragment RenderMain =>
@<main class="@MainClassString">
@<main class="layout-main">
@if (UseTabSet)
{
<Tab ClickTabToNavigation="ClickTabToNavigation" AdditionalAssemblies="@AdditionalAssemblies"

View File

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Localization;
@ -109,6 +108,8 @@ public partial class Layout : IHandlerException
/// </summary>
/// <remarks>为真时增加 is-page 样式</remarks>
[Parameter]
[Obsolete("已弃用直接删除即可Deprecated Please remove it")]
[ExcludeFromCodeCoverage]
public bool IsPage { get; set; }
/// <summary>
@ -233,8 +234,8 @@ public partial class Layout : IHandlerException
/// </summary>
private string? ClassString => CssBuilder.Default("layout")
.AddClass("has-sidebar", Side != null && IsFullSide)
.AddClass("is-page", IsPage)
.AddClass("has-footer", ShowFooter && Footer != null)
.AddClass("is-collapsed", IsCollapsed)
.AddClassFromAttributes(AdditionalAttributes)
.Build();
@ -249,7 +250,6 @@ public partial class Layout : IHandlerException
/// </summary>
private string? FooterClassString => CssBuilder.Default("layout-footer")
.AddClass("is-fixed", IsFixedFooter)
.AddClass("is-collapsed", IsCollapsed)
.Build();
/// <summary>
@ -263,7 +263,6 @@ public partial class Layout : IHandlerException
/// 获得 侧边栏样式
/// </summary>
private string? SideClassString => CssBuilder.Default("layout-side")
.AddClass("is-collapsed", IsCollapsed)
.AddClass("is-fixed-header", IsFixedHeader)
.AddClass("is-fixed-footer", IsFixedFooter)
.Build();
@ -272,21 +271,7 @@ public partial class Layout : IHandlerException
/// 获得 侧边栏 Style 字符串
/// </summary>
private string? SideStyleString => CssBuilder.Default()
.AddClass($"width: {SideWidth.ConvertToPercentString()}", !IsCollapsed && !string.IsNullOrEmpty(SideWidth) && SideWidth != "0")
.Build();
/// <summary>
/// 获得 Main 样式
/// </summary>
private string? MainClassString => CssBuilder.Default("layout-main")
.AddClass("is-collapsed", IsCollapsed)
.Build();
/// <summary>
/// 获得 展开收缩 Bar 样式
/// </summary>
private string? CollapseBarClassString => CssBuilder.Default("layout-header-bar")
.AddClass("is-collapsed", IsCollapsed)
.AddClass($"--bb-layout-sidebar-width: {SideWidth.ConvertToPercentString()}", !string.IsNullOrEmpty(SideWidth) && SideWidth != "0")
.Build();
/// <summary>
@ -336,6 +321,8 @@ public partial class Layout : IHandlerException
private bool _init { get; set; }
//private bool _isInteractive = true;
/// <summary>
/// <inheritdoc/>
/// </summary>
@ -350,6 +337,10 @@ public partial class Layout : IHandlerException
}
ErrorLogger?.Register(this);
#if NET9_0_OR_GREATER
//_isInteractive = RendererInfo.IsInteractive;
#endif
}
/// <summary>
@ -395,8 +386,6 @@ public partial class Layout : IHandlerException
base.OnParametersSet();
TooltipText ??= Localizer[nameof(TooltipText)];
SideWidth ??= "300";
MenuBarIcon ??= IconTheme.GetIconByKey(ComponentIcons.LayoutMenuBarIcon);
}
@ -455,12 +444,26 @@ public partial class Layout : IHandlerException
}
/// <summary>
/// 点击 收缩展开按钮时回调此方法
/// 点击菜单时回调此方法
/// </summary>
/// <returns></returns>
private async Task CollapseMenu()
private async Task ClickMenu(MenuItem item)
{
// 小屏幕时生效
if (IsSmallScreen && !item.Items.Any())
{
IsCollapsed = false;
await TriggerCollapseChanged();
}
if (OnClickMenu != null)
{
await OnClickMenu(item);
}
}
private async Task TriggerCollapseChanged()
{
IsCollapsed = !IsCollapsed;
if (IsCollapsedChanged.HasDelegate)
{
await IsCollapsedChanged.InvokeAsync(IsCollapsed);
@ -472,23 +475,12 @@ public partial class Layout : IHandlerException
}
}
/// <summary>
/// 点击菜单时回调此方法
/// </summary>
/// <returns></returns>
private Func<MenuItem, Task> ClickMenu() => async item =>
private async Task ToggleSidebar()
{
// 小屏幕时生效
if (IsSmallScreen && !item.Items.Any())
{
await CollapseMenu();
}
IsCollapsed = !IsCollapsed;
if (OnClickMenu != null)
{
await OnClickMenu(item);
}
};
await TriggerCollapseChanged();
}
/// <summary>
/// 上次渲染错误内容

View File

@ -10,7 +10,7 @@ export function init(id, invoke, callback) {
}
Data.set(id, layout)
const tooltip = document.querySelector('.layout-header [data-bs-toggle="tooltip"]')
const tooltip = document.querySelector('.layout-header-bar')
if (tooltip) {
layout.tooltip = bootstrap.Tooltip.getOrCreateInstance(tooltip)
}

View File

@ -42,166 +42,139 @@
height: 100%;
width: 100%;
flex-direction: column;
position: relative;
.layout-main {
flex: 1;
.layout-banner {
display: flex;
align-items: center;
padding: 0 0.625rem;
height: var(--bb-layout-header-height);
font-size: var(--bb-layout-banner-font-size);
border-bottom: 1px solid var(--bb-layout-banner-border-color);
background-color: var(--bb-layout-sidebar-banner-background);
.layout-title {
display: inline-block;
color: var(--bb-layout-title-color);
overflow: hidden;
white-space: nowrap;
flex: 1 1 auto;
opacity: 1;
transition: opacity .3s linear;
}
.layout-logo {
width: var(--bb-layout-banner-logo-width);
border-radius: var(--bs-border-radius);
background-color: var(--bb-layout-logo-bg);
border: 1px solid var(--bb-layout-logo-border-color);
+ .layout-title {
margin-left: var(--bb-layout-title-margin-left);
}
}
}
&.is-page {
.layout-side {
transform: translateX(-100%);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: auto;
transition: transform .3s linear;
z-index: 1030;
background-color: var(--bb-layout-sidebar-background);
color: var(--bb-layout-sidebar-color);
.has-sidebar {
/*left-right*/
position: relative;
flex: 1;
&.is-collapsed {
transform: translateX(0);
}
.layout-banner {
display: flex;
align-items: center;
padding: 0 0.625rem;
background-color: var(--bb-layout-sidebar-banner-background);
height: var(--bb-layout-header-height);
font-size: var(--bb-layout-banner-font-size);
border-bottom: 1px solid var(--bb-layout-banner-border-color);
.layout-logo {
width: var(--bb-layout-banner-logo-width);
border-radius: var(--bs-border-radius);
background-color: var(--bb-layout-logo-bg);
border: 1px solid var(--bb-layout-logo-border-color);
+ .layout-title {
margin-left: var(--bb-layout-title-margin-left);
}
}
}
.layout-user {
display: none;
}
.layout-menu {
overflow-x: hidden;
padding: 0.5rem 0;
height: calc(100vh - var(--bb-layout-header-height));
.menu {
--bb-menu-item-hover-color: var(--bb-layout-header-color);
}
}
}
.has-sidebar {
.layout-side {
top: var(--bb-layout-header-height);
.layout-banner {
display: none;
}
&.is-fixed-header {
z-index: 1020;
}
}
.layout-main {
width: 100%;
}
}
.layout-right {
width: 100%;
}
&.is-fixed {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.layout-header {
background-color: var(--bb-layout-header-background);
height: var(--bb-layout-header-height);
color: var(--bb-layout-header-color);
display: flex;
align-items: center;
padding: 0 1rem;
position: sticky;
z-index: 1035;
border-bottom: 1px solid var(--bb-layout-header-border-color);
&.is-fixed {
top: 0;
}
.widget {
--bb-widget-toggle-color: var(--bb-layout-header-color);
.progress {
height: 7px;
}
}
.logout-avatar {
border-radius: 50%;
}
.dropdown-logout {
--bb-logout-text-color: var(--bb-layout-header-color);
--bb-logout-user-bg: var(--bb-layout-menu-user-banner-background);
--bb-logout-menu-border-color: var(--bb-layout-menu-user-border-color);
.dropdown-user img {
border-radius: 50%;
}
}
.layout-banner {
display: none;
}
.layout-menu {
border-right: 1px solid var(--bs-border-color);
.menu {
--bb-menu-item-hover-bg: var(--bb-layout-menu-item-hover-bg);
}
height: 100%;
}
.layout-main {
padding: 1rem;
position: relative;
min-height: calc(100vh - var(--bb-layout-header-height) - var(--bb-layout-footer-height));
width: 100%;
}
}
.layout-menu {
overflow-x: hidden;
padding: 0.5rem 0;
height: calc(100% - var(--bb-layout-header-height));
.menu {
--bb-menu-item-hover-bg: var(--bb-layout-menu-item-hover-bg);
--bb-menu-item-hover-color: var(--bb-layout-header-color);
}
}
.layout-side {
background-color: var(--bb-layout-sidebar-background);
color: var(--bb-layout-sidebar-color);
transition: transform .3s linear;
transform: translateX(-100%);
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1036;
}
.layout-main {
padding: 1rem;
position: relative;
min-height: calc(100vh - var(--bb-layout-header-height) - var(--bb-layout-footer-height));
flex: 1;
.tabs.tabs-border-card {
box-shadow: none;
}
}
.layout-header {
background-color: var(--bb-layout-header-background);
height: var(--bb-layout-header-height);
color: var(--bb-layout-header-color);
display: flex;
align-items: center;
padding: 0 1rem;
position: sticky;
z-index: 1035;
border-bottom: 1px solid var(--bb-layout-header-border-color);
&.is-fixed {
top: 0;
}
.layout-footer {
&.is-collapsed {
display: none;
.widget {
--bb-widget-toggle-color: var(--bb-layout-header-color);
.progress {
height: 7px;
}
}
.layout-footer {
height: var(--bb-layout-footer-height);
color: var(--bb-layout-footer-color);
background-color: var(--bb-layout-footer-background);
display: flex;
align-items: center;
padding: 0 1rem;
border-top: 1px solid var(--bs-border-color);
.logout-avatar {
border-radius: 50%;
}
&.is-fixed {
z-index: 1020;
position: sticky;
bottom: 0;
.dropdown-logout {
--bb-logout-text-color: var(--bb-layout-header-color);
--bb-logout-user-bg: var(--bb-layout-menu-user-banner-background);
--bb-logout-menu-border-color: var(--bb-layout-menu-user-border-color);
.dropdown-user img {
border-radius: 50%;
}
}
.layout-header-bar {
padding: var(--bb-layout-headerbar-padding);
color: var(--bb-layout-headerbar-color);
background-color: var(--bb-layout-headerbar-background);
border: var(--bs-border-width) solid var(--bb-layout-headerbar-border-color);
border-radius: var(--bs-border-radius);
cursor: pointer;
.fa-bars {
transition: transform .3s linear;
}
}
}
@ -209,137 +182,118 @@
&:not(.has-footer) {
--bb-layout-footer-height: 0px;
}
&.is-collapsed {
.fa-bars {
transform: rotate(90deg);
}
.layout-side {
transform: translateX(0);
}
.layout-right {
overflow: hidden;
height: 100vh;
}
.layout-footer {
display: none;
}
.has-sidebar {
.layout-side {
z-index: 1030;
}
.layout-main {
overflow: hidden;
height: calc(100vh - var(--bb-layout-header-height));
}
}
}
.layout-footer {
height: var(--bb-layout-footer-height);
color: var(--bb-layout-footer-color);
background-color: var(--bb-layout-footer-background);
display: flex;
align-items: center;
padding: 0 1rem;
border-top: 1px solid var(--bs-border-color);
&.is-fixed {
z-index: 1035;
position: sticky;
bottom: 0;
}
}
}
.has-sidebar {
flex-direction: row;
display: flex;
flex: 1;
}
.layout-side {
position: relative;
}
@media(min-width: 768px) {
.layout {
.layout-side {
position: relative;
width: var(--bb-layout-sidebar-width);
transform: translateX(0);
transition: width .3s linear;
.layout-header-bar {
padding: var(--bb-layout-headerbar-padding);
color: var(--bb-layout-headerbar-color);
background-color: var(--bb-layout-headerbar-background);
border: var(--bs-border-width) solid var(--bb-layout-headerbar-border-color);
border-radius: var(--bs-border-radius);
.layout-menu {
border-right: 1px solid var(--bs-border-color);
}
}
.fa-bars {
transition: transform .3s linear;
}
.layout-right {
display: flex;
flex-flow: column;
flex: 1;
}
&.is-collapsed .fa-bars {
transform: rotate(90deg);
}
}
.layout-banner-link {
display: flex;
align-items: center;
color: inherit;
}
@media (min-width: 768px) {
.layout.is-page .layout-side {
transform: translateX(0);
position: relative;
}
.layout.is-page .has-sidebar .layout-side {
top: 0;
}
.layout.is-page.has-sidebar .layout-side.is-fixed-header {
top: 0;
height: calc(100vh);
}
.layout.is-page :not(.layout-right) .layout-main,
.layout.is-page .layout-right {
width: calc(100% - var(--bb-layout-sidebar-width));
}
.layout.is-page .layout-side {
width: var(--bb-layout-sidebar-width);
}
.layout.is-page .layout-side.is-collapsed:not(:hover),
.layout.is-page .layout-side:not(:hover) .menu.is-vertical.is-collapsed {
width: var(--bb-layout-sidebar-collapse-width);
}
.layout.is-page .layout-side.is-fixed-header {
position: sticky;
top: var(--bb-layout-header-height);
height: calc(100vh - var(--bb-layout-header-height));
}
.layout.is-page .layout-side.is-fixed-header.is-fixed-footer {
height: calc(100vh - var(--bb-layout-header-height) - var(--bb-layout-footer-height));
}
.layout.is-page .layout-side .layout-menu {
height: auto;
}
.layout .layout-side,
.layout.is-page .layout-side,
.layout .menu.is-vertical {
transition: width .3s linear;
}
.layout-right {
display: flex;
flex-flow: column;
flex: 1;
}
.layout.is-page .layout-footer.is-fixed,
.layout.is-page .layout-header.is-fixed,
.layout.is-page .layout-side.is-fixed-header {
z-index: 1025;
}
.layout.is-page .layout-title {
display: inline-block;
overflow: hidden;
white-space: nowrap;
flex: 1 1 auto;
opacity: 1;
transition: opacity .3s linear;
color: var(--bb-layout-title-color);
}
.layout.is-page .is-collapsed .layout-title {
opacity: 0;
}
.layout.is-page.has-sidebar .layout-side {
min-height: calc(100vh);
}
.layout.is-page .has-sidebar .layout-side {
min-height: calc(100vh - var(--bb-layout-header-height) - var(--bb-layout-footer-height));
}
.layout.is-page.has-sidebar .layout-side.is-fixed-header.is-fixed-footer .layout-menu,
.layout.is-page .layout-side.is-fixed-header .layout-menu {
height: calc(100vh - var(--bb-layout-header-height));
}
.layout.is-page .layout-side.is-fixed-header.is-fixed-footer .layout-menu {
height: calc(100vh - var(--bb-layout-header-height) - var(--bb-layout-footer-height));
}
.layout.is-page .layout-main.is-collapsed {
display: block;
}
.layout.is-page .layout-footer.is-collapsed {
display: flex;
&.has-sidebar {
.layout-side {
&.is-fixed-header {
position: sticky;
top: 0;
height: 100vh;
}
}
}
&.is-collapsed {
.layout-side {
&:not(:hover) {
--bb-layout-sidebar-width: var(--bb-layout-sidebar-collapse-width);
.menu.is-vertical {
--bb-layout-sidebar-width: var(--bb-layout-sidebar-collapse-width);
}
}
&:hover {
.layout-title,
.menu.is-vertical .menu-text {
opacity: 1;
}
}
}
.layout-main {
display: block;
}
.layout-footer {
display: flex;
}
.layout-title,
.menu.is-vertical .menu-text {
opacity: 0;
}
}
}
}

View File

@ -89,9 +89,10 @@ public class LayoutTest : BootstrapBlazorTestBase
pb.Add(a => a.IsCollapsedChanged, v => collapsed = v);
});
var bar = cut.Find(".layout-header-bar");
await cut.InvokeAsync(() =>
{
cut.Find("header > a").Click();
bar.Click();
});
Assert.True(collapsed);
@ -99,19 +100,6 @@ public class LayoutTest : BootstrapBlazorTestBase
cut.WaitForAssertion(() => Assert.DoesNotContain("<i class=\"fa-solid fa-bars\"></i>", cut.Markup));
}
[Fact]
public void IsPage_OK()
{
var cut = Context.RenderComponent<Layout>(pb =>
{
pb.Add(a => a.IsPage, true);
});
Assert.Contains("is-page", cut.Markup);
cut.SetParametersAndRender(pb => pb.Add(a => a.IsPage, false));
cut.WaitForAssertion(() => Assert.DoesNotContain("is-page", cut.Markup));
}
[Fact]
public void IsFullSide_OK()
{