Skip to content

Commit e0c64f3

Browse files
refactor: enhance navbar context and item matching logic (#11)
* refactor: enhance navbar context and item matching logic - Update NavbarContext to allow setExpandedItems to accept a function for state updates. - Refactor Navbar component to utilize useRef for settings and improve useEffect for path behavior. - Optimize navigation item matching logic to return the best matching item based on path scores. * chore: update package json version to 0.0.11
1 parent 962bc0e commit e0c64f3

File tree

4 files changed

+80
-55
lines changed

4 files changed

+80
-55
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@guardiafinance/design-system",
3-
"version": "0.0.10",
3+
"version": "0.0.11",
44
"type": "module",
55
"exports": {
66
".": {

src/components/navbar/navbar-context.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { createContext, useContext, type ReactNode, useState } from 'react';
1+
import { createContext, useContext, type ReactNode, useState } from 'react';
22
import { SidebarProvider } from '../sidebar';
33

44
export interface NavbarState {
@@ -13,7 +13,7 @@ export interface NavbarContextValue {
1313
setActiveItem: (item: string | null) => void;
1414
setState: (state: NavbarState) => void;
1515
toggleExpandedItem: (itemTitle: string) => void;
16-
setExpandedItems: (items: string[]) => void;
16+
setExpandedItems: (items: string[] | ((prev: string[]) => string[])) => void;
1717
}
1818

1919
const NavbarContext = createContext<NavbarContextValue | null>(null);
@@ -54,8 +54,11 @@ export function NavbarProvider({
5454
}));
5555
};
5656

57-
const setExpandedItems = (items: string[]) => {
58-
setState(prev => ({ ...prev, expandedItems: items }));
57+
const setExpandedItems = (items: string[] | ((prev: string[]) => string[])) => {
58+
setState(prev => ({
59+
...prev,
60+
expandedItems: typeof items === 'function' ? items(prev.expandedItems) : items,
61+
}));
5962
};
6063

6164
const contextValue: NavbarContextValue = {
@@ -91,4 +94,4 @@ export function useNavbarContext(): NavbarContextValue {
9194
export function useNavbarControl() {
9295
const context = useContext(NavbarContext);
9396
return context;
94-
}
97+
}

src/components/navbar/navbar.tsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect } from "react";
1+
import { useEffect, useRef } from "react";
22
import { useNavigate, useLocation } from "react-router";
33
import {
44
Sidebar,
@@ -53,26 +53,40 @@ function NavbarInternal({
5353
const activeNavigationArea = navbarContext.state.activeArea || defaultActiveArea;
5454
const activeItem = navbarContext.state.activeItem;
5555

56-
useEffect(() => {
57-
if (settings.allowDefaultPathBehavior === true) {
58-
const { activeArea, activeItem: currentActiveItem } = getActiveStatesFromPath(settings, location.pathname);
59-
navbarContext.setActiveArea(activeArea);
60-
navbarContext.setActiveItem(currentActiveItem);
56+
const settingsRef = useRef(settings);
57+
settingsRef.current = settings;
6158

62-
match(currentActiveItem)
63-
.with(P.string, () => {
64-
const expandableParent = findExpandableParentByChildPath(settings, location.pathname);
65-
match(expandableParent)
66-
.with(P.string, (parent) => {
67-
if (!navbarContext.state.expandedItems.includes(parent)) {
68-
navbarContext.setExpandedItems([...navbarContext.state.expandedItems, parent]);
69-
}
70-
})
71-
.otherwise(() => { });
72-
})
73-
.otherwise(() => { });
59+
useEffect(() => {
60+
const s = settingsRef.current;
61+
if (s.allowDefaultPathBehavior !== true) {
62+
return;
7463
}
75-
}, [location.pathname, settings]);
64+
65+
const { activeArea, activeItem: currentActiveItem } = getActiveStatesFromPath(
66+
s,
67+
location.pathname
68+
);
69+
navbarContext.setActiveArea(activeArea);
70+
navbarContext.setActiveItem(currentActiveItem);
71+
72+
match(currentActiveItem)
73+
.with(P.string, () => {
74+
const expandableParent = findExpandableParentByChildPath(s, location.pathname);
75+
match(expandableParent)
76+
.with(P.string, (parent) => {
77+
navbarContext.setExpandedItems((prev) =>
78+
prev.includes(parent) ? prev : [...prev, parent]
79+
);
80+
})
81+
.otherwise(() => { });
82+
})
83+
.otherwise(() => { });
84+
}, [
85+
location.pathname,
86+
settings.routePrefix,
87+
settings.defaultActiveArea,
88+
settings.allowDefaultPathBehavior,
89+
]);
7690

7791
const handleItemClick = (item: MenuItemType) => {
7892
if ('children' in item) {
@@ -281,4 +295,4 @@ export function Navbar(props: NavbarProps) {
281295
);
282296
}
283297

284-
export default Navbar;
298+
export default Navbar;

src/components/navbar/utils.tsx

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -160,46 +160,57 @@ export const matchPathPattern = (pathname: string, pattern: string): boolean =>
160160
};
161161

162162
export const matchesNavigationItem = (pathname: string, item: NavigationItem): boolean => {
163+
return navigationItemMatchScore(pathname, item) > 0;
164+
};
165+
166+
const EXACT_PATH_SCORE = 1_000_000;
167+
const PATTERN_SCORE = 100_000;
168+
169+
const navigationItemMatchScore = (pathname: string, item: NavigationItem): number => {
163170
if (item.path && pathname === item.path) {
164-
return true;
171+
return EXACT_PATH_SCORE + item.path.length;
165172
}
166173

167174
if (item.pathPattern && matchPathPattern(pathname, item.pathPattern)) {
168-
return true;
175+
return PATTERN_SCORE;
169176
}
170177

171178
if (item.path && pathname.startsWith(item.path + '/')) {
172-
return true;
179+
return item.path.length;
173180
}
174181

175-
return false;
182+
return 0;
176183
};
177184

178185
export const findNavigationItemByPath = (config: NavbarConfiguration, path: string): NavigationItem | null => {
179186
const strippedPath = stripRoutePrefix(path, config.routePrefix);
187+
let best: NavigationItem | null = null;
188+
let bestScore = -1;
189+
190+
const consider = (candidate: NavigationItem) => {
191+
const score = navigationItemMatchScore(strippedPath, candidate);
192+
if (score > bestScore) {
193+
bestScore = score;
194+
best = candidate;
195+
}
196+
};
180197

181198
for (const area of config.areas) {
182199
for (const section of area.sections) {
183200
for (const item of section.items) {
184-
const result = match(item)
201+
match(item)
185202
.with({ children: P.array(P.any) }, (expandableItem) => {
186203
for (const child of expandableItem.children) {
187-
if (matchesNavigationItem(strippedPath, child)) {
188-
return child;
189-
}
204+
consider(child);
190205
}
191-
return null;
192206
})
193-
.with({ icon: P.any }, (regularItem) =>
194-
matchesNavigationItem(strippedPath, regularItem) ? regularItem : null
195-
)
196-
.otherwise(() => null);
197-
198-
if (result) return result;
207+
.with({ icon: P.any }, (regularItem) => consider(regularItem))
208+
.otherwise(() => { });
199209
}
200210
}
201211
}
202-
return null;
212+
213+
return bestScore > 0 ? best : null;
203214
};
204215

205216
export const getAllNavigationPaths = (config: NavbarConfiguration): string[] => {
@@ -260,23 +271,20 @@ export const getActiveStatesFromPath = (config: NavbarConfiguration, pathname: s
260271
};
261272

262273
export const findExpandableParentByChildPath = (config: NavbarConfiguration, childPath: string): string | null => {
263-
const strippedPath = stripRoutePrefix(childPath, config.routePrefix);
274+
const found = findNavigationItemByPath(config, childPath);
275+
if (!found) {
276+
return null;
277+
}
264278

265279
for (const area of config.areas) {
266280
for (const section of area.sections) {
267281
for (const item of section.items) {
268-
const result = match(item)
269-
.with({ children: P.array(P.any) }, (expandableItem) => {
270-
for (const child of expandableItem.children) {
271-
if (matchesNavigationItem(strippedPath, child)) {
272-
return expandableItem.title;
273-
}
274-
}
275-
return null;
276-
})
277-
.otherwise(() => null);
278-
279-
if (result) return result;
282+
if (
283+
isExpandableItem(item) &&
284+
item.children.some((child) => child === found)
285+
) {
286+
return item.title;
287+
}
280288
}
281289
}
282290
}

0 commit comments

Comments
 (0)