diff --git a/Directory.Build.props b/Directory.Build.props
index 7a784a92c0..37c032747d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,8 +12,8 @@
$(MSBuildThisFileDirectory)
- 3.1.2
- 3.1.2
+ 3.2.0
+ 3.2.0$(VersionFile)
diff --git a/README.md b/README.md
index 0801e8b3dc..8dd1003c80 100644
--- a/README.md
+++ b/README.md
@@ -73,14 +73,13 @@ to your `index.html` or `_Layout.cshtml` file in the `
` section like this:
The file contains a number of CSS variables that are required to be defined for the components to work correctly.
-### Project file
-if you want to use icons and/or emoji, starting with version 2.1 you need add a `` to your project file. Within this group you can specify which icons and emoji are made available for usage and publication. Please refer to the [project setup](https://www.fluentui-blazor.net/ProjectSetup) document for more information.
-
-
### Code
Please refer to the [code setup](https://www.fluentui-blazor.net/CodeSetup) document to learn what needs to be included in your `Program.cs` file
so that all necessary services are available and setup in the correct way.
+## Working with Icons and Emoji
+We have additional packages available that include the complete Fluent UI System icons and Fluent UI Emoji collections.
+Please refer to the [Icons and Emoji](https://www.fluentui-blazor.net/IconsAndEmoji) page for more information.
## Getting started by using project templates
To make it easier to start a project that uses the Fluent UI Blazor components out of the box, we have created the
diff --git a/WHATSNEW.md b/WHATSNEW.md
index 5db09c21ea..ec395c2771 100644
--- a/WHATSNEW.md
+++ b/WHATSNEW.md
@@ -1,4 +1,7 @@
-## V3.1.1
+## V3.2.0
+- New NavMenu, NavGroup and NavLink components. **Breaking change** - See the [Upgrade guide](https://www.fluentui-blazor.net/UpgradeGuide) for details. See [NavMenu](https://www.fluentui-blazor.net/NavMenu) page for examples.
+
+## V3.1.1
- Fix [#776](https://github.com/microsoft/fluentui-blazor/issues/776): Icon throws exception when deployed to Azure
- Fix [#755](https://github.com/microsoft/fluentui-blazor/issues/755): Icon throws exception when deployed to Azure
- Fix [#789](https://github.com/microsoft/fluentui-blazor/issues/789): Navigation to "/" crashes with FluentNavMenu
diff --git a/examples/Demo/Shared/Pages/Index/Index.razor b/examples/Demo/Shared/Pages/Index/Index.razor
index ed2bc810c8..717e1dfe65 100644
--- a/examples/Demo/Shared/Pages/Index/Index.razor
+++ b/examples/Demo/Shared/Pages/Index/Index.razor
@@ -6,10 +6,10 @@
Welcome to the Fluent UI Blazor components library
-
This is the demo and documentation site for the next version (3.1.1) of the library
+
This is the demo and documentation site for the next version (3.2.0) of the library
+ With version 3.2 a new, much improved, set of components to build side-bar menus has been added.
+
+
+
+ If you DO NOT want to upgrade to these new menu components, you can continue to use the pre-version 3.2 components. The only thing
+ you need to do is to change the name of the FluentNavMenu component in your application to FluentNavMenuTree.
+
+
+
+ Everything works exactely as before by changing the name of this FluentNavMenu component in your application. (This probably needs to be done
+ in one place only). The FluentNavMenuGroup and FluentNavMenuLink components have not been changed and do not need to be altered.
+
+
+ We consider the FluentNavMenuTree, FluentNavMenuGroup and FluentNavMenuLink components as deprecated and will remove them in a future version.
+
+
+ If you wish to upgrade to the new menu components, please refer to the Upgrade guide for more information.
+
+
-
NavMenu, NavMenuGroup and NavMenuLink
- The FluentNavMenuGroup, FluentNavMenuLink and FluentNavMenu components can be used to build
- hierarchical, collapsible and expandable menus. They can range from simple 1-level deep lists to complex multi-level menu
- structures.
+ The FluentNavMenu, FluentNavGroup and FluentNavLink components can be used to build
+ hierarchical, collapsible and expandable side-bar menus. They can range from simple 1-level deep lists to complex multi-level menu
+ structures (with a max of 5 levels).
None of these components are particulary useful when used stand-alone.
-
-
-
-
-
-
Examples
- This demo shows 3 different versions of a NavMenu (with FluentNavMenuGoups and FluentNavMenuLinks).
+ This demo shows 3 different versions of a NavMenu (with FluentNavGroups and FluentNavLink)s.
From left to right:
-
Menu with several sub-menus, icons, etc
+
Menu with several sub-menus, icons, etc. The first group (Item 3) uses both the Title as the TitleTemplate parameters
Menu without sub-menus but with icons
Menu with just text links
@@ -38,12 +57,13 @@
- An example data binding the Expanded parameter.
+ An example of binding to the Expanded parameter.
-
-
- An example of intercepting menu actions to provide custom behavior.
-
-
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce quis lorem lacus.
+ Ut id leo non enim feugiat ultrices. Proin vulputate volutpat urna nec iaculis.
+ Integer dui lacus, fermentum sit amet aliquet in, scelerisque vitae dui.
+ Nulla fringilla sagittis orci eu consectetur. Fusce eget dolor non lectus placerat
+ tincidunt. Pellentesque aliquam non odio ac porttitor. Nam finibus mattis sagittis.
+ Ut hendrerit porttitor massa in aliquam. Duis laoreet fringilla feugiat.
+ Sed maximus, urna in fringilla posuere, enim urna bibendum justo, vel molestie nibh orci nec lectus.
+ Etiam a varius justo. Aenean nisl ante, interdum eget vulputate eget, iaculis ut massa.
+ Suspendisse maximus sed purus id molestie. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+
+
+
+@code
+{
+ bool Expanded = true;
+}
\ No newline at end of file
diff --git a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCustomActions.razor b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCustomActions.razor
similarity index 96%
rename from examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCustomActions.razor
rename to examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCustomActions.razor
index 1afe53dca6..ba11d8a38c 100644
--- a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCustomActions.razor
+++ b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCustomActions.razor
@@ -1,11 +1,10 @@
@using System.Text;
-@namespace FluentUI.Demo.Shared
Custom Actions Example
-
+
@*
This item is handled by the MenuLevelHandler, which intercepts all items
but will only handle ones with Id ending with _HandleAtMenuLevel
@@ -48,7 +47,7 @@
-
+
Log
diff --git a/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDataBound.razor b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDataBound.razor
new file mode 100644
index 0000000000..891f81ac0a
--- /dev/null
+++ b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDataBound.razor
@@ -0,0 +1,56 @@
+
+ With version 3.2 a new, much improved, set of components to build side-bar menus has been added. The demos shown here are using
+ components which are marked obsolete and will be removed in a future version.
+
+
+
+ If you DO NOT want to upgrade to these new menu components, you can continue to use the pre-version 3.2 components. The only thing
+ you need to do is to change the name of the FluentNavMenu component in your application to FluentNavMenuTree.
+
+
+
+ Everything works exactely as before by changing the name of this FluentNavMenu component in your application. (This probably needs to be done
+ in one place only). The FluentNavMenuGroup and FluentNavMenuLink components have not been changed and do not need to be altered.
+
+
+ We consider the FluentNavMenuTree, FluentNavMenuGroup and FluentNavMenuLink components as deprecated and will remove them in a future version.
+
+
+ If you wish to upgrade to the new menu components, please refer to the Upgrade guide for more information.
+
+
+
+ The FluentNavMenuGroup, FluentNavMenuLink and FluentNavMenu components can be used to build
+ hierarchical, collapsible and expandable menus. They can range from simple 1-level deep lists to complex multi-level menu
+ structures.
+
+
+ None of these components are particulary useful when used stand-alone.
+
+
+
Examples
+
+
+
+ This demo shows 3 different versions of a NavMenu (with FluentNavMenuGoups and FluentNavMenuLinks).
+ From left to right:
+
+
Menu with several sub-menus, icons, etc
+
Menu without sub-menus but with icons
+
Menu with just text links
+
+
+
+
+
+
+ More complete example which show how menu works with together with a text section when it collapses.
+
+
+
+
+
+ An example data binding the Expanded parameter.
+
+
+
+
+
+ An example of intercepting menu actions to provide custom behavior.
+
+
+
+
API Documentation
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Demo/Shared/Shared/DemoMainLayout.razor b/examples/Demo/Shared/Shared/DemoMainLayout.razor
index 221bcb0a05..fbd73a253f 100644
--- a/examples/Demo/Shared/Shared/DemoMainLayout.razor
+++ b/examples/Demo/Shared/Shared/DemoMainLayout.razor
@@ -53,7 +53,7 @@
diff --git a/examples/Demo/Shared/Shared/DemoNavMenu.razor b/examples/Demo/Shared/Shared/DemoNavMenu.razor
new file mode 100644
index 0000000000..d32edddab8
--- /dev/null
+++ b/examples/Demo/Shared/Shared/DemoNavMenu.razor
@@ -0,0 +1,106 @@
+
\ No newline at end of file
diff --git a/examples/Demo/Shared/wwwroot/docs/IconsAndEmoji.md b/examples/Demo/Shared/wwwroot/docs/IconsAndEmoji.md
index 30a35b068c..7a4251fe5e 100644
--- a/examples/Demo/Shared/wwwroot/docs/IconsAndEmoji.md
+++ b/examples/Demo/Shared/wwwroot/docs/IconsAndEmoji.md
@@ -1,3 +1,5 @@
+>**If you are upgrading from a previous version of the library and you were alreasy using icons and/or emoji, please see the [Upgrade Guide](https://www.fluentui-blazor.net/UpgradeGuide) for more information.**
+
Starting with v3, the assets for the icons and emoji are removed from the library package and are provided through additional (separate) packages for
both the icon and emoji resources. The components, and icons that are used by the library itself, are still part of the package.
Adding the [Microsoft.Fast.Components.FluentUI.Icons package](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Icons) and/or [Microsoft.Fast.Components.FluentUI.Emojis package](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Emojis)
@@ -5,16 +7,24 @@ is enough to make the resources available to your code.
We use the [.NET trimming capabilities](https://learn.microsoft.com/aspnet/core/blazor/host-and-deploy/configure-trimmer) to publish only those assests that are actually being used in your program. Usually this results in some very small DLL's that only contain the resources that are actually being used in your application.
-We still have support for both the **complete** [Fluent UI System Icons](https://github.com/microsoft/fluentui-system-icons) and the [Fluent Emoji](https://github.com/microsoft/fluentui-emoji) libraries.
+We support the **complete** [Fluent UI System Icons](https://github.com/microsoft/fluentui-system-icons) and [Fluent Emoji](https://github.com/microsoft/fluentui-emoji) collections.
## Getting Started
-To use the **Fluent UI System Icons** in your application, you will need to install the [Microsoft.Fast.Components.FluentUI.Icons](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Icons/) NuGet package in the project are using the main library.
+To use the **Fluent UI System Icons** in your application, you will need to install the [Microsoft.Fast.Components.FluentUI.Icons](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Icons/) NuGet package in the project which is using the main library.
+
+```shell
+dotnet add package Microsoft.Fast.Components.FluentUI.Icons
+```
+To use the **Fluent UI Emoji** in your application, you need to install the [Microsoft.Fast.Components.FluentUI.Emojis](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Emojis/) NuGet package in the project which is using the main library.
+
+```shell
+dotnet add package Microsoft.Fast.Components.FluentUI.Emojis
+```
-To use the **Fluent UI Emoji** in your application, you need to install the [Microsoft.Fast.Components.FluentUI.Emojis](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Emojis/) NuGet package in the project are using the main library.
-#### `FluentIcon` component
+#### Using the `FluentIcon` component
To use the icons, you add a `FluentIcon` component in your code like this:
@@ -58,7 +68,7 @@ After adding the class, you can start using this custom icon like a "normal" Flu
```
-#### `FluentEmoji` component
+#### Using the `FluentEmoji` component
To use the emoji, you add a `FleuntEmoji` component in your code like this:
diff --git a/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md b/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md
index 740734e98d..45225821c1 100644
--- a/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md
+++ b/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md
@@ -1,10 +1,29 @@
-## Breaking changes
- The `FluentDataGrid` component is, as you may know, a `QuickGrid` in disguise. We
- aligned the underlying code even more to the productized version that will ship with
- .NET 8. Where we previously aligned parameter names to the `fluent-datagrid` Web
- Component, we will now align to the `QuickGrid` naming. This should make
- integrating/copying `QuickGrid` component examples in your own environment easier and
- will make it easier for us to keep the code up-to-date. Changes that need to be made in parameter names from v2 are:
+## Breaking changes v3.2.0
+
+### The pre-v3.2 `FluentNavMenu` has been renamed to `FluentNavMenuTree`
+A new `FluentNavMenu` component has been added.
+
+If you want to **upgrade** your previous menu code, the following changes need to be made:
+
+* Change all occurrences of `` to ``
+* Change `FluentNavMenuLink` from a self-closing tag to a tag with a closing tag
+* Move the `FluentNavMenuLink` `Text` parameter content to in between the opening and closing tag
+* Change any `@onclick` occurrences to `OnClick`
+
+* Change all occurrences of `FluentNavMenuGroup` to `FluentNavGroup'
+* Replace the `Text` parameter with `Title`
+
+If you want to **keep** your previous menu code, the following change needs to be made:
+* Rename `FluentNavMenu` to `FluentNavMenuTree`
+
+
+## Breaking changes v3.0.0
+The `FluentDataGrid` component is, as you may know, a `QuickGrid` in disguise. We
+aligned the underlying code even more to the productized version that will ship with
+.NET 8. Where we previously aligned parameter names to the `fluent-datagrid` Web
+Component, we will now align to the `QuickGrid` naming. This should make
+integrating/copying `QuickGrid` component examples in your own environment easier and
+will make it easier for us to keep the code up-to-date. Changes that need to be made in parameter names from v2 are:
* RowsData -> Items
* RowsDataProvider -> ItemsProvider
* RowsDataSize -> ItemSize
diff --git a/examples/Demo/Shared/wwwroot/docs/WhatsNew.md b/examples/Demo/Shared/wwwroot/docs/WhatsNew.md
index 6b84563f29..386ee3d954 100644
--- a/examples/Demo/Shared/wwwroot/docs/WhatsNew.md
+++ b/examples/Demo/Shared/wwwroot/docs/WhatsNew.md
@@ -1,4 +1,7 @@
-## V3.1.1
+## V3.2.0
+- New NavMenu, NavGroup and NavLink components. **Breaking change** - See the [Upgrade guide](https://www.fluentui-blazor.net/UpgradeGuide) for details. See [NavMenu](https://www.fluentui-blazor.net/NavMenu) page for examples.
+
+## V3.1.1
- Fix [#776](https://github.com/microsoft/fluentui-blazor/issues/776): Icon throws exception when deployed to Azure
- Fix [#755](https://github.com/microsoft/fluentui-blazor/issues/755): Icon throws exception when deployed to Azure
- Fix [#789](https://github.com/microsoft/fluentui-blazor/issues/789): Navigation to "/" crashes with FluentNavMenu
diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor
new file mode 100644
index 0000000000..66ab387d57
--- /dev/null
+++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor
@@ -0,0 +1,7 @@
+@namespace Microsoft.Fast.Components.FluentUI
+@inherits FluentComponentBase
+@using System.Globalization
+
+
+ @ChildContent
+
\ No newline at end of file
diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs
new file mode 100644
index 0000000000..96c3d9faf5
--- /dev/null
+++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Globalization;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components;
+using Microsoft.Fast.Components.FluentUI.Utilities;
+using Microsoft.JSInterop;
+
+
+namespace Microsoft.Fast.Components.FluentUI;
+public partial class FluentCollapsibleRegion : FluentComponentBase
+{
+ private bool _expanded;
+
+ protected string? StyleValue =>
+ new StyleBuilder(Style)
+ .AddStyle("max-height", MaxHeight, !string.IsNullOrEmpty(MaxHeight))
+ .AddStyle("height", "auto", Expanded)
+ .AddStyle("height", "0", !Expanded)
+ .Build();
+
+ protected string? ClassValue =>
+ new CssBuilder(Class)
+ .AddClass("fluent-collapsible-region-container")
+ .Build();
+
+ ///
+ /// If true, the region is expaned, otherwise it is collapsed.
+ ///
+ [Parameter]
+ public bool Expanded
+ {
+ get => _expanded;
+ set
+ {
+ if (_expanded == value)
+ {
+ return;
+ }
+ _expanded = value;
+ _ = ExpandedChanged.InvokeAsync(_expanded);
+ }
+ }
+
+ ///
+ /// Explicitly sets the height for the Collapse element to override the css default.
+ ///
+ [Parameter]
+ public string? MaxHeight { get; set; }
+
+ ///
+ /// Child content of component.
+ ///
+ [Parameter]
+ public RenderFragment? ChildContent { get; set; }
+
+ ///
+ /// Callback for when the Expanded property changes.
+ ///
+ [Parameter]
+ public EventCallback ExpandedChanged { get; set; }
+
+}
diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css
new file mode 100644
index 0000000000..2f5758665a
--- /dev/null
+++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css
@@ -0,0 +1,3 @@
+.fluent-collapsible-region-container {
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/src/Core/Components/Divider/FluentDivider.razor b/src/Core/Components/Divider/FluentDivider.razor
index eace7ea01f..4b6d2c2b52 100644
--- a/src/Core/Components/Divider/FluentDivider.razor
+++ b/src/Core/Components/Divider/FluentDivider.razor
@@ -1,6 +1,7 @@
@namespace Microsoft.Fast.Components.FluentUI
@inherits FluentComponentBase
") { } }
+ public class ChevronLeft : Icon { public ChevronLeft() : base("ChevronLeft", IconVariant.Regular, IconSize.Size12, "") { } }
+ public class ChevronRight : Icon { public ChevronRight() : base("ChevronRight", IconVariant.Regular, IconSize.Size12, "") { } }
+ public class ChevronUp : Icon { public ChevronUp() : base("ChevronUp", IconVariant.Regular, IconSize.Size12, "") { } }
+
+ }
+ }
internal static partial class Regular
{
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
diff --git a/src/Core/Components/MainLayout/FluentMainLayout.razor b/src/Core/Components/MainLayout/FluentMainLayout.razor
index 1a4e7c3867..91ca93c839 100644
--- a/src/Core/Components/MainLayout/FluentMainLayout.razor
+++ b/src/Core/Components/MainLayout/FluentMainLayout.razor
@@ -5,15 +5,17 @@
@Header
-
-
+
+
@SubHeader
-
+
-
- @NavMenuContent
-
-
+ @if (NavMenuContent != null)
+ {
+
+ @NavMenuContent
+
+ }
@Body
diff --git a/src/Core/Components/NavMenu/FluentNavBase.cs b/src/Core/Components/NavMenu/FluentNavBase.cs
new file mode 100644
index 0000000000..c1779964f0
--- /dev/null
+++ b/src/Core/Components/NavMenu/FluentNavBase.cs
@@ -0,0 +1,102 @@
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Routing;
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.Fast.Components.FluentUI;
+using Microsoft.Fast.Components.FluentUI.Utilities;
+
+namespace Microsoft.Fast.Components.FluentUI;
+
+///
+/// Base class for and .
+///
+public abstract class FluentNavBase : FluentComponentBase
+{
+ ///
+ /// URL for the group.
+ ///
+ [Parameter]
+ public string? Href { get; set; }
+
+ ///
+ /// The target attribute specifies where to open the group, if Href is specified.
+ /// Possible values: _blank | _self | _parent | _top.
+ ///
+ [Parameter]
+ public string? Target { get; set; }
+
+ ///
+ /// Icon to use if set.
+ ///
+ [Parameter]
+ public Icon? Icon { get; set; }
+
+ ///
+ /// The color of the icon. It supports the theme colors, default value uses the themes drawer icon color.
+ ///
+ [Parameter]
+ public Color IconColor { get; set; } = Color.Accent;
+
+ ///
+ /// If true, the button will be disabled.
+ ///
+ [Parameter]
+ public bool Disabled { get; set; }
+
+ ///
+ /// Gets or sets the content to be shown.
+ ///
+ [Parameter]
+ public RenderFragment? ChildContent { get; set; }
+
+ ///
+ /// Class names to use to indicate the item is active, separated by space.
+ ///
+ [Parameter]
+ public string ActiveClass { get; set; } = "active";
+
+ ///
+ /// Gets or sets how the link should be matched. Defaults to .
+ ///
+ [Parameter]
+ public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix;
+
+ [CascadingParameter(Name = "NavMenuExpanded")]
+ public bool NavMenuExpanded { get; private set; }
+
+ ///
+ /// Returns if the item has an set.
+ ///
+ internal bool HasIcon => Icon is not null;
+
+ ///
+ /// The callback to invoke when the item is clicked.
+ ///
+ [Parameter]
+ public EventCallback OnClick { get; set; }
+
+ ///
+ /// If true, force browser to redirect outside component router-space.
+ ///
+ [Parameter]
+ public bool ForceLoad { get; set; }
+
+ [Inject]
+ private NavigationManager NavigationManager { get; set; } = default!;
+
+ protected async Task OnClickHandler(MouseEventArgs ev)
+ {
+ if (Disabled)
+ {
+ return;
+ }
+ if (!string.IsNullOrEmpty(Href))
+ {
+ NavigationManager.NavigateTo(Href, ForceLoad);
+ }
+ else
+ {
+ await OnClick.InvokeAsync(ev);
+ }
+ }
+}
+
diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor b/src/Core/Components/NavMenu/FluentNavGroup.razor
new file mode 100644
index 0000000000..baac263994
--- /dev/null
+++ b/src/Core/Components/NavMenu/FluentNavGroup.razor
@@ -0,0 +1,70 @@
+@namespace Microsoft.Fast.Components.FluentUI
+@inherits FluentNavBase
+@using Microsoft.AspNetCore.Components.Rendering
+@using Microsoft.AspNetCore.Components.Routing
+
+
+@if (NavMenuExpanded || HasIcon)
+{
+
\ No newline at end of file
diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs
new file mode 100644
index 0000000000..764229bfc6
--- /dev/null
+++ b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs
@@ -0,0 +1,373 @@
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Routing;
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.Fast.Components.FluentUI.Utilities;
+
+namespace Microsoft.Fast.Components.FluentUI;
+
+//[Obsolete("This component has been replaced with the FluentNavMenu and will be removed in a future version.")]
+public partial class FluentNavMenuTree : FluentComponentBase, INavMenuItemsOwner, IDisposable
+{
+ private const string WIDTH_COLLAPSED_MENU = "40px";
+ private bool _disposed;
+ private bool _hasChildIcons => ((INavMenuItemsOwner)this).HasChildIcons;
+ private readonly Dictionary _allItems = new();
+ private readonly List _childItems = new();
+ private readonly string _expandCollapseTreeItemId = Identifier.NewId();
+ private FluentTreeItem? _currentlySelectedTreeItem;
+ private FluentTreeItem? _previousSuccessfullySelectedTreeItem;
+
+ protected string? ClassValue => new CssBuilder(Class)
+ .AddClass("navmenu")
+ .AddClass("collapsed", Collapsed)
+ .AddClass("navmenu-parent-element")
+ .Build();
+
+ protected string? StyleValue => new StyleBuilder(Style)
+ .AddStyle("width", $"{Width}px", () => Expanded && Width.HasValue)
+ .AddStyle("width", WIDTH_COLLAPSED_MENU, () => !Expanded)
+ .AddStyle("min-width", WIDTH_COLLAPSED_MENU, () => !Expanded)
+ .Build();
+
+ ///
+ /// Gets or sets the content to be rendered inside the component.
+ ///
+ [Parameter]
+ public RenderFragment? ChildContent { get; set; }
+
+ ///
+ /// Gets or sets the content to be rendered for the expander icon
+ /// when the menu is collapsible. The default icon will be used if
+ /// this is not specified.
+ ///
+ [Parameter]
+ public RenderFragment? ExpanderContent { get; set; }
+
+ ///
+ /// Gets or sets the title of the navigation menu
+ /// Default to "Navigation menu"
+ ///
+ [Parameter]
+ public string? Title { get; set; } = "Navigation menu";
+
+ ///
+ /// Gets or sets the width of the menu (in pixels).
+ ///
+ [Parameter]
+ public int? Width { get; set; }
+
+ ///
+ /// Gets or sets whether or not the menu can be collapsed.
+ ///
+ [Parameter]
+ public bool Collapsible { get; set; }
+
+ ///
+ [Parameter]
+ public bool Expanded { get; set; } = true;
+
+ ///
+ /// Event callback for when the property changes.
+ ///
+ [Parameter]
+ public EventCallback ExpandedChanged { get; set; }
+
+ ///
+ /// Called when the user attempts to execute the default action of a menu item.
+ ///
+ [Parameter]
+ public EventCallback OnAction { get; set; }
+
+ ///
+ /// If set to then the tree will
+ /// expand when it is created.
+ ///
+ [Parameter]
+ public bool InitiallyExpanded { get; set; }
+
+ ///
+ /// If true, the menu will re-navigate to the current page when the user clicks on the currently selected menu item.
+ ///
+ [Parameter]
+ public bool ReNavigate { get; set; } = false;
+
+ ///
+ public bool Collapsed => !Expanded;
+
+ ///
+ /// Navigation manager
+ ///
+ [Inject]
+ protected NavigationManager NavigationManager { get; private set; } = null!;
+
+ public FluentNavMenuTree()
+ {
+ Id = Identifier.NewId();
+ }
+
+ ///
+ IEnumerable INavMenuItemsOwner.GetChildItems() => _childItems;
+
+ ///
+ void INavMenuItemsOwner.Register(FluentNavMenuItemBase child)
+ {
+ _childItems.Add(child);
+ StateHasChanged();
+ }
+
+ ///
+ void INavMenuItemsOwner.Unregister(FluentNavMenuItemBase child)
+ {
+ _childItems.Remove(child);
+ StateHasChanged();
+ }
+
+ void IDisposable.Dispose()
+ {
+ // Do not change this code. Put clean-up code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ internal void Register(FluentNavMenuItemBase item)
+ {
+ _allItems[item.Id!] = item;
+ StateHasChanged();
+ }
+
+ internal async Task MenuItemExpandedChangedAsync(INavMenuItemsOwner menuItem)
+ {
+ if (menuItem.Id == _expandCollapseTreeItemId)
+ {
+ return;
+ }
+
+ if (menuItem.Expanded && !Expanded)
+ {
+ await SetExpandedAsync(value: true);
+ }
+ }
+
+ internal void Unregister(FluentNavMenuItemBase item)
+ {
+ _allItems.Remove(item.Id!);
+ StateHasChanged();
+ }
+
+ protected override async Task OnInitializedAsync()
+ {
+ NavigationManager.LocationChanged += HandleNavigationManagerLocationChanged;
+
+ if (InitiallyExpanded)
+ {
+ await SetExpandedAsync(true);
+ }
+ }
+
+ protected override void OnAfterRender(bool firstRender)
+ {
+ if (firstRender)
+ {
+ SelectMenuItemForCurrentUrl();
+ }
+ }
+
+ private void SelectMenuItemForCurrentUrl()
+ {
+ HandleNavigationManagerLocationChanged(null, new LocationChangedEventArgs(NavigationManager.Uri, isNavigationIntercepted: false));
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ NavigationManager.LocationChanged -= HandleNavigationManagerLocationChanged;
+ _allItems.Clear();
+ _childItems.Clear();
+ }
+
+ _disposed = true;
+ }
+
+ private Task ToggleCollapsedAsync() => SetExpandedAsync(!Expanded);
+
+ private async void HandleNavigationManagerLocationChanged(object? sender, LocationChangedEventArgs e)
+ {
+ FluentNavMenuItemBase? menuItem = null;
+
+ string localPath = new Uri(NavigationManager.Uri).LocalPath;
+ if (string.IsNullOrEmpty(localPath))
+ localPath = "/";
+
+ if (localPath == "/")
+ {
+ if (_allItems.Count > 0) menuItem = _allItems.Values.ElementAt(0);
+ }
+ else
+ {
+ // This will match the first item that has a Href that matches the current URL exactly
+ menuItem = _allItems.Values
+ .Where(x => !string.IsNullOrEmpty(x.Href))
+ .FirstOrDefault(x => x.Href != "/" && localPath.Equals((x.Href!), StringComparison.InvariantCultureIgnoreCase));
+
+ // If not found, try to match the first item that has a Href (ending in a "/") that starts with the current URL
+ // URL: https://.../Panel/Panel2 starts with Href: https://.../Panel + "/"
+ // Extra "/" is needed to avoid matching https://.../Panels with https://.../Panel
+ if (menuItem is null)
+ {
+ menuItem = _allItems.Values
+ .Where(x => !string.IsNullOrEmpty(x.Href))
+ .FirstOrDefault(x => x.Href != "/" && localPath.StartsWith((x.Href! + "/"), StringComparison.InvariantCultureIgnoreCase));
+ }
+ }
+ if (menuItem is not null)
+ {
+ _currentlySelectedTreeItem = menuItem.TreeItem;
+ _previousSuccessfullySelectedTreeItem = menuItem.TreeItem;
+ await _currentlySelectedTreeItem.SetSelectedAsync(true);
+ }
+ }
+
+ private async Task HandleExpandCollapseKeyDownAsync(KeyboardEventArgs args)
+ {
+ Task handler = args.Code switch
+ {
+ "Enter" => SetExpandedAsync(true),
+ "ArrowRight" => SetExpandedAsync(true),
+ "ArrowLeft" => SetExpandedAsync(false),
+ _ => Task.CompletedTask
+ };
+ await handler;
+ }
+
+ private async Task SetExpandedAsync(bool value)
+ {
+ if (value == Expanded)
+ {
+ return;
+ }
+
+ Expanded = value;
+ if (ExpandedChanged.HasDelegate)
+ {
+ await ExpandedChanged.InvokeAsync(value);
+ }
+
+ StateHasChanged();
+ }
+
+ private async Task HandleCurrentSelectedChangedAsync(FluentTreeItem? treeItem)
+ {
+ // If an already activated menu item is clicked again, then it will
+ // match the previously selected one but have Selected == false.
+ // In this case, the user has indicated they wish to re-trigger
+ // its action. For the case of a simple navigation, this will be the same
+ // page and therefore do nothing.
+ // But for a nav menu with custom actions like showing a dialog etc, it will
+ // re-trigger and repeat that action.
+
+ // itemWasClickedWhilstAlreadySelected will never be true as treeItem is null when the treeItem is clicked again
+ // left the code in for now but this should probably be removed
+ //bool itemWasClickedWhilstAlreadySelected = treeItem?.Selected == false && treeItem == _previousSuccessfullySelectedTreeItem;
+ //if (itemWasClickedWhilstAlreadySelected)
+ //{
+ // await TryActivateMenuItemAsync(treeItem);
+ // return;
+ //}
+
+ if (treeItem is null && _previousSuccessfullySelectedTreeItem is not null && ReNavigate)
+ {
+ await TryActivateMenuItemAsync(_previousSuccessfullySelectedTreeItem, true);
+ return;
+ }
+ // If the user has selected a different item, then it will not match the previously
+ // selected item, and it will have Selected == true.
+ // So try to activate the new one instead of the old one.
+ // If it succeeds then keep it selected, if it fails then revert to the last successfully selected
+ // tree item. This prevents the user from selecting an item with no Href or custom action.
+ if (treeItem?.Selected == true && _allItems.TryGetValue(treeItem.Id!, out FluentNavMenuItemBase? menuItem))
+ {
+ bool activated = await TryActivateMenuItemAsync(treeItem);
+ if (activated)
+ {
+ _currentlySelectedTreeItem = treeItem;
+ _previousSuccessfullySelectedTreeItem = treeItem;
+ }
+ else
+ {
+ _currentlySelectedTreeItem = _previousSuccessfullySelectedTreeItem;
+ }
+ }
+
+ // At this point we have either
+ // 1) Succeeded,
+ // 2) Failed and reverted to a previously successful item without re-executing its action,
+ // 3) Failed and have no previously successful item to revert to.
+ // If we have no currently selected item then we fall back to selecting whichever matches the current
+ // URI.
+ if (_currentlySelectedTreeItem is null)
+ {
+ SelectMenuItemForCurrentUrl();
+ }
+
+ // Now we need to ensure the currently selected item has Selected=true, and
+ // the previous has Selected=false
+ if (_currentlySelectedTreeItem?.Selected == false)
+ {
+ await _currentlySelectedTreeItem.SetSelectedAsync(true);
+ }
+
+ if (_currentlySelectedTreeItem?.Selected == true)
+ {
+ if (_previousSuccessfullySelectedTreeItem?.Selected == true && _previousSuccessfullySelectedTreeItem != _currentlySelectedTreeItem)
+ {
+ await _previousSuccessfullySelectedTreeItem.SetSelectedAsync(false);
+ }
+ _previousSuccessfullySelectedTreeItem = _currentlySelectedTreeItem;
+ }
+
+ // If we still don't have a currently selected item, then make sure the invalid one
+ // the user tried to select is not selected.
+ if (treeItem?.Selected == true && treeItem != _currentlySelectedTreeItem)
+ {
+ await treeItem.SetSelectedAsync(false);
+ }
+ }
+
+ private async ValueTask TryActivateMenuItemAsync(FluentTreeItem? treeItem, bool renavigate = false)
+ {
+ if (treeItem is null)
+ {
+ return false;
+ }
+
+ if (!_allItems.TryGetValue(treeItem.Id!, out FluentNavMenuItemBase? menuItem))
+ {
+ return false;
+ }
+
+ NavMenuActionArgs? actionArgs = new NavMenuActionArgs(target: menuItem, renavigate: renavigate);
+ if (OnAction.HasDelegate)
+ {
+ await OnAction.InvokeAsync(actionArgs);
+ }
+
+ if (!actionArgs.Handled)
+ {
+ await menuItem.ExecuteAsync(actionArgs);
+ }
+
+ if (actionArgs.Handled)
+ {
+ await menuItem.SetSelectedAsync(true);
+ }
+
+ return actionArgs.Handled;
+ }
+
+}
diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.css b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.css
new file mode 100644
index 0000000000..23b0d4d066
--- /dev/null
+++ b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.css
@@ -0,0 +1,94 @@
+/*
+ NavMenu
+*/
+.navmenu {
+ background-color: var(--neutral-layer-1);
+}
+
+/*
+ NavMenu expander
+*/
+::deep .navmenu-expander::part(positioning-region) {
+ background-color: var(--neutral-fill-stealth-rest);
+}
+
+::deep .navmenu-expander::part(content-region) {
+ margin-inline-start: calc(var(--design-unit) * 2px);
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+::deep .navmenu-expander::part(positioning-region):hover {
+ background-color: var(--neutral-fill-secondary-rest);
+}
+
+::deep .navmenu-expander.selected::after {
+ display: none;
+}
+
+
+
+/*
+ Child Elements (Groups and Items)
+*/
+::deep .navmenu-child-element::part(content-region) {
+ -webkit-user-select: none;
+ user-select: none;
+ margin-inline-start: calc(var(--design-unit) * 2px);
+ margin-inline-end: calc(var(--design-unit) * 2px);
+}
+
+::deep .navmenu .treeitem-text, ::deep .navmenu fluent-tree-item.navmenu-child-element::part(start) {
+ pointer-events: none;
+}
+
+::deep .navmenu.collapsed .treeitem-text, ::deep .navmenu.collapsed fluent-tree-item.navmenu-child-element::part(expand-collapse-button) {
+ display: none;
+}
+
+
+
+/*
+ Groups
+*/
+::deep .navmenu-group::part(content-region) {
+ margin-inline-end: var(--expand-collapse-button-size);
+}
+
+
+
+/*
+ Group expander
+*/
+::deep .navmenu .navmenu-group::part(expand-collapse-button) {
+ right: calc(var(--design-unit) * 2px);
+ left: unset;
+ margin-inline-end: calc(var(--expand-collapse-button-size) * -1);
+}
+
+[dir="rtl"] * ::deep .navmenu-group::part(expand-collapse-button) {
+ left: calc(var(--design-unit) * 2px);
+ right: unset;
+ margin-inline-start: calc(var(--expand-collapse-button-size) - (var(--design-unit) * 2px));
+}
+
+
+/*
+ Group items
+*/
+::deep .navmenu-group .navmenu-child-element::part(content-region) {
+ padding-inline-start: calc(var(--design-unit) * 2px);
+}
+
+/* Hide any items inside groups of a collapsed nav menu*/
+::deep .navmenu.collapsed > .navmenu-parent-element::part(items) {
+ display: none;
+}
+
+
+/*
+ Nav links
+*/
+::deep .navmenu .navmenu-link::part(content-region) {
+ margin-inline-start: calc(var(--design-unit) * 2px);
+}
\ No newline at end of file
diff --git a/src/Core/Components/NavMenu/INavMenuItemsOwner.cs b/src/Core/Components/NavMenuTree/INavMenuItemsOwner.cs
similarity index 100%
rename from src/Core/Components/NavMenu/INavMenuItemsOwner.cs
rename to src/Core/Components/NavMenuTree/INavMenuItemsOwner.cs
diff --git a/src/Core/Enums/Color.cs b/src/Core/Enums/Color.cs
index b347790366..31b848bcfd 100644
--- a/src/Core/Enums/Color.cs
+++ b/src/Core/Enums/Color.cs
@@ -71,6 +71,12 @@ public enum Color
[Description("var(--neutral-layer-1)")]
Lightweight,
+ ///
+ /// Use the --neutral-stroke-rest CSS variable color, adapts to light/dark mode.
+ ///
+ [Description("var(--neutral-stroke-rest)")]
+ Disabled,
+
///
/// Supply an HTML hex color string value (#rrggbb or #rgb) for the CustomColor parameter.
///
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html
new file mode 100644
index 0000000000..91478a34cb
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ Group title
+
+
+
+
+
+
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified (2).html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified (2).html
new file mode 100644
index 0000000000..6d41c49e26
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified (2).html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
Group title
+
+
+
+
+
+
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified.html
new file mode 100644
index 0000000000..39c558143d
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
Group title
+
+
+
+
+
+
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Empty.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Empty.verified.html
new file mode 100644
index 0000000000..469d4b508d
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Empty.verified.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Gap.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Gap.verified.html
new file mode 100644
index 0000000000..3e6730937b
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Gap.verified.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
Group title
+
+
+
+
+
+
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_HideExpander.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_HideExpander.verified.html
new file mode 100644
index 0000000000..705d4cd30e
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_HideExpander.verified.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Href.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Href.verified.html
new file mode 100644
index 0000000000..52b6fd2bdd
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Href.verified.html
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Icon.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Icon.verified.html
new file mode 100644
index 0000000000..0ff11a7609
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Icon.verified.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
Group title
+
+
+
+
+
+
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_IconAndIconColor.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_IconAndIconColor.verified.html
new file mode 100644
index 0000000000..1e7bc5b5ae
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_IconAndIconColor.verified.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
Group title
+
+
+
+
+
+
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_TitleTemplate.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_TitleTemplate.verified.html
new file mode 100644
index 0000000000..91d63a0657
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_TitleTemplate.verified.html
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ExtendedTtitle.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ExtendedTtitle.verified.html
new file mode 100644
index 0000000000..3b7ccb8f99
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ExtendedTtitle.verified.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
NavLink text
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ForceLoad.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ForceLoad.verified.html
new file mode 100644
index 0000000000..08d6cf5258
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ForceLoad.verified.html
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Href.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Href.verified.html
new file mode 100644
index 0000000000..08d6cf5258
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Href.verified.html
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Icon.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Icon.verified.html
new file mode 100644
index 0000000000..61c7275191
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Icon.verified.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
NavLink text
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_IconAndIconColor.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_IconAndIconColor.verified.html
new file mode 100644
index 0000000000..d891dbbbb2
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_IconAndIconColor.verified.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
NavLink text
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Match.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Match.verified.html
new file mode 100644
index 0000000000..08d6cf5258
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Match.verified.html
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_OnClick.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_OnClick.verified.html
new file mode 100644
index 0000000000..17d77eeee3
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_OnClick.verified.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
NavLink text
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Target.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Target.verified.html
new file mode 100644
index 0000000000..2d91ef5729
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Target.verified.html
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CollapsibleCustomTitle.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CollapsibleCustomTitle.verified.html
new file mode 100644
index 0000000000..d6b657561e
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CollapsibleCustomTitle.verified.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
NavGroups and NavLinks here
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CustomTitle.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CustomTitle.verified.html
new file mode 100644
index 0000000000..39ec9a18d1
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CustomTitle.verified.html
@@ -0,0 +1,2 @@
+
+
NavGroups and NavLinks here
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Default.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Default.verified.html
new file mode 100644
index 0000000000..bbf9fd18df
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Default.verified.html
@@ -0,0 +1,2 @@
+
+
NavGroups and NavLinks here
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_ExpanderContent.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_ExpanderContent.verified.html
new file mode 100644
index 0000000000..ab9e5b4c22
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_ExpanderContent.verified.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
custom expander
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Margin.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Margin.verified.html
new file mode 100644
index 0000000000..a3874c3e52
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Margin.verified.html
@@ -0,0 +1,2 @@
+
+
NavGroups and NavLinks here
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_NotExpanded.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_NotExpanded.verified.html
new file mode 100644
index 0000000000..40aabf93c6
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_NotExpanded.verified.html
@@ -0,0 +1,2 @@
+
+
NavGroups and NavLinks here
\ No newline at end of file
diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Width.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Width.verified.html
new file mode 100644
index 0000000000..f1c083ae5b
--- /dev/null
+++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Width.verified.html
@@ -0,0 +1,2 @@
+
+