All files / packages/design-system-2-site/src/lib navigation.ts

97.67% Statements 42/43
90.32% Branches 28/31
100% Functions 18/18
100% Lines 32/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112                      4x     101x 101x 101x       4x       336x       1x       11x       1519x       87x                   41x 41x 41x     19x           21x 21x 21x 21x   21x 21x 21x       21x   20x 35x 20x   20x                                   18x   13x   13x 33x 33x             13x    
/**
 * Navigation utilities.
 *
 * All navigation is driven by content/navigation.md — a plain markdown list.
 * Edit that file to add, remove, reorder, or nest pages and categories.
 */
 
import { parseNavigationMd, type NavCategory, type NavGroup, type NavPage, type NavTab } from './parse-navigation-md';
 
export type { NavCategory, NavGroup, NavPage, NavTab };
 
let _cached: NavCategory[] | null = null;
 
function load(): NavCategory[] {
  const isDev = process.env.NODE_ENV === 'development';
  if (isDev || !_cached) _cached = parseNavigationMd();
  return _cached;
}
 
export function getNavigation(): NavCategory[] {
  return load();
}
 
export function getAllPages(): NavPage[] {
  return load().flatMap((c) => c.children);
}
 
export function getAllNavCategories(): NavCategory[] {
  return load();
}
 
export function getPageBySlug(slug: string): NavPage | undefined {
  return getAllPages().find((p) => p.slug === slug);
}
 
export function getPagesByCategory(category: string): NavPage[] {
  return getAllPages().filter((p) => p.category === category);
}
 
export function getCategoryBySlug(slug: string): NavCategory | undefined {
  return load().find((c) => c.slug === slug);
}
 
/**
 * The nav page a route URL renders: the page itself, or the page that lists it
 * as a tab. Matching on the URL (not the bare slug) keeps same-named pages in
 * different categories apart (`/components/icons` vs `/icons/usage`).
 * A tab file missing from the nav falls back to its parent page (`/cat/page--tab` → `/cat/page`).
 */
export function getPageByUrl(url: string): NavPage | undefined {
  const pages = getAllPages();
  const parentUrl = url.split('--')[0];
  return (
    pages.find((p) => p.url === url) ??
    pages.find((p) => p.tabs?.some((t) => t.url === url)) ??
    pages.find((p) => p.url === parentUrl)
  );
}
 
/** Category › Group › Page. A label group has no page, so its crumb has no URL. */
export function getBreadcrumbs(page: NavPage): { name: string; url?: string }[] {
  const crumbs: { name: string; url?: string }[] = [];
  Eif (page.category) {
    const category = getCategoryBySlug(page.category);
    Eif (category) crumbs.push({ name: category.name, url: `/${category.slug}` });
  }
  if (page.group) crumbs.push({ name: page.group.name, url: page.group.url });
  crumbs.push({ name: page.name, url: page.url });
  return crumbs;
}
 
export function getAdjacentPages(page: NavPage): { prev: NavPage | null; next: NavPage | null } {
  if (!page.category) return { prev: null, next: null };
 
  const categoryPages = getPagesByCategory(page.category);
  const idx = categoryPages.findIndex((p) => p.url === page.url);
  Iif (idx === -1) return { prev: null, next: null };
 
  return {
    prev: idx > 0 ? categoryPages[idx - 1] : null,
    next: idx < categoryPages.length - 1 ? categoryPages[idx + 1] : null,
  };
}
 
/**
 * Given a page and the URL being rendered, return the page's tab list with the
 * active tab marked. The first tab links to the parent URL so
 * `/components/badge` and `/components/badge--usage` share a tab.
 */
export function getTabContext(
  parent: NavPage,
  url: string,
): {
  parent: NavPage;
  tabs: { name: string; url: string; active: boolean }[];
} | null {
  if (!parent.tabs || parent.tabs.length === 0) return null;
 
  const isOnParent = url === parent.url;
 
  const tabs = parent.tabs.map((t, i) => {
    const isFirst = i === 0;
    return {
      name: t.name,
      url: isFirst ? parent.url : t.url,
      active: isFirst ? isOnParent || t.url === url : t.url === url,
    };
  });
 
  return { parent, tabs };
}