Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
Items="_filteredNavItems"
ItemTemplate="ItemTemplate"
ItemTemplateRenderMode="ItemTemplateRenderMode"
Match="NavMatch"
Mode="NavMode"
NoCollapse="NoCollapse"
OnItemClick="(TItem item) => HandleNavItemClick(item)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ public partial class BitNavPanel<TItem> : BitComponentBase where TItem : class
/// </summary>
[Parameter] public BitNavClassStyles? NavClasses { get; set; }

/// <summary>
/// Determines the global URL matching behavior of the nav.
/// </summary>
[Parameter] public BitNavMatch? NavMatch { get; set; }

/// <summary>
/// Determines how the navigation will be handled.
/// </summary>
Expand Down
79 changes: 74 additions & 5 deletions src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/BitNav.razor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.Routing;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Components.Routing;

namespace Bit.BlazorUI;

Expand Down Expand Up @@ -117,6 +118,11 @@ public partial class BitNav<TItem> : BitComponentBase where TItem : class
/// </summary>
[Parameter] public BitNavItemTemplateRenderMode ItemTemplateRenderMode { get; set; }

/// <summary>
/// Gets or sets a value representing the global URL matching behavior of the nav.
/// </summary>
[Parameter] public BitNavMatch? Match { get; set; }

/// <summary>
/// Determines how the navigation will be handled.
/// </summary>
Expand Down Expand Up @@ -374,7 +380,7 @@ internal void SetIsExpanded(TItem item, bool value)

item.SetValueToProperty(NameSelectors.IsExpanded.Name, value);
}

internal void SetItemExpanded(TItem item, bool value)
{
var isExpanded = GetIsExpanded(item);
Expand Down Expand Up @@ -908,13 +914,76 @@ internal string GetItemKey(TItem item, string defaultKey)
return GetKey(item) ?? $"{UniqueId}-{defaultKey}";
}

internal BitNavMatch? GetMatch(TItem item)
{
if (item is BitNavItem navItem)
{
return navItem.Match;
}

if (item is BitNavOption navOption)
{
return navOption.Match;
}

if (NameSelectors is null) return null;

if (NameSelectors.Match.Selector is not null)
{
return NameSelectors.Match.Selector!(item);
}

return item.GetValueFromProperty<BitNavMatch?>(NameSelectors.Match.Name);
}

internal void SetSelectedItemByCurrentUrl()
{
var currentUrl = _navigationManager.Uri.Replace(_navigationManager.BaseUri, "/", StringComparison.Ordinal);
var currentItem = Flatten(_items).FirstOrDefault(item => GetUrl(item) == currentUrl
|| (GetAdditionalUrls(item)?.Contains(currentUrl) ?? false));
if (Mode is not BitNavMode.Automatic) return;

string currentUrl = _navigationManager.Uri.Replace(_navigationManager.BaseUri, "/", StringComparison.Ordinal);
var currentItem = Flatten(_items).FirstOrDefault(item =>
{
var match = GetMatch(item) ?? Match ?? BitNavMatch.Exact;

if (IsMatch(GetUrl(item), match)) return true;

return GetAdditionalUrls(item)?.Any(u => IsMatch(u, match)) is true;
});

_ = SetSelectedItem(currentItem);

const string DOUBLE_STAR_PLACEHOLDER = "___BIT_NAV_DOUBLESTAR_PLACEHOLDER___";
bool IsMatch(string? itemUrl, BitNavMatch? match)
{
if (itemUrl is null) return false;

return match switch
{
BitNavMatch.Exact => itemUrl == currentUrl,
BitNavMatch.Prefix => currentUrl.StartsWith(itemUrl, StringComparison.Ordinal),
BitNavMatch.Regex => Regex.IsMatch(currentUrl, itemUrl),
BitNavMatch.Wildcard => IsWildcardMatch(currentUrl, itemUrl),
_ => itemUrl == currentUrl,
};

bool IsWildcardMatch(string input, string pattern)
{
string regexPattern = $"^{WildcardToRegex(pattern)}$";
return Regex.IsMatch(input, regexPattern);
}

string WildcardToRegex(string pattern)
{
pattern = Regex.Escape(pattern);

pattern = pattern.Replace(@"\*\*", DOUBLE_STAR_PLACEHOLDER);
pattern = pattern.Replace(@"\*", "[^/]*");
pattern = pattern.Replace(@"\?", "[^/]");
pattern = pattern.Replace(DOUBLE_STAR_PLACEHOLDER, ".*");

return pattern;
}
}
}


Expand Down
5 changes: 5 additions & 0 deletions src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/BitNavItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ public class BitNavItem
/// </summary>
public string? Key { get; set; }

/// <summary>
/// Gets or sets a value representing the URL matching behavior of the nav item.
/// </summary>
[Parameter] public BitNavMatch? Match { get; set; }

/// <summary>
/// Custom CSS style for the nav item.
/// </summary>
Expand Down
27 changes: 27 additions & 0 deletions src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/BitNavMatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Bit.BlazorUI;

/// <summary>
/// Modifies the URL matching behavior for a <see cref="BitNav&lt;TItem&gt;"/>.
/// </summary>
public enum BitNavMatch
{
/// <summary>
/// Specifies that the nav item should be active when it matches exactly the current URL.
/// </summary>
Exact,

/// <summary>
/// Specifies that the nav item should be active when it matches any prefix of the current URL.
/// </summary>
Prefix,

/// <summary>
/// Specifies that the nav item should be active when its provided regex matches the current URL.
/// </summary>
Regex,

/// <summary>
/// Specifies that the nav item should be active when its provided wildcard matches the current URL.
/// </summary>
Wildcard
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,112 +3,117 @@
public class BitNavNameSelectors<TItem>
{
/// <summary>
/// The AriaCurrent field name and selector of the custom input class.
/// The AriaCurrent field name and selector of the custom input class (see <see cref="BitNavItem.AriaCurrent"/>).
/// </summary>
public BitNameSelectorPair<TItem, BitNavAriaCurrent?> AriaCurrent { get; set; } = new(nameof(BitNavItem.AriaCurrent));

/// <summary>
/// The AriaLabel field name and selector of the custom input class.
/// The AriaLabel field name and selector of the custom input class (see <see cref="BitNavItem.AriaLabel"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> AriaLabel { get; set; } = new(nameof(BitNavItem.AriaLabel));

/// <summary>
/// The Class field name and selector of the custom input class.
/// The Class field name and selector of the custom input class (see <see cref="BitNavItem.Class"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> Class { get; set; } = new(nameof(BitNavItem.Class));

/// <summary>
/// The ChildItems field name and selector of the custom input class.
/// The ChildItems field name and selector of the custom input class (see <see cref="BitNavItem.ChildItems"/>).
/// </summary>
public BitNameSelectorPair<TItem, List<TItem>?> ChildItems { get; set; } = new(nameof(BitNavItem.ChildItems));

/// <summary>
/// The CollapseAriaLabel field name and selector of the custom input class.
/// The CollapseAriaLabel field name and selector of the custom input class (see <see cref="BitNavItem.CollapseAriaLabel"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> CollapseAriaLabel { get; set; } = new(nameof(BitNavItem.CollapseAriaLabel));

/// <summary>
/// The Data field name and selector of the custom input class.
/// The Data field name and selector of the custom input class (see <see cref="BitNavItem.Data"/>).
/// </summary>
public BitNameSelectorPair<TItem, object?> Data { get; set; } = new(nameof(BitNavItem.Data));

/// <summary>
/// The Description field name and selector of the custom input class.
/// The Description field name and selector of the custom input class (see <see cref="BitNavItem.Description"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> Description { get; set; } = new(nameof(BitNavItem.Description));

/// <summary>
/// The ExpandAriaLabel field name and selector of the custom input class.
/// The ExpandAriaLabel field name and selector of the custom input class (see <see cref="BitNavItem.ExpandAriaLabel"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> ExpandAriaLabel { get; set; } = new(nameof(BitNavItem.ExpandAriaLabel));

/// <summary>
/// The ForceAnchor field name and selector of the custom input class.
/// The ForceAnchor field name and selector of the custom input class (see <see cref="BitNavItem.ForceAnchor"/>).
/// </summary>
public BitNameSelectorPair<TItem, bool?> ForceAnchor { get; set; } = new(nameof(BitNavItem.ForceAnchor));

/// <summary>
/// The IconName field name and selector of the custom input class.
/// The IconName field name and selector of the custom input class (see <see cref="BitNavItem.IconName"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> IconName { get; set; } = new(nameof(BitNavItem.IconName));

/// <summary>
/// The IsEnabled field name and selector of the custom input class.
/// The IsEnabled field name and selector of the custom input class (see <see cref="BitNavItem.IsEnabled"/>).
/// </summary>
public BitNameSelectorPair<TItem, bool?> IsEnabled { get; set; } = new(nameof(BitNavItem.IsEnabled));

/// <summary>
/// The IsExpanded field name and selector of the custom input class.
/// The IsExpanded field name and selector of the custom input class (see <see cref="BitNavItem.IsExpanded"/>).
/// </summary>
public BitNameSelectorPair<TItem, bool?> IsExpanded { get; set; } = new(nameof(BitNavItem.IsExpanded));

/// <summary>
/// The IsSeparator field name and selector of the custom input class.
/// The IsSeparator field name and selector of the custom input class (see <see cref="BitNavItem.IsSeparator"/>).
/// </summary>
public BitNameSelectorPair<TItem, bool?> IsSeparator { get; set; } = new(nameof(BitNavItem.IsSeparator));

/// <summary>
/// The Key field name and selector of the custom input class.
/// The Key field name and selector of the custom input class (see <see cref="BitNavItem.Key"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> Key { get; set; } = new(nameof(BitNavItem.Key));

/// <summary>
/// The Style field name and selector of the custom input class.
/// The Match field name and selector of the custom input class (see <see cref="BitNavItem.Match"/>).
/// </summary>
public BitNameSelectorPair<TItem, BitNavMatch?> Match { get; set; } = new(nameof(BitNavItem.Match));

/// <summary>
/// The Style field name and selector of the custom input class (see <see cref="BitNavItem.Style"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> Style { get; set; } = new(nameof(BitNavItem.Style));

/// <summary>
/// The Target field name and selector of the custom input class.
/// The Target field name and selector of the custom input class (see <see cref="BitNavItem.Target"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> Target { get; set; } = new(nameof(BitNavItem.Target));

/// <summary>
/// The Template field name and selector of the custom input class.
/// The Template field name and selector of the custom input class (see <see cref="BitNavItem.Template"/>).
/// </summary>
public BitNameSelectorPair<TItem, RenderFragment<TItem>?> Template { get; set; } = new(nameof(BitNavItem.Template));

/// <summary>
/// The TemplateRenderMode field name and selector of the custom input class.
/// The TemplateRenderMode field name and selector of the custom input class (see <see cref="BitNavItem.TemplateRenderMode"/>).
/// </summary>
public BitNameSelectorPair<TItem, BitNavItemTemplateRenderMode?> TemplateRenderMode { get; set; } = new(nameof(BitNavItem.TemplateRenderMode));

/// <summary>
/// The Text field name and selector of the custom input class.
/// The Text field name and selector of the custom input class (see <see cref="BitNavItem.Text"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> Text { get; set; } = new(nameof(BitNavItem.Text));

/// <summary>
/// The Title field name and selector of the custom input class.
/// The Title field name and selector of the custom input class (see <see cref="BitNavItem.Title"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> Title { get; set; } = new(nameof(BitNavItem.Title));

/// <summary>
/// The Url field name and selector of the custom input class.
/// The Url field name and selector of the custom input class (see <see cref="BitNavItem.Url"/>).
/// </summary>
public BitNameSelectorPair<TItem, string?> Url { get; set; } = new(nameof(BitNavItem.Url));

/// <summary>
/// The AdditionalUrls field name and selector of the custom input class.
/// The AdditionalUrls field name and selector of the custom input class (see <see cref="BitNavItem.AdditionalUrls"/>).
/// </summary>
public BitNameSelectorPair<TItem, IEnumerable<string>?> AdditionalUrls { get; set; } = new(nameof(BitNavItem.AdditionalUrls));
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ public partial class BitNavOption : ComponentBase, IDisposable
/// </summary>
[Parameter] public string? Key { get; set; }

/// <summary>
/// Gets or sets a value representing the URL matching behavior of the nav option.
/// </summary>
[Parameter] public BitNavMatch? Match { get; set; }

/// <summary>
/// Custom CSS style for the nav option.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.NavPanel;
namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.NavPanel;

public partial class BitNavPanelDemo
{
Expand Down Expand Up @@ -191,6 +190,15 @@ public partial class BitNavPanelDemo
Description = "Custom CSS classes for different parts of the nav component of the nav panel.",
},
new()
{
Name = "NavMatch",
Type = "BitNavMatch",
DefaultValue = "null",
Description = "Determines the global URL matching behavior of the nav.",
LinkType = LinkType.Link,
Href = "#nav-match-enum",
},
new()
{
Name = "NavMode",
Type = "BitNavMode",
Expand Down Expand Up @@ -535,6 +543,39 @@ public partial class BitNavPanelDemo
]
},
new()
{
Id = "nav-match-enum",
Name = "BitNavMatch",
Description = "Modifies the URL matching behavior for a BitNav<TItem>.",
Items =
[
new()
{
Name = "Exact",
Description = "Specifies that the nav item should be active when it matches exactly the current URL.",
Value = "0",
},
new()
{
Name = "Prefix",
Description = "Specifies that the nav item should be active when it matches any prefix of the current URL.",
Value = "1",
},
new()
{
Name = "Regex",
Description = "Specifies that the nav item should be active when its provided regex matches the current URL.",
Value = "2",
},
new()
{
Name = "Wildcard",
Description = "Specifies that the nav item should be active when its provided wildcard matches the current URL.",
Value = "3",
}
]
},
new()
{
Id = "nav-mode-enum",
Name = "BitNavMode",
Expand Down
Loading
Loading