Skip to content
8 changes: 7 additions & 1 deletion src/Core/Components/NavMenu/FluentNavLink.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
<div id="@Id" @attributes="AdditionalAttributes" class="@ClassValue" disabled="@Disabled" style="@Style" role="menuitem">
@if (!OnClick.HasDelegate && Href is not null)
{
<NavLink class="@LinkClassValue"
@* Add keyboard handling for navigation when inside a NavGroup *@
@if (Owner != null)
{
<FluentKeyCode Anchor="@($"{Id}-navlink")" Only="new[] { KeyCode.Enter, KeyCode.Space }" StopPropagation="@true" PreventDefault="@true" OnKeyDown="@HandleNavLinkKeyDownAsync" />
}
<NavLink id="@($"{Id}-navlink")"
class="@LinkClassValue"
@attributes="@Attributes"
Match="@Match"
ActiveClass="@ActiveClass"
Expand Down
10 changes: 10 additions & 0 deletions src/Core/Components/NavMenu/FluentNavLink.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// ------------------------------------------------------------------------

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.FluentUI.AspNetCore.Components.Utilities;

namespace Microsoft.FluentUI.AspNetCore.Components;
Expand Down Expand Up @@ -31,6 +32,15 @@ public partial class FluentNavLink : FluentNavBase

public FluentNavLink()
{
Id = Identifier.NewId();
_renderContent = RenderContent;
}

private async Task HandleNavLinkKeyDownAsync(FluentKeyCodeEventArgs args)
{
if ((args.Key == KeyCode.Enter || args.Key == KeyCode.Space) && !string.IsNullOrEmpty(Href) && !Disabled)
{
await OnClickHandlerAsync(new MouseEventArgs());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

<div id="xxx" class="fluent-nav-item" role="menuitem" b-5upkyn31e7="">
<a id="xxx" href="/example-page" rel="" class="fluent-nav-link">
<div class="positioning-region" b-5upkyn31e7="">
<div class="content-region" b-5upkyn31e7="">
<span class="fluent-nav-icon" style="min-width: 20px;" b-5upkyn31e7=""></span>
<div class="fluent-nav-text " b-5upkyn31e7="">NavLink text</div>
</div>
</div>
</a>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

<div id="xxx" class="fluent-nav-item" role="menuitem" b-5upkyn31e7="">
<a id="xxx" href="/example-page" rel="" class="fluent-nav-link">
<div class="positioning-region" b-5upkyn31e7="">
<div class="content-region" b-5upkyn31e7="">
<span class="fluent-nav-icon" style="min-width: 20px;" b-5upkyn31e7=""></span>
<div class="fluent-nav-text " b-5upkyn31e7="">Example page</div>
</div>
</div>
</a>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

<div id="xxx" class="fluent-nav-item" role="menuitem" b-5upkyn31e7="">
<a id="xxx" href="/example-page" rel="" class="fluent-nav-link">
<div class="positioning-region" b-5upkyn31e7="">
<div class="content-region" b-5upkyn31e7="">
<span class="fluent-nav-icon" style="min-width: 20px;" b-5upkyn31e7=""></span>
<div class="fluent-nav-text " b-5upkyn31e7="">Example page</div>
</div>
</div>
</a>
</div>
80 changes: 80 additions & 0 deletions tests/Core/NavMenu/FluentNavLinkTests.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
// ------------------------------------------------------------------------
// This file is licensed to you under the MIT License.
// ------------------------------------------------------------------------
using Bunit;
using Bunit.Rendering;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FluentUI.AspNetCore.Components.Tests.Extensions;
using Xunit;

namespace Microsoft.FluentUI.AspNetCore.Components.Tests.NavMenu;

public class FluentNavLinkTests : TestBase
{
public FluentNavLinkTests()
{
TestContext.JSInterop.Mode = JSRuntimeMode.Loose;
TestContext.Services.AddSingleton(LibraryConfiguration.ForUnitTests);
}
[Fact]
public void FluentNavLink_Default()
{
Expand Down Expand Up @@ -179,6 +187,78 @@ public void FluentNavLink_OnClick()
cut.Verify();
}

[Fact]
public void FluentNavLink_InsideNavGroup_WithHref()
{
// Arrange & Act
var m = new FluentNavMenu
{
Expanded = true
};
var cut = TestContext.RenderComponent<FluentNavLink>(parameters =>
{
parameters.Add(p => p.Owner, m);
parameters.Add(p => p.Href, "/example-page");
parameters.AddChildContent("NavLink text");
});

// Assert
cut.Verify();

// Verify that FluentKeyCode is added when Owner is not null and Href is set
var fluentKeyCode = cut.FindComponent<FluentKeyCode>();
Assert.NotNull(fluentKeyCode);

// Verify the anchor points to the NavLink element
var navLinkId = cut.Find("a[id]").GetAttribute("id");
Assert.Contains("navlink", navLinkId);
}

[Fact]
public void FluentNavLink_KeyboardNavigation_InsideNavGroup()
{
// Arrange & Act - Test NavLink inside a NavGroup scenario
var m = new FluentNavMenu
{
Expanded = true
};
var cut = TestContext.RenderComponent<FluentNavLink>(parameters =>
{
parameters.Add(p => p.Owner, m);
parameters.Add(p => p.Href, "/example-page");
parameters.AddChildContent("Example page");
});

// Assert - Verify that keyboard handling is added for NavLink inside group
cut.Verify();

// Verify FluentKeyCode component is present
var fluentKeyCode = cut.FindComponent<FluentKeyCode>();
Assert.NotNull(fluentKeyCode);

// Verify the NavLink has the expected id
var navLink = cut.Find("a[id]");
var navLinkId = navLink.GetAttribute("id");
Assert.Contains("-navlink", navLinkId);
}

[Fact]
public void FluentNavLink_KeyboardNavigation_Standalone()
{
// Arrange & Act - Test standalone NavLink (no Owner)
var cut = TestContext.RenderComponent<FluentNavLink>(parameters =>
{
parameters.Add(p => p.Href, "/example-page");
parameters.AddChildContent("Example page");
});

// Assert - Verify that no keyboard handling is added for standalone NavLink
cut.Verify();

// Verify no FluentKeyCode component is present
Assert.Throws<ComponentNotFoundException>(() => cut.FindComponent<FluentKeyCode>());
}

//ActiveClass
//Match
//Target
Expand Down
Loading