Skip to content

Commit f835651

Browse files
committed
add url match feature to BitNav #11225
1 parent ac61823 commit f835651

File tree

12 files changed

+246
-31
lines changed

12 files changed

+246
-31
lines changed

src/BlazorUI/Bit.BlazorUI.Extras/Components/NavPanel/BitNavPanel.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
Items="_filteredNavItems"
128128
ItemTemplate="ItemTemplate"
129129
ItemTemplateRenderMode="ItemTemplateRenderMode"
130+
Match="NavMatch"
130131
Mode="NavMode"
131132
NoCollapse="NoCollapse"
132133
OnItemClick="(TItem item) => HandleNavItemClick(item)"

src/BlazorUI/Bit.BlazorUI.Extras/Components/NavPanel/BitNavPanel.razor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ public partial class BitNavPanel<TItem> : BitComponentBase where TItem : class
145145
/// </summary>
146146
[Parameter] public BitNavClassStyles? NavClasses { get; set; }
147147

148+
/// <summary>
149+
/// Determines the global URL matching behavior of the nav.
150+
/// </summary>
151+
[Parameter] public BitNavMatch? NavMatch { get; set; }
152+
148153
/// <summary>
149154
/// Determines how the navigation will be handled.
150155
/// </summary>

src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/BitNav.razor.cs

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.AspNetCore.Components.Routing;
1+
using System.Text.RegularExpressions;
2+
using Microsoft.AspNetCore.Components.Routing;
23

34
namespace Bit.BlazorUI;
45

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

121+
/// <summary>
122+
/// Gets or sets a value representing the global URL matching behavior of the nav.
123+
/// </summary>
124+
[Parameter] public BitNavMatch? Match { get; set; }
125+
120126
/// <summary>
121127
/// Determines how the navigation will be handled.
122128
/// </summary>
@@ -374,7 +380,7 @@ internal void SetIsExpanded(TItem item, bool value)
374380

375381
item.SetValueToProperty(NameSelectors.IsExpanded.Name, value);
376382
}
377-
383+
378384
internal void SetItemExpanded(TItem item, bool value)
379385
{
380386
var isExpanded = GetIsExpanded(item);
@@ -908,13 +914,76 @@ internal string GetItemKey(TItem item, string defaultKey)
908914
return GetKey(item) ?? $"{UniqueId}-{defaultKey}";
909915
}
910916

917+
internal BitNavMatch? GetMatch(TItem item)
918+
{
919+
if (item is BitNavItem navItem)
920+
{
921+
return navItem.Match;
922+
}
923+
924+
if (item is BitNavOption navOption)
925+
{
926+
return navOption.Match;
927+
}
928+
929+
if (NameSelectors is null) return null;
930+
931+
if (NameSelectors.Match.Selector is not null)
932+
{
933+
return NameSelectors.Match.Selector!(item);
934+
}
935+
936+
return item.GetValueFromProperty<BitNavMatch?>(NameSelectors.Match.Name);
937+
}
938+
911939
internal void SetSelectedItemByCurrentUrl()
912940
{
913-
var currentUrl = _navigationManager.Uri.Replace(_navigationManager.BaseUri, "/", StringComparison.Ordinal);
914-
var currentItem = Flatten(_items).FirstOrDefault(item => GetUrl(item) == currentUrl
915-
|| (GetAdditionalUrls(item)?.Contains(currentUrl) ?? false));
941+
if (Mode is not BitNavMode.Automatic) return;
942+
943+
string currentUrl = _navigationManager.Uri.Replace(_navigationManager.BaseUri, "/", StringComparison.Ordinal);
944+
var currentItem = Flatten(_items).FirstOrDefault(item =>
945+
{
946+
var match = GetMatch(item) ?? Match ?? BitNavMatch.Exact;
947+
948+
if (IsMatch(GetUrl(item), match)) return true;
949+
950+
return GetAdditionalUrls(item)?.Any(u => IsMatch(u, match)) is true;
951+
});
916952

917953
_ = SetSelectedItem(currentItem);
954+
955+
const string DOUBLE_STAR_PLACEHOLDER = "___BIT_NAV_DOUBLESTAR_PLACEHOLDER___";
956+
bool IsMatch(string? itemUrl, BitNavMatch? match)
957+
{
958+
if (itemUrl is null) return false;
959+
960+
return match switch
961+
{
962+
BitNavMatch.Exact => itemUrl == currentUrl,
963+
BitNavMatch.Prefix => currentUrl.StartsWith(itemUrl, StringComparison.Ordinal),
964+
BitNavMatch.Regex => Regex.IsMatch(currentUrl, itemUrl),
965+
BitNavMatch.Wildcard => IsWildcardMatch(currentUrl, itemUrl),
966+
_ => itemUrl == currentUrl,
967+
};
968+
969+
bool IsWildcardMatch(string input, string pattern)
970+
{
971+
string regexPattern = $"^{WildcardToRegex(pattern)}$";
972+
return Regex.IsMatch(input, regexPattern);
973+
}
974+
975+
string WildcardToRegex(string pattern)
976+
{
977+
pattern = Regex.Escape(pattern);
978+
979+
pattern = pattern.Replace(@"\*\*", DOUBLE_STAR_PLACEHOLDER);
980+
pattern = pattern.Replace(@"\*", "[^/]*");
981+
pattern = pattern.Replace(@"\?", "[^/]");
982+
pattern = pattern.Replace(DOUBLE_STAR_PLACEHOLDER, ".*");
983+
984+
return pattern;
985+
}
986+
}
918987
}
919988

920989

src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/BitNavItem.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public class BitNavItem
7272
/// </summary>
7373
public string? Key { get; set; }
7474

75+
/// <summary>
76+
/// Gets or sets a value representing the URL matching behavior of the nav item.
77+
/// </summary>
78+
[Parameter] public BitNavMatch? Match { get; set; }
79+
7580
/// <summary>
7681
/// Custom CSS style for the nav item.
7782
/// </summary>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Bit.BlazorUI;
2+
3+
/// <summary>
4+
/// Modifies the URL matching behavior for a <see cref="BitNav&lt;TItem&gt;"/>.
5+
/// </summary>
6+
public enum BitNavMatch
7+
{
8+
/// <summary>
9+
/// Specifies that the nav item should be active when it matches exactly the current URL.
10+
/// </summary>
11+
Exact,
12+
13+
/// <summary>
14+
/// Specifies that the nav item should be active when it matches any prefix of the current URL.
15+
/// </summary>
16+
Prefix,
17+
18+
/// <summary>
19+
/// Specifies that the nav item should be active when its provided regex matches the current URL.
20+
/// </summary>
21+
Regex,
22+
23+
/// <summary>
24+
/// Specifies that the nav item should be active when its provided wildcard matches the current URL.
25+
/// </summary>
26+
Wildcard
27+
}

src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/BitNavNameSelectors.cs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,112 +3,117 @@
33
public class BitNavNameSelectors<TItem>
44
{
55
/// <summary>
6-
/// The AriaCurrent field name and selector of the custom input class.
6+
/// The AriaCurrent field name and selector of the custom input class (see <see cref="BitNavItem.AriaCurrent"/>).
77
/// </summary>
88
public BitNameSelectorPair<TItem, BitNavAriaCurrent?> AriaCurrent { get; set; } = new(nameof(BitNavItem.AriaCurrent));
99

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

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

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

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

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

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

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

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

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

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

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

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

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

7575
/// <summary>
76-
/// The Style field name and selector of the custom input class.
76+
/// The Match field name and selector of the custom input class (see <see cref="BitNavItem.Match"/>).
77+
/// </summary>
78+
public BitNameSelectorPair<TItem, BitNavMatch?> Match { get; set; } = new(nameof(BitNavItem.Match));
79+
80+
/// <summary>
81+
/// The Style field name and selector of the custom input class (see <see cref="BitNavItem.Style"/>).
7782
/// </summary>
7883
public BitNameSelectorPair<TItem, string?> Style { get; set; } = new(nameof(BitNavItem.Style));
7984

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

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

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

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

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

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

110115
/// <summary>
111-
/// The AdditionalUrls field name and selector of the custom input class.
116+
/// The AdditionalUrls field name and selector of the custom input class (see <see cref="BitNavItem.AdditionalUrls"/>).
112117
/// </summary>
113118
public BitNameSelectorPair<TItem, IEnumerable<string>?> AdditionalUrls { get; set; } = new(nameof(BitNavItem.AdditionalUrls));
114119
}

src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/BitNavOption.razor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ public partial class BitNavOption : ComponentBase, IDisposable
8686
/// </summary>
8787
[Parameter] public string? Key { get; set; }
8888

89+
/// <summary>
90+
/// Gets or sets a value representing the URL matching behavior of the nav option.
91+
/// </summary>
92+
[Parameter] public BitNavMatch? Match { get; set; }
93+
8994
/// <summary>
9095
/// Custom CSS style for the nav option.
9196
/// </summary>

src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/NavPanel/BitNavPanelDemo.razor.cs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-

2-
namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.NavPanel;
1+
namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.NavPanel;
32

43
public partial class BitNavPanelDemo
54
{
@@ -191,6 +190,15 @@ public partial class BitNavPanelDemo
191190
Description = "Custom CSS classes for different parts of the nav component of the nav panel.",
192191
},
193192
new()
193+
{
194+
Name = "NavMatch",
195+
Type = "BitNavMatch",
196+
DefaultValue = "null",
197+
Description = "Determines the global URL matching behavior of the nav.",
198+
LinkType = LinkType.Link,
199+
Href = "#nav-match-enum",
200+
},
201+
new()
194202
{
195203
Name = "NavMode",
196204
Type = "BitNavMode",
@@ -535,6 +543,39 @@ public partial class BitNavPanelDemo
535543
]
536544
},
537545
new()
546+
{
547+
Id = "nav-match-enum",
548+
Name = "BitNavMatch",
549+
Description = "Modifies the URL matching behavior for a BitNav<TItem>.",
550+
Items =
551+
[
552+
new()
553+
{
554+
Name = "Exact",
555+
Description = "Specifies that the nav item should be active when it matches exactly the current URL.",
556+
Value = "0",
557+
},
558+
new()
559+
{
560+
Name = "Prefix",
561+
Description = "Specifies that the nav item should be active when it matches any prefix of the current URL.",
562+
Value = "1",
563+
},
564+
new()
565+
{
566+
Name = "Regex",
567+
Description = "Specifies that the nav item should be active when its provided regex matches the current URL.",
568+
Value = "2",
569+
},
570+
new()
571+
{
572+
Name = "Wildcard",
573+
Description = "Specifies that the nav item should be active when its provided wildcard matches the current URL.",
574+
Value = "3",
575+
}
576+
]
577+
},
578+
new()
538579
{
539580
Id = "nav-mode-enum",
540581
Name = "BitNavMode",

0 commit comments

Comments
 (0)