const ROOT_PATH = '/'; /** * @name isRouteActive * @description A function to check if a route is active. This is used to * @param end * @param path * @param currentPath */ export function isRouteActive( path: string, currentPath: string, end?: boolean | ((path: string) => boolean), ) { // if the path is the same as the current path, we return true if (path === currentPath) { return true; } // if the end prop is a function, we call it with the current path if (typeof end === 'function') { return !end(currentPath); } // otherwise - we use the evaluateIsRouteActive function const defaultEnd = end ?? true; const oneLevelDeep = 1; const threeLevelsDeep = 3; // how far down should segments be matched? const depth = defaultEnd ? oneLevelDeep : threeLevelsDeep; return checkIfRouteIsActive(path, currentPath, depth); } /** * @name checkIfRouteIsActive * @description A function to check if a route is active. This is used to * highlight the active link in the navigation. * @param targetLink - The link to check against * @param currentRoute - the current route * @param depth - how far down should segments be matched? */ export function checkIfRouteIsActive( targetLink: string, currentRoute: string, depth = 1, ) { // we remove any eventual query param from the route's URL const currentRoutePath = currentRoute.split('?')[0] ?? ''; if (!isRoot(currentRoutePath) && isRoot(targetLink)) { return false; } if (!currentRoutePath.includes(targetLink)) { return false; } const isSameRoute = targetLink === currentRoutePath; if (isSameRoute) { return true; } return hasMatchingSegments(targetLink, currentRoutePath, depth); } function splitIntoSegments(href: string) { return href.split('/').filter(Boolean); } function hasMatchingSegments( targetLink: string, currentRoute: string, depth: number, ) { const segments = splitIntoSegments(targetLink); const matchingSegments = numberOfMatchingSegments(currentRoute, segments); if (targetLink === currentRoute) { return true; } // how far down should segments be matched? // - if depth = 1 => only highlight the links of the immediate parent // - if depth = 2 => for url = /account match /account/organization/members return matchingSegments > segments.length - (depth - 1); } function numberOfMatchingSegments(href: string, segments: string[]) { let count = 0; for (const segment of splitIntoSegments(href)) { // for as long as the segments match, keep counting + 1 if (segments.includes(segment)) { count += 1; } else { return count; } } return count; } function isRoot(path: string) { return path === ROOT_PATH; }