diff --git a/BlazorBootstrap.Demo.RCL/Pages/Callout/Callout_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Pages/Callout/Callout_Demo_01_Examples.razor index ac8a1b872..8b3683b58 100644 --- a/BlazorBootstrap.Demo.RCL/Pages/Callout/Callout_Demo_01_Examples.razor +++ b/BlazorBootstrap.Demo.RCL/Pages/Callout/Callout_Demo_01_Examples.razor @@ -14,6 +14,10 @@ This is an info callout. Example text to show it in action. See callout documentation. - + This is an tip callout. Example text to show it in action. See callout documentation. - \ No newline at end of file + + + + This is an success callout. Example text to show it in action. See callout documentation. + diff --git a/BlazorBootstrap.Demo.RCL/Pages/Index.razor b/BlazorBootstrap.Demo.RCL/Pages/Index.razor index 647eb3d82..104162964 100644 --- a/BlazorBootstrap.Demo.RCL/Pages/Index.razor +++ b/BlazorBootstrap.Demo.RCL/Pages/Index.razor @@ -170,6 +170,11 @@

Sidebar

+
+ +

Sidebar 2 New

+
+

Spinners New

diff --git a/BlazorBootstrap.Demo.RCL/Pages/Sidebar/SidebarDocumentation.razor b/BlazorBootstrap.Demo.RCL/Pages/Sidebar/SidebarDocumentation.razor index 7878c9803..542a98c95 100644 --- a/BlazorBootstrap.Demo.RCL/Pages/Sidebar/SidebarDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Pages/Sidebar/SidebarDocumentation.razor @@ -17,7 +17,7 @@
Use NavItem's Id and ParentId to set the parent-child relation.
-At this moment, two levels of navigation are supported. +Currently, two levels of navigation are supported. For more than two levels, use the
Sidebar2 component.
Set IconColor property to change the color.
diff --git a/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2Documentation.razor b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2Documentation.razor new file mode 100644 index 000000000..f2d85726a --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2Documentation.razor @@ -0,0 +1,43 @@ +@page "/sidebar2" + +@title + + + +

Blazor Sidebar2

+
+ Use the Blazor Bootstrap Sidebar2 component to display consistent, cross-browser, and responsive navigation links that support more than two nested levels. +
+ +@* *@ + + +
+ + + +
+ + +@* +
Set IconColor property to change the color.
+ *@ + + +
Replace your MainLayout.razor page code with the below example to have a complete layout with a sidebar.
+ + + +
Use the CustomIconName parameter to set the custom logo icon using font awesome or other icons.
+ + + +
Use the ImageSrc parameter to set the brand logo.
+ + +@code { + private string pageUrl = "sidebar2"; + private string title = "Blazor Sidebar2 Component"; + private string description = "Use the Blazor Bootstrap Sidebar2 component to display consistent, cross-browser, and responsive navigation links that support more than two nested levels."; + private string imageUrl = "https://i.imgur.com/U0l6wXo.png"; +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_01_Basic_Usage.razor b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_01_Basic_Usage.razor new file mode 100644 index 000000000..a8672111d --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_01_Basic_Usage.razor @@ -0,0 +1,45 @@ + + +@code { + IEnumerable? navItems; + + private async Task Sidebar2DataProvider(Sidebar2DataProviderRequest request) + { + if (navItems is null) + navItems = GetNavItems(); + + await Task.Delay(2000); + + return await Task.FromResult(request.ApplyTo(navItems)); + } + + private IEnumerable GetNavItems() + { + navItems = new List + { + new NavItem { Id = "1", Href = "/getting-started", IconName = IconName.HouseDoorFill, Text = "Getting Started"}, + + new NavItem { Id = "2", IconName = IconName.LayoutSidebarInset, Text = "Content" }, + new NavItem { Id = "3", Href = "/icons", IconName = IconName.PersonSquare, Text = "Icons", ParentId="2"}, + + new NavItem { Id = "4", IconName = IconName.ExclamationTriangleFill, Text = "Components" }, + new NavItem { Id = "5", Href = "/alerts", IconName = IconName.CheckCircleFill, Text = "Alerts", ParentId="4"}, + new NavItem { Id = "6", Href = "/breadcrumb", IconName = IconName.SegmentedNav, Text = "Breadcrumb", ParentId="4"}, + + new NavItem { Id = "7", IconName = IconName.ListNested, Text = "Sidebar 2", ParentId="4"}, + new NavItem { Id = "701", Href = "/sidebar2", IconName = IconName.Dash, Text = "How to use", ParentId="7"}, + new NavItem { Id = "702", Href = "/sidebar2-examples", IconName = IconName.Dash, Text = "Examples", ParentId="7"}, + + new NavItem { Id = "8", IconName = IconName.WindowPlus, Text = "Forms" }, + new NavItem { Id = "9", Href = "/autocomplete", IconName = IconName.InputCursorText, Text = "Auto Complete", ParentId="8"}, + new NavItem { Id = "10", Href = "/currency-input", IconName = IconName.CurrencyDollar, Text = "Currency Input", ParentId="8"}, + new NavItem { Id = "11", Href = "/number-input", IconName = IconName.InputCursor, Text = "Number Input", ParentId="8"}, + new NavItem { Id = "12", Href = "/switch", IconName = IconName.ToggleOn, Text = "Switch", ParentId="8"}, + }; + + return navItems; + } +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_02_More_Nested_Levels.razor b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_02_More_Nested_Levels.razor new file mode 100644 index 000000000..a2ccee7c2 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_02_More_Nested_Levels.razor @@ -0,0 +1,52 @@ + + +@code { + IEnumerable? navItems; + + private async Task Sidebar2DataProvider(Sidebar2DataProviderRequest request) + { + if (navItems is null) + navItems = GetNavItems(); + + await Task.Delay(2000); + + return await Task.FromResult(request.ApplyTo(navItems)); + } + + private IEnumerable GetNavItems() + { + navItems = new List + { + new NavItem { Id = "1", Href = "/getting-started", IconName = IconName.HouseDoorFill, Text = "Getting Started"}, + + new NavItem { Id = "2", IconName = IconName.LayoutSidebarInset, Text = "Content" }, + new NavItem { Id = "3", Href = "/icons", IconName = IconName.PersonSquare, Text = "Icons", ParentId="2"}, + + new NavItem { Id = "4", IconName = IconName.ExclamationTriangleFill, Text = "Components" }, + new NavItem { Id = "5", Href = "/alerts", IconName = IconName.CheckCircleFill, Text = "Alerts", ParentId="4"}, + new NavItem { Id = "6", Href = "/breadcrumb", IconName = IconName.SegmentedNav, Text = "Breadcrumb", ParentId="4"}, + + new NavItem { Id = "7", IconName = IconName.ListNested, Text = "Sidebar 2", ParentId="4"}, + new NavItem { Id = "701", Href = "/sidebar2/how-to-use", IconName = IconName.Dash, Text = "How to use", ParentId="7"}, + + new NavItem { Id = "702", IconName = IconName.Dash, Text = "Examples", ParentId="7"}, + new NavItem { Id = "70201", Href = "/sidebar2", IconName = IconName.Dash, Text = "Nested levels", ParentId="702"}, + + new NavItem { Id = "8", IconName = IconName.Grid, Text = "Grid", ParentId="4"}, + new NavItem { Id = "801", Href = "/grid/#", IconName = IconName.Dash, Text = "How to use", ParentId="8"}, + new NavItem { Id = "802", IconName = IconName.Dash, Text = "Examples", ParentId="8"}, + new NavItem { Id = "80201", Href = "/grid/#", IconName = IconName.Dash, Text = "Filters", ParentId="802"}, + + new NavItem { Id = "9", IconName = IconName.WindowPlus, Text = "Forms" }, + new NavItem { Id = "10", Href = "/autocomplete", IconName = IconName.InputCursorText, Text = "Auto Complete", ParentId="9"}, + new NavItem { Id = "11", Href = "/currency-input", IconName = IconName.CurrencyDollar, Text = "Currency Input", ParentId="9"}, + new NavItem { Id = "12", Href = "/number-input", IconName = IconName.InputCursor, Text = "Number Input", ParentId="9"}, + new NavItem { Id = "13", Href = "/switch", IconName = IconName.ToggleOn, Text = "Switch", ParentId="9"}, + }; + + return navItems; + } +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_03_Change_Icons_Color.razor b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_03_Change_Icons_Color.razor new file mode 100644 index 000000000..2d65942b6 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_03_Change_Icons_Color.razor @@ -0,0 +1,52 @@ + + +@code { + IEnumerable? navItems; + + private async Task Sidebar2DataProvider(Sidebar2DataProviderRequest request) + { + if (navItems is null) + navItems = GetNavItems(); + + await Task.Delay(2000); + + return await Task.FromResult(request.ApplyTo(navItems)); + } + + private IEnumerable GetNavItems() + { + navItems = new List + { + new NavItem { Id = "1", Href = "/getting-started", IconName = IconName.HouseDoorFill, Text = "Getting Started"}, + + new NavItem { Id = "2", IconName = IconName.LayoutSidebarInset, Text = "Content", IconColor = IconColor.Primary }, + new NavItem { Id = "3", Href = "/icons", IconName = IconName.PersonSquare, Text = "Icons", ParentId="2"}, + + new NavItem { Id = "4", IconName = IconName.ExclamationTriangleFill, Text = "Components", IconColor = IconColor.Success }, + new NavItem { Id = "5", Href = "/alerts", IconName = IconName.CheckCircleFill, Text = "Alerts", ParentId="4"}, + new NavItem { Id = "6", Href = "/breadcrumb", IconName = IconName.SegmentedNav, Text = "Breadcrumb", ParentId="4"}, + + new NavItem { Id = "7", IconName = IconName.ListNested, Text = "Sidebar 2", ParentId="4"}, + new NavItem { Id = "701", Href = "/sidebar2/how-to-use", IconName = IconName.Dash, Text = "How to use", ParentId="7"}, + + new NavItem { Id = "702", IconName = IconName.Dash, Text = "Examples", ParentId="7"}, + new NavItem { Id = "70201", Href = "/sidebar2", IconName = IconName.Dash, Text = "Nested levels", ParentId="702"}, + + new NavItem { Id = "8", IconName = IconName.Grid, Text = "Grid", ParentId="4", IconColor = IconColor.Danger }, + new NavItem { Id = "801", Href = "/grid/#", IconName = IconName.Dash, Text = "How to use", ParentId="8"}, + new NavItem { Id = "802", IconName = IconName.Dash, Text = "Examples", ParentId="8"}, + new NavItem { Id = "80201", Href = "/grid/#", IconName = IconName.Dash, Text = "Filters", ParentId="802"}, + + new NavItem { Id = "9", IconName = IconName.WindowPlus, Text = "Forms", IconColor = IconColor.Warning }, + new NavItem { Id = "10", Href = "/autocomplete", IconName = IconName.InputCursorText, Text = "Auto Complete", ParentId="9"}, + new NavItem { Id = "11", Href = "/currency-input", IconName = IconName.CurrencyDollar, Text = "Currency Input", ParentId="9"}, + new NavItem { Id = "12", Href = "/number-input", IconName = IconName.InputCursor, Text = "Number Input", ParentId="9"}, + new NavItem { Id = "13", Href = "/switch", IconName = IconName.ToggleOn, Text = "Switch", ParentId="9"}, + }; + + return navItems; + } +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_04_Full_layout_with_sidebar.razor b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_04_Full_layout_with_sidebar.razor new file mode 100644 index 000000000..bdd816e72 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_04_Full_layout_with_sidebar.razor @@ -0,0 +1,66 @@ +
+ + + +
+
+ About +
+ +
+
Page content goes here
+
+
+ +
+ +@code { + IEnumerable? navItems; + + private async Task Sidebar2DataProvider(Sidebar2DataProviderRequest request) + { + if (navItems is null) + navItems = GetNavItems(); + + await Task.Delay(2000); + + return await Task.FromResult(request.ApplyTo(navItems)); + } + + private IEnumerable GetNavItems() + { + navItems = new List + { + new NavItem { Id = "1", Href = "/getting-started", IconName = IconName.HouseDoorFill, Text = "Getting Started"}, + + new NavItem { Id = "2", IconName = IconName.LayoutSidebarInset, Text = "Content", IconColor = IconColor.Primary }, + new NavItem { Id = "3", Href = "/icons", IconName = IconName.PersonSquare, Text = "Icons", ParentId="2"}, + + new NavItem { Id = "4", IconName = IconName.ExclamationTriangleFill, Text = "Components", IconColor = IconColor.Success }, + new NavItem { Id = "5", Href = "/alerts", IconName = IconName.CheckCircleFill, Text = "Alerts", ParentId="4"}, + new NavItem { Id = "6", Href = "/breadcrumb", IconName = IconName.SegmentedNav, Text = "Breadcrumb", ParentId="4"}, + + new NavItem { Id = "7", IconName = IconName.ListNested, Text = "Sidebar 2", ParentId="4"}, + new NavItem { Id = "701", Href = "/sidebar2/how-to-use", IconName = IconName.Dash, Text = "How to use", ParentId="7"}, + + new NavItem { Id = "702", IconName = IconName.Dash, Text = "Examples", ParentId="7"}, + new NavItem { Id = "70201", Href = "/sidebar2", IconName = IconName.Dash, Text = "Nested levels", ParentId="702"}, + + new NavItem { Id = "8", IconName = IconName.Grid, Text = "Grid", ParentId="4", IconColor = IconColor.Danger }, + new NavItem { Id = "801", Href = "/grid/#", IconName = IconName.Dash, Text = "How to use", ParentId="8"}, + new NavItem { Id = "802", IconName = IconName.Dash, Text = "Examples", ParentId="8"}, + new NavItem { Id = "80201", Href = "/grid/#", IconName = IconName.Dash, Text = "Filters", ParentId="802"}, + + new NavItem { Id = "9", IconName = IconName.WindowPlus, Text = "Forms", IconColor = IconColor.Warning }, + new NavItem { Id = "10", Href = "/autocomplete", IconName = IconName.InputCursorText, Text = "Auto Complete", ParentId="9"}, + new NavItem { Id = "11", Href = "/currency-input", IconName = IconName.CurrencyDollar, Text = "Currency Input", ParentId="9"}, + new NavItem { Id = "12", Href = "/number-input", IconName = IconName.InputCursor, Text = "Number Input", ParentId="9"}, + new NavItem { Id = "13", Href = "/switch", IconName = IconName.ToggleOn, Text = "Switch", ParentId="9"}, + }; + + return navItems; + } +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_07_Custom_Brand_Icon.razor b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_07_Custom_Brand_Icon.razor new file mode 100644 index 000000000..758a76596 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_07_Custom_Brand_Icon.razor @@ -0,0 +1,66 @@ +
+ + + +
+
+ About +
+ +
+
Page content goes here
+
+
+ +
+ +@code { + IEnumerable? navItems; + + private async Task Sidebar2DataProvider(Sidebar2DataProviderRequest request) + { + if (navItems is null) + navItems = GetNavItems(); + + await Task.Delay(2000); + + return await Task.FromResult(request.ApplyTo(navItems)); + } + + private IEnumerable GetNavItems() + { + navItems = new List + { + new NavItem { Id = "1", Href = "/getting-started", IconName = IconName.HouseDoorFill, Text = "Getting Started"}, + + new NavItem { Id = "2", IconName = IconName.LayoutSidebarInset, Text = "Content", IconColor = IconColor.Primary }, + new NavItem { Id = "3", Href = "/icons", IconName = IconName.PersonSquare, Text = "Icons", ParentId="2"}, + + new NavItem { Id = "4", IconName = IconName.ExclamationTriangleFill, Text = "Components", IconColor = IconColor.Success }, + new NavItem { Id = "5", Href = "/alerts", IconName = IconName.CheckCircleFill, Text = "Alerts", ParentId="4"}, + new NavItem { Id = "6", Href = "/breadcrumb", IconName = IconName.SegmentedNav, Text = "Breadcrumb", ParentId="4"}, + + new NavItem { Id = "7", IconName = IconName.ListNested, Text = "Sidebar 2", ParentId="4"}, + new NavItem { Id = "701", Href = "/sidebar2/how-to-use", IconName = IconName.Dash, Text = "How to use", ParentId="7"}, + + new NavItem { Id = "702", IconName = IconName.Dash, Text = "Examples", ParentId="7"}, + new NavItem { Id = "70201", Href = "/sidebar2", IconName = IconName.Dash, Text = "Nested levels", ParentId="702"}, + + new NavItem { Id = "8", IconName = IconName.Grid, Text = "Grid", ParentId="4", IconColor = IconColor.Danger }, + new NavItem { Id = "801", Href = "/grid/#", IconName = IconName.Dash, Text = "How to use", ParentId="8"}, + new NavItem { Id = "802", IconName = IconName.Dash, Text = "Examples", ParentId="8"}, + new NavItem { Id = "80201", Href = "/grid/#", IconName = IconName.Dash, Text = "Filters", ParentId="802"}, + + new NavItem { Id = "9", IconName = IconName.WindowPlus, Text = "Forms", IconColor = IconColor.Warning }, + new NavItem { Id = "10", Href = "/autocomplete", IconName = IconName.InputCursorText, Text = "Auto Complete", ParentId="9"}, + new NavItem { Id = "11", Href = "/currency-input", IconName = IconName.CurrencyDollar, Text = "Currency Input", ParentId="9"}, + new NavItem { Id = "12", Href = "/number-input", IconName = IconName.InputCursor, Text = "Number Input", ParentId="9"}, + new NavItem { Id = "13", Href = "/switch", IconName = IconName.ToggleOn, Text = "Switch", ParentId="9"}, + }; + + return navItems; + } +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_08_Show_Image_as_Brand_Logo.razor b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_08_Show_Image_as_Brand_Logo.razor new file mode 100644 index 000000000..e9ec65894 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Pages/Sidebar2/Sidebar2_Demo_08_Show_Image_as_Brand_Logo.razor @@ -0,0 +1,66 @@ +
+ + + +
+
+ About +
+ +
+
Page content goes here
+
+
+ +
+ +@code { + IEnumerable? navItems; + + private async Task Sidebar2DataProvider(Sidebar2DataProviderRequest request) + { + if (navItems is null) + navItems = GetNavItems(); + + await Task.Delay(2000); + + return await Task.FromResult(request.ApplyTo(navItems)); + } + + private IEnumerable GetNavItems() + { + navItems = new List + { + new NavItem { Id = "1", Href = "/getting-started", IconName = IconName.HouseDoorFill, Text = "Getting Started"}, + + new NavItem { Id = "2", IconName = IconName.LayoutSidebarInset, Text = "Content", IconColor = IconColor.Primary }, + new NavItem { Id = "3", Href = "/icons", IconName = IconName.PersonSquare, Text = "Icons", ParentId="2"}, + + new NavItem { Id = "4", IconName = IconName.ExclamationTriangleFill, Text = "Components", IconColor = IconColor.Success }, + new NavItem { Id = "5", Href = "/alerts", IconName = IconName.CheckCircleFill, Text = "Alerts", ParentId="4"}, + new NavItem { Id = "6", Href = "/breadcrumb", IconName = IconName.SegmentedNav, Text = "Breadcrumb", ParentId="4"}, + + new NavItem { Id = "7", IconName = IconName.ListNested, Text = "Sidebar 2", ParentId="4"}, + new NavItem { Id = "701", Href = "/sidebar2/how-to-use", IconName = IconName.Dash, Text = "How to use", ParentId="7"}, + + new NavItem { Id = "702", IconName = IconName.Dash, Text = "Examples", ParentId="7"}, + new NavItem { Id = "70201", Href = "/sidebar2", IconName = IconName.Dash, Text = "Nested levels", ParentId="702"}, + + new NavItem { Id = "8", IconName = IconName.Grid, Text = "Grid", ParentId="4", IconColor = IconColor.Danger }, + new NavItem { Id = "801", Href = "/grid/#", IconName = IconName.Dash, Text = "How to use", ParentId="8"}, + new NavItem { Id = "802", IconName = IconName.Dash, Text = "Examples", ParentId="8"}, + new NavItem { Id = "80201", Href = "/grid/#", IconName = IconName.Dash, Text = "Filters", ParentId="802"}, + + new NavItem { Id = "9", IconName = IconName.WindowPlus, Text = "Forms", IconColor = IconColor.Warning }, + new NavItem { Id = "10", Href = "/autocomplete", IconName = IconName.InputCursorText, Text = "Auto Complete", ParentId="9"}, + new NavItem { Id = "11", Href = "/currency-input", IconName = IconName.CurrencyDollar, Text = "Currency Input", ParentId="9"}, + new NavItem { Id = "12", Href = "/number-input", IconName = IconName.InputCursor, Text = "Number Input", ParentId="9"}, + new NavItem { Id = "13", Href = "/switch", IconName = IconName.ToggleOn, Text = "Switch", ParentId="9"}, + }; + + return navItems; + } +} diff --git a/BlazorBootstrap.Demo.RCL/Pages/Spinners/SpinnersDocumentation.razor b/BlazorBootstrap.Demo.RCL/Pages/Spinners/SpinnersDocumentation.razor index 0c08bd9c7..8a6d1c216 100644 --- a/BlazorBootstrap.Demo.RCL/Pages/Spinners/SpinnersDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Pages/Spinners/SpinnersDocumentation.razor @@ -61,5 +61,5 @@ private string pageUrl = "/spinner"; private string title = "Blazor Spinner Component"; private string description = "Visualize the loading state of a component or page using the Blazor Bootstrap Spinner component."; - private string imageUrl = "https://i.imgur.com/273TamX.png"; // TODO: update this + private string imageUrl = "https://i.imgur.com/G4wyEd6.png"; } diff --git a/BlazorBootstrap.Demo.RCL/Shared/MainLayout.razor.cs b/BlazorBootstrap.Demo.RCL/Shared/MainLayout.razor.cs index 7593bb943..f88fc2deb 100644 --- a/BlazorBootstrap.Demo.RCL/Shared/MainLayout.razor.cs +++ b/BlazorBootstrap.Demo.RCL/Shared/MainLayout.razor.cs @@ -66,10 +66,11 @@ private IEnumerable GetNavItems() new (){ Id = "518", Text = "Progress", Href = "/progress", IconName = IconName.UsbC, ParentId = "5" }, new (){ Id = "519", Text = "Script Loader", Href = "/script-loader", IconName = IconName.CodeSlash, ParentId = "5" }, new (){ Id = "520", Text = "Sidebar", Href = "/sidebar", IconName = IconName.LayoutSidebar, ParentId = "5" }, - new (){ Id = "521", Text = "Spinner", Href = "/spinners", IconName = IconName.ArrowRepeat, ParentId = "5" }, - new (){ Id = "522", Text = "Tabs", Href = "/tabs", IconName = IconName.WindowPlus, ParentId = "5" }, - new (){ Id = "523", Text = "Toasts", Href = "/toasts", IconName = IconName.ExclamationTriangleFill, ParentId = "5" }, - new (){ Id = "524", Text = "Tooltips", Href = "/tooltips", IconName = IconName.ChatSquareDotsFill, ParentId = "5" }, + new (){ Id = "521", Text = "Sidebar 2", Href = "/sidebar2", IconName = IconName.ListNested, ParentId = "5" }, + new (){ Id = "522", Text = "Spinner", Href = "/spinners", IconName = IconName.ArrowRepeat, ParentId = "5" }, + new (){ Id = "523", Text = "Tabs", Href = "/tabs", IconName = IconName.WindowPlus, ParentId = "5" }, + new (){ Id = "524", Text = "Toasts", Href = "/toasts", IconName = IconName.ExclamationTriangleFill, ParentId = "5" }, + new (){ Id = "525", Text = "Tooltips", Href = "/tooltips", IconName = IconName.ChatSquareDotsFill, ParentId = "5" }, new (){ Id = "6", Text = "Data Visualization", IconName = IconName.BarChartFill, IconColor = IconColor.Warning }, new (){ Id = "600", Text = "Bar Chart", Href = "/charts/bar-chart", IconName = IconName.BarChartFill, ParentId = "6", Match = NavLinkMatch.All }, diff --git a/blazorbootstrap/Components/Sidebar/Sidebar.razor b/blazorbootstrap/Components/Sidebar/Sidebar.razor index d64cf3984..c1d1145af 100644 --- a/blazorbootstrap/Components/Sidebar/Sidebar.razor +++ b/blazorbootstrap/Components/Sidebar/Sidebar.razor @@ -32,7 +32,16 @@
- + @if (items is null) + { +
+ +
+ } + else + { + + }
diff --git a/blazorbootstrap/Components/Sidebar/SidebarItem.razor.cs b/blazorbootstrap/Components/Sidebar/SidebarItem.razor.cs index e42994641..093655c99 100644 --- a/blazorbootstrap/Components/Sidebar/SidebarItem.razor.cs +++ b/blazorbootstrap/Components/Sidebar/SidebarItem.razor.cs @@ -25,7 +25,7 @@ protected override void OnParametersSet() return; foreach (var childItem in ChildItems) - if (ShouldExpand(NavigationManager.Uri, childItem.Href!)) + if (NavLinkExtensions.ShouldExpand(NavigationManager, childItem.Href!, Match)) { navitemGroupExpanded = true; @@ -38,52 +38,6 @@ private void AutoHideNavMenu() Parent.HideNavMenuOnMobile(); } - private bool EqualsHrefExactlyOrIfTrailingSlashAdded(string currentUriAbsolute, string hrefAbsolute) - { - if (string.Equals(currentUriAbsolute, hrefAbsolute, StringComparison.OrdinalIgnoreCase)) return true; - - if (currentUriAbsolute.Length == hrefAbsolute.Length - 1) - // Special case: highlight links to http://host/path/ even if you're - // at http://host/path (with no trailing slash) - // - // This is because the router accepts an absolute URI value of "same - // as base URI but without trailing slash" as equivalent to "base URI", - // which in turn is because it's common for servers to return the same page - // for http://host/vdir as they do for host://host/vdir/ as it's no - // good to display a blank page in that case. - if (hrefAbsolute[^1] == '/' - && hrefAbsolute.StartsWith(currentUriAbsolute, StringComparison.OrdinalIgnoreCase)) - return true; - - return false; - } - - private static bool IsStrictlyPrefixWithSeparator(string value, string prefix) - { - var prefixLength = prefix.Length; - - return value.Length > prefixLength - && value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) - && ( - // Only match when there's a separator character either at the end of the - // prefix or right after it. - // Example: "/abc" is treated as a prefix of "/abc/def" but not "/abcdef" - // Example: "/abc/" is treated as a prefix of "/abc/def" but not "/abcdef" - prefixLength == 0 - || !char.IsLetterOrDigit(prefix[prefixLength - 1]) - || !char.IsLetterOrDigit(value[prefixLength]) - ); - } - - private bool ShouldExpand(string currentUriAbsolute, string href) - { - var hrefAbsolute = href == null ? null : NavigationManager.ToAbsoluteUri(href).AbsoluteUri; - - return hrefAbsolute != null - && (EqualsHrefExactlyOrIfTrailingSlashAdded(currentUriAbsolute, hrefAbsolute) - || (Match == NavLinkMatch.Prefix && IsStrictlyPrefixWithSeparator(currentUriAbsolute, hrefAbsolute))); - } - private void ToggleNavItemGroup() => navitemGroupExpanded = !navitemGroupExpanded; #endregion diff --git a/blazorbootstrap/Components/Sidebar/SidebarItemGroup.razor b/blazorbootstrap/Components/Sidebar/SidebarItemGroup.razor index 8f733ef5f..3a3ee1c3b 100644 --- a/blazorbootstrap/Components/Sidebar/SidebarItemGroup.razor +++ b/blazorbootstrap/Components/Sidebar/SidebarItemGroup.razor @@ -15,7 +15,7 @@ Match="@item.Match" HasChilds="@item.HasChildItems" ChildItems="@item.ChildItems" - Class="@item.Class"/> + Class="@item.Class" /> } } diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2.razor b/blazorbootstrap/Components/Sidebar2/Sidebar2.razor new file mode 100644 index 000000000..c1270cedf --- /dev/null +++ b/blazorbootstrap/Components/Sidebar2/Sidebar2.razor @@ -0,0 +1,48 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + + + + + + diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2.razor.cs b/blazorbootstrap/Components/Sidebar2/Sidebar2.razor.cs new file mode 100644 index 000000000..91bed5bfe --- /dev/null +++ b/blazorbootstrap/Components/Sidebar2/Sidebar2.razor.cs @@ -0,0 +1,169 @@ +namespace BlazorBootstrap; + +public partial class Sidebar2 : BlazorBootstrapComponentBase +{ + #region Fields and Constants + + private bool collapseNavMenu = true; + + private bool collapseSidebar = false; + + private bool isMobile = false; + + private IEnumerable? items = null; + + private DotNetObjectReference objRef = default!; + + private bool requestInProgress = false; + + #endregion + + #region Methods + + protected override void BuildClasses() + { + this.AddClass("bb-sidebar2"); + this.AddClass("collapsed", collapseSidebar); + this.AddClass("expanded", !collapseSidebar); + + base.BuildClasses(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var width = await JS.InvokeAsync("window.blazorBootstrap.sidebar.windowSize"); + bsWindowResize(width); + await RefreshDataAsync(firstRender); + } + + await base.OnAfterRenderAsync(firstRender); + } + + protected override async Task OnInitializedAsync() + { + Attributes ??= new Dictionary(); + objRef ??= DotNetObjectReference.Create(this); + + await base.OnInitializedAsync(); + + QueueAfterRenderAction(async () => await JS.InvokeVoidAsync("window.blazorBootstrap.sidebar.initialize", ElementId, objRef), new RenderPriority()); + } + + [JSInvokable] + public void bsWindowResize(int width) + { + if (width < 641) // mobile + isMobile = true; + else + isMobile = false; + } + + /// + /// Refresh the sidebar data. + /// + /// Task + public async Task RefreshDataAsync(bool firstRender = false) + { + if (requestInProgress) + return; + + requestInProgress = true; + + if (DataProvider != null) + { + var request = new Sidebar2DataProviderRequest(); + var result = await DataProvider.Invoke(request); + + items = result != null ? result.Data : new List(); + } + + requestInProgress = false; + + await InvokeAsync(StateHasChanged); + } + + /// + /// Toggles sidebar. + /// + public void ToggleSidebar() + { + collapseSidebar = !collapseSidebar; + DirtyClasses(); + StateHasChanged(); + } + + internal void HideNavMenuOnMobile() + { + if (isMobile && !collapseNavMenu) + collapseNavMenu = true; + } + + private string GetNavMenuCssClass() + { + var classList = new HashSet(); + + if (collapseNavMenu) + classList.Add("collapse"); + + classList.Add("bb-sidebar2-content nav-scrollable bb-scrollbar"); + + if (collapseSidebar) + classList.Add("bb-scrollbar-hidden"); + + return string.Join(" ", classList); + } + + private void ToggleNavMenu() => collapseNavMenu = !collapseNavMenu; + + #endregion + + #region Properties, Indexers + + /// + protected override bool ShouldAutoGenerateId => true; + + /// + /// Gets or sets the badge text. + /// + [Parameter] + public string? BadgeText { get; set; } + + /// + /// Gets or sets the custom icon name. + /// + [Parameter] + public string? CustomIconName { get; set; } + + /// + /// DataProvider is for items to render. + /// The provider should always return an instance of 'SidebarDataProviderResult', and 'null' is not allowed. + /// + [Parameter] + [EditorRequired] + public Sidebar2DataProviderDelegate? DataProvider { get; set; } = default!; + + /// + /// Gets or sets the IconName. + /// + [Parameter] + public IconName IconName { get; set; } + + /// + /// Gets or sets the logo. + /// + [Parameter] + public string? ImageSrc { get; set; } + + private string? navMenuCssClass => GetNavMenuCssClass(); + + /// + /// Gets or sets the title. + /// + [Parameter] + [EditorRequired] + public string? Title { get; set; } = default!; + + #endregion +} diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2.razor.css b/blazorbootstrap/Components/Sidebar2/Sidebar2.razor.css new file mode 100644 index 000000000..e9d60ed34 --- /dev/null +++ b/blazorbootstrap/Components/Sidebar2/Sidebar2.razor.css @@ -0,0 +1,91 @@ +.bb-sidebar2 { + background-color: var(--bb-sidebar2-background-color); + border-right: 1px solid var(--bb-sidebar2-content-border-color); +} + + .bb-sidebar2.collapsed { + width: var(--bb-sidebar2-collapsed-width); + } + + .bb-sidebar2.collapsed .expanded-only { + display: none; + } + +@media (min-width: 641px) { + .bb-sidebar2 { + width: var(--bb-sidebar2-width); + position: sticky; + top: 0; + } +} + +.bb-sidebar2-top-row { + height: 3.5rem; + background-color: var(--bb-sidebar2-top-row-background-color); + border-bottom: 1px solid var(--bb-sidebar2-top-row-border-color); + border-right: 1px solid var(--bb-sidebar2-top-row-border-color); +} + +.navbar-toggler { + background-color: var(--bb-sidebar2-navbar-toggler-background-color); + color: rgb(var(--bb-sidebar2-nav-item-text-active-color-rgb)); + padding: inherit !important; + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + line-height: inherit !important; + font-size: inherit !important; +} + +.navbar-toggler-icon { + background-image: inherit !important; + color: var(--bb-sidebar2-navbar-toggler-icon-color) !important; +} + +.navbar-toggler:focus { + box-shadow: none !important; +} + +.navbar-brand { + font-size: 1.1rem; +} + +.navbar-brand-icon { + color: var(--bb-sidebar2-brand-icon-color); +} + +.navbar-brand-image { + height: var(--bb-sidebar2-brand-image-height); +} + + .navbar-brand-image img { + width: var(--bb-sidebar2-brand-image-width); + height: var(--bb-sidebar2-brand-image-height); + vertical-align: initial !important; + } + +.navbar-brand-text { + color: var(--bb-sidebar2-title-text-color); + font-weight: 600 !important; +} + +.navbar-brand-badge { + color: var(--bb-sidebar2-title-badge-text-color); + background-color: var(--bb-sidebar2-title-badge-background-color); +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .collapse { + /* Never collapse the sidebar for wide screens */ + display: block; + } + + .nav-scrollable { + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor b/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor new file mode 100644 index 000000000..f738a796a --- /dev/null +++ b/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor @@ -0,0 +1,69 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + +
+ @if (HasChilds) + { + + @{ + var navLinkArrowCssClass = ""; + if (Level == 0 && NavItemGroupExpanded) + navLinkArrowCssClass = "ps-1"; + else if (Level > 0 && !NavItemGroupExpanded) + navLinkArrowCssClass = "ps-1"; + else if (Level > 0) + navLinkArrowCssClass = "ps-2"; + } + + + + + @* + *@ + + @{ + var navLinkTextCssClass = "ms-2"; + if (NavItemGroupExpanded) + navLinkTextCssClass += " fw-semibold"; + } + @Text + + } + else + { + + @{ + var navLinkArrowCssClass = "ps-2"; + if (Level == 0) + navLinkArrowCssClass = ""; + } + + + + @{ + var navLinkTextCssClass = "ms-2"; + } + @Text + + } + + @if (NavItemGroupExpanded && HasChilds && ChildItems is not null && ChildItems.Any()) + { + @foreach (var childItem in ChildItems) + { + + } + } +
diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor.cs b/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor.cs new file mode 100644 index 000000000..732dbb10a --- /dev/null +++ b/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor.cs @@ -0,0 +1,102 @@ +namespace BlazorBootstrap; + +public partial class Sidebar2Item : BlazorBootstrapComponentBase +{ + #region Methods + + protected override void BuildClasses() + { + this.AddClass("nav-item"); + this.AddClass($"nav-item-level-{Level}"); + this.AddClass("nav-item-group", HasChilds); + this.AddClass("active", NavItemGroupExpanded); + + base.BuildClasses(); + } + + protected override void OnInitialized() + { + if (NavLinkExtensions.ShouldExpand(NavigationManager, ChildItems!, Match)) + NavItemGroupExpanded = true; + + base.OnInitialized(); + } + + private void AutoHideNavMenu() + { + Root.HideNavMenuOnMobile(); + } + + private string GetNavLinkStyle() + { + // Implementation: + // Level 0 = 1rem = 0 + 1 + (0 * 0.5) + // Level 1 = 2.5rem = 1 + 1 + (1 * 0.5) + // Level 2 = 4rem = 2 + 1 + (2 * 0.5) + // ... + // Level n = ..... = n + 1 + (n * 0.5) + + var level = Level + 1 + Level * 0.5; + + if (HasChilds && !NavItemGroupExpanded) + level += 0.25; + else if (!HasChilds && Level == 0) + level += 0.25; + + return $"padding-left:{level}rem;"; + } + + private void ToggleNavItemGroup() => NavItemGroupExpanded = !NavItemGroupExpanded; + + #endregion + + #region Properties, Indexers + + /// + protected override bool ShouldAutoGenerateId => true; + + [Parameter] public IEnumerable? ChildItems { get; set; } + + [CascadingParameter] public bool CollapseSidebar { get; set; } + + [Parameter] public string? CustomIconName { get; set; } + + [Parameter] public bool HasChilds { get; set; } + + [Parameter] public string? Href { get; set; } + + [Parameter] public IconColor IconColor { get; set; } + + private string iconColorCssClass => BootstrapClassProvider.IconColor(IconColor); + + [Parameter] public IconName IconName { get; set; } + + [Parameter] public int Level { get; set; } = 0; + + /// + /// Gets or sets a value representing the URL matching behavior. + /// + [Parameter] + public NavLinkMatch Match { get; set; } + + [Inject] private NavigationManager NavigationManager { get; set; } = default!; + + [Parameter] public bool NavItemGroupExpanded { get; set; } = false; + + /// + /// Get nav link style. + /// + private string navLinkStyle => GetNavLinkStyle(); + + [Parameter] public Action OnNavItemGroupExpanded { get; set; } = default!; + + [CascadingParameter] public Sidebar2 Root { get; set; } = default!; + + [Parameter] public Target Target { get; set; } + + private string targetString => Target.ToTargetString()!; + + [Parameter] public string? Text { get; set; } + + #endregion +} diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor.css b/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor.css new file mode 100644 index 000000000..3f2eb9323 --- /dev/null +++ b/blazorbootstrap/Components/Sidebar2/Sidebar2Item.razor.css @@ -0,0 +1,42 @@ +.bb-sidebar2.collapsed .expanded-only { + display: none; +} + +.bb-sidebar2.collapsed .bi.expanded-only { + display: none !important; +} + +.bb-sidebar2 .nav-link-icon { + padding-bottom: 0.5rem; + padding-top: 0.5rem; +} + +.bb-sidebar2 nav .nav-item ::deep a { + color: var(--bb-sidebar2-nav-item-text-color); + align-items: center; + padding: 0.5rem 1rem; +} + + .bb-sidebar2 nav .nav-item ::deep a:hover { + background-color: var(--bb-sidebar2-nav-item-background-hover-color); + color: var(--bb-sidebar2-nav-item-text-hover-color); + } + + .bb-sidebar2 nav .nav-item ::deep a.active { + background-color: var(--bb-sidebar2-nav-item-background-hover-color); + color: var(--bb-sidebar2-nav-item-text-active-color); + font-weight: 600; + } + +/* MDN REFERENCE: https://developer.mozilla.org/en-US/docs/Web/CSS/:has#browser_compatibility */ +.bb-sidebar2 nav .nav-item.nav-item-group:has(.nav-link.active) { + background-color: var(--bb-sidebar2-nav-item-group-background-color); +} + +.bb-sidebar2 nav .nav-item.nav-item-group:has(.nav-item-group.active) { + background-color: var(--bb-sidebar2-nav-item-group-background-color); +} + +.bb-sidebar2 nav .nav-item.nav-item-group.active { + background-color: var(--bb-sidebar2-nav-item-group-background-color); +} diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2ItemGroup.razor b/blazorbootstrap/Components/Sidebar2/Sidebar2ItemGroup.razor new file mode 100644 index 000000000..9b0070109 --- /dev/null +++ b/blazorbootstrap/Components/Sidebar2/Sidebar2ItemGroup.razor @@ -0,0 +1,24 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + + diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2ItemGroup.razor.cs b/blazorbootstrap/Components/Sidebar2/Sidebar2ItemGroup.razor.cs new file mode 100644 index 000000000..374d2ff3e --- /dev/null +++ b/blazorbootstrap/Components/Sidebar2/Sidebar2ItemGroup.razor.cs @@ -0,0 +1,34 @@ +namespace BlazorBootstrap; + +public partial class Sidebar2ItemGroup : BlazorBootstrapComponentBase +{ + #region Methods + + protected override void BuildClasses() + { + this.AddClass("flex-column"); + + base.BuildClasses(); + } + + #endregion + + #region Properties, Indexers + + /// + protected override bool ShouldAutoGenerateId => true; + + [CascadingParameter] public bool CollapseSidebar { get; set; } + + /// + /// Gets or sets a value representing the URL matching behavior. + /// + [Parameter] + public NavLinkMatch Match { get; set; } + + [Inject] private NavigationManager NavigationManager { get; set; } = default!; + + [Parameter] public IEnumerable? NavItems { get; set; } + + #endregion +} diff --git a/blazorbootstrap/Components/Sidebar2/Sidebar2ItemGroup.razor.css b/blazorbootstrap/Components/Sidebar2/Sidebar2ItemGroup.razor.css new file mode 100644 index 000000000..e69de29bb diff --git a/blazorbootstrap/Extensions/NavLinkExtensions.cs b/blazorbootstrap/Extensions/NavLinkExtensions.cs new file mode 100644 index 000000000..26fdb0531 --- /dev/null +++ b/blazorbootstrap/Extensions/NavLinkExtensions.cs @@ -0,0 +1,70 @@ +namespace BlazorBootstrap; + +public static class NavLinkExtensions +{ + private static bool EqualsHrefExactlyOrIfTrailingSlashAdded(string currentUriAbsolute, string hrefAbsolute) + { + if (string.Equals(currentUriAbsolute, hrefAbsolute, StringComparison.OrdinalIgnoreCase)) return true; + + if (currentUriAbsolute.Length == hrefAbsolute.Length - 1) + // Special case: highlight links to http://host/path/ even if you're + // at http://host/path (with no trailing slash) + // + // This is because the router accepts an absolute URI value of "same + // as base URI but without trailing slash" as equivalent to "base URI", + // which in turn is because it's common for servers to return the same page + // for http://host/vdir as they do for host://host/vdir/ as it's no + // good to display a blank page in that case. + if (hrefAbsolute[^1] == '/' + && hrefAbsolute.StartsWith(currentUriAbsolute, StringComparison.OrdinalIgnoreCase)) + return true; + + return false; + } + + private static bool IsStrictlyPrefixWithSeparator(string value, string prefix) + { + var prefixLength = prefix.Length; + + return value.Length > prefixLength + && value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) + && ( + // Only match when there's a separator character either at the end of the + // prefix or right after it. + // Example: "/abc" is treated as a prefix of "/abc/def" but not "/abcdef" + // Example: "/abc/" is treated as a prefix of "/abc/def" but not "/abcdef" + prefixLength == 0 + || !char.IsLetterOrDigit(prefix[prefixLength - 1]) + || !char.IsLetterOrDigit(value[prefixLength]) + ); + } + + public static bool ShouldExpand(NavigationManager navigationManager, string href, NavLinkMatch match) + { + var hrefAbsolute = href is null ? null : navigationManager.ToAbsoluteUri(href).AbsoluteUri; + return hrefAbsolute is not null + && (EqualsHrefExactlyOrIfTrailingSlashAdded(navigationManager.Uri, hrefAbsolute) + || (match == NavLinkMatch.Prefix && IsStrictlyPrefixWithSeparator(navigationManager.Uri, hrefAbsolute))); + } + + public static bool ShouldExpand(NavigationManager navigationManager, IEnumerable navItems, NavLinkMatch match, int currentLevel = 0) + { + if(currentLevel > 16) + return false; + + if (navItems?.Any() ?? false) + { + foreach (var item in navItems) + { + if (ShouldExpand(navigationManager, item.Href!, match)) + return true; + + if (item?.HasChildItems ?? false) + if (ShouldExpand(navigationManager, item.ChildItems!, match, currentLevel + 1)) + return true; + } + } + + return false; + } +} diff --git a/blazorbootstrap/Models/NavItem.cs b/blazorbootstrap/Models/NavItem.cs index ca4908e58..ab9ba48ea 100644 --- a/blazorbootstrap/Models/NavItem.cs +++ b/blazorbootstrap/Models/NavItem.cs @@ -7,7 +7,7 @@ public class NavItem /// /// Gets or sets the collection of child navigation items. /// - internal IEnumerable? ChildItems { get; set; } + internal List? ChildItems { get; set; } /// /// Gets or sets an additional CSS class. @@ -44,6 +44,11 @@ public class NavItem /// public string? Id { get; set; } + /// + /// Gets or sets the item level. + /// + public int Level { get; set; } = 0; + /// /// Gets or sets the URL matching behavior. /// diff --git a/blazorbootstrap/Models/Sidebar2DataProviderDelegate.cs b/blazorbootstrap/Models/Sidebar2DataProviderDelegate.cs new file mode 100644 index 000000000..c9a8b3d36 --- /dev/null +++ b/blazorbootstrap/Models/Sidebar2DataProviderDelegate.cs @@ -0,0 +1,6 @@ +namespace BlazorBootstrap; + +/// +/// Data provider (delegate). +/// +public delegate Task Sidebar2DataProviderDelegate(Sidebar2DataProviderRequest request); diff --git a/blazorbootstrap/Models/Sidebar2DataProviderRequest.cs b/blazorbootstrap/Models/Sidebar2DataProviderRequest.cs new file mode 100644 index 000000000..76646c580 --- /dev/null +++ b/blazorbootstrap/Models/Sidebar2DataProviderRequest.cs @@ -0,0 +1,92 @@ +namespace BlazorBootstrap; + +public class Sidebar2DataProviderRequest +{ + #region Methods + + public Sidebar2DataProviderResult ApplyTo(IEnumerable data) + { + if (data is null) + { + return new Sidebar2DataProviderResult { Data = Enumerable.Empty() }; + } + + var result = new List(); + var parentNavItems = data.Where(x => string.IsNullOrWhiteSpace(x.ParentId))?.OrderBy(x => x.Sequence)?.ToList(); + + if (!parentNavItems?.Any() ?? true) + { + return new Sidebar2DataProviderResult { Data = Enumerable.Empty() }; + } + + result.AddRange(parentNavItems!); + + foreach (var navItem in result) + { + if (string.IsNullOrWhiteSpace(navItem.Id)) + continue; + + navItem.Level = 0; + + var childNavItems = data.Where(x => x.ParentId == navItem.Id)?.OrderBy(x => x.Sequence)?.ToList(); + + if (childNavItems?.Any() ?? false) + { + navItem.HasChildItems = true; + navItem.ChildItems = childNavItems; + + try + { + SetLevel(data, navItem.ChildItems, 1, new HashSet()); + } + catch (CircularReferenceException ex) + { + // Handle circular reference exception (e.g., log the error, skip processing the item) + Console.WriteLine($"Circular reference detected: {ex.Message}"); + } + } + } + + return new Sidebar2DataProviderResult { Data = result }; + } + + private void SetLevel(IEnumerable data, List items, int currentLevel, HashSet visitedIds) + { + foreach (var item in items) + { + item.Level = currentLevel; + + if (string.IsNullOrWhiteSpace(item.Id)) + continue; + + if (visitedIds.Contains(item.Id)) + { + throw new CircularReferenceException($"Circular reference detected: Item {item.Id} has a child referring back to it or an ancestor."); + } + + visitedIds.Add(item.Id); + + var childItems = data.Where(x => x.ParentId == item.Id)?.OrderBy(x => x.Sequence)?.ToList(); + + if (childItems?.Any() ?? false) + { + item.HasChildItems = true; + item.ChildItems = childItems; + + SetLevel(data, childItems, currentLevel + 1, new HashSet(visitedIds)); // Pass a copy of visitedIds + } + + visitedIds.Remove(item.Id); // Remove from visitedIds after processing the item + } + } + + #endregion +} + +// Add a custom exception class for circular references (optional) +public class CircularReferenceException : Exception +{ + public CircularReferenceException(string message) : base(message) + { + } +} diff --git a/blazorbootstrap/Models/Sidebar2DataProviderResult.cs b/blazorbootstrap/Models/Sidebar2DataProviderResult.cs new file mode 100644 index 000000000..c05ef385a --- /dev/null +++ b/blazorbootstrap/Models/Sidebar2DataProviderResult.cs @@ -0,0 +1,13 @@ +namespace BlazorBootstrap; + +public class Sidebar2DataProviderResult +{ + #region Properties, Indexers + + /// + /// The provided items by the request. + /// + public IEnumerable? Data { get; init; } + + #endregion +} diff --git a/blazorbootstrap/Models/SidebarDataProviderRequest.cs b/blazorbootstrap/Models/SidebarDataProviderRequest.cs index d26e5cbe7..55217154c 100644 --- a/blazorbootstrap/Models/SidebarDataProviderRequest.cs +++ b/blazorbootstrap/Models/SidebarDataProviderRequest.cs @@ -22,7 +22,7 @@ public SidebarDataProviderResult ApplyTo(IEnumerable data) if (string.IsNullOrWhiteSpace(navItem.Id)) continue; - var childNavItems = data.Where(x => x.ParentId == navItem.Id)?.OrderBy(x => x.Sequence); + var childNavItems = data.Where(x => x.ParentId == navItem.Id)?.OrderBy(x => x.Sequence)?.ToList(); if (childNavItems is not null && childNavItems.Any()) { diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.css b/blazorbootstrap/wwwroot/blazor.bootstrap.css index 25329e670..21bf6f3a3 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.css +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.css @@ -53,6 +53,27 @@ /* callout */ --bb-callout-link: 10, 88, 202; --bb-callout-code-color: #ab296a; + /* sidebar2 */ + --bb-sidebar2-width: 270px; + --bb-sidebar2-collapsed-width: 50px; + --bb-sidebar2-background-color: rgba(255, 255, 255, 1); + --bb-sidebar2-top-row-background-color: var(--bb-violet); + --bb-sidebar2-top-row-border-color: var(--bb-violet); + --bb-sidebar2-title-text-color: rgb(255,255,255); + --bb-sidebar2-brand-icon-color: rgb(255,255,255); + --bb-sidebar2-brand-image-width: 24px; + --bb-sidebar2-brand-image-height: 24px; + --bb-sidebar2-title-badge-text-color: var(--bb-violet); + --bb-sidebar2-title-badge-background-color: rgb(255,255,255); + --bb-sidebar2-navbar-toggler-icon-color: var(--bb-violet); + --bb-sidebar2-navbar-toggler-background-color: rgb(255,255,255); + --bb-sidebar2-content-border-color: rgb(214,213,213); + --bb-sidebar2-nav-item-text-color: rgba(0,0,0,0.9); + --bb-sidebar2-nav-item-text-active-color-rgb: 112.520718,44.062154,249.437846; + --bb-sidebar2-nav-item-text-hover-color: rgba(var(--bb-sidebar2-nav-item-text-active-color-rgb),0.9); + --bb-sidebar2-nav-item-text-active-color: rgba(var(--bb-sidebar2-nav-item-text-active-color-rgb),0.9); + --bb-sidebar2-nav-item-background-hover-color: rgba(var(--bb-sidebar2-nav-item-text-active-color-rgb),0.08); + --bb-sidebar2-nav-item-group-background-color: rgba(var(--bb-sidebar2-nav-item-text-active-color-rgb),0.08); } /* preload */ diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 79d4e548f..0f47a974b 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -733,6 +733,14 @@ window.blazorBootstrap = { bootstrap?.Tooltip?.getOrCreateInstance(elementRef)?.dispose(); } }, + treeview: { + initialize: (elementId, dotNetHelper) => { + window.addEventListener("resize", () => { + dotNetHelper.invokeMethodAsync('bsWindowResize', window.innerWidth); + }); + }, + windowSize: () => window.innerWidth + }, // global function invokeMethodAsync: (callbackEventName, dotNetHelper) => { dotNetHelper.invokeMethodAsync(callbackEventName);