Skip to content

Commit 2f76712

Browse files
authored
Add breadcrumbs to page header (#2628)
1 parent c73e07d commit 2f76712

File tree

3 files changed

+74
-4
lines changed

3 files changed

+74
-4
lines changed

.changeset/green-turtles-vanish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gitbook': patch
3+
---
4+
5+
Add breadcrumbs above page title

packages/gitbook/src/components/PageBody/PageBody.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export function PageBody(props: {
7979
<PageCover as="hero" page={page} cover={page.cover} context={context} />
8080
) : null}
8181

82-
<PageHeader page={page} />
82+
<PageHeader page={page} pages={context.pages} />
8383
{document && !isNodeEmpty(document) ? (
8484
<React.Suspense
8585
fallback={

packages/gitbook/src/components/PageBody/PageHeader.tsx

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,78 @@
1-
import { RevisionPageDocument } from '@gitbook/api';
1+
import {
2+
RevisionPage,
3+
RevisionPageDocument,
4+
RevisionPageGroup,
5+
RevisionPageType,
6+
} from '@gitbook/api';
7+
import { Icon } from '@gitbook/icons';
28

9+
import { pageHref } from '@/lib/links';
310
import { tcls } from '@/lib/tailwind';
411

512
import { PageIcon } from '../PageIcon';
13+
import { StyledLink } from '../primitives';
614

7-
export function PageHeader(props: { page: RevisionPageDocument }) {
8-
const { page } = props;
15+
export function PageHeader(props: { page: RevisionPageDocument; pages: RevisionPage[] }) {
16+
const { page, pages } = props;
917

1018
if (!page.layout.title && !page.layout.description) {
1119
return null;
1220
}
1321

22+
const pathSegments = page.path.split('/').slice(0, -1); // Exclude the current page from the breadcrumbs
23+
const flattenedPages = flattenPages(pages);
24+
const breadcrumbs = pathSegments
25+
.map((pathSegment) =>
26+
flattenedPages.find((page) => 'slug' in page && page.slug == pathSegment),
27+
)
28+
.filter((page): page is RevisionPageDocument | RevisionPageGroup => page !== undefined);
29+
1430
return (
1531
<header
1632
className={tcls('max-w-3xl', 'mx-auto', 'mb-6', 'space-y-3', 'page-api-block:ml-0')}
1733
>
34+
{breadcrumbs?.length > 0 && (
35+
<nav>
36+
<ol className={tcls('flex', 'flex-wrap', 'items-center', 'gap-2')}>
37+
{breadcrumbs.map((breadcrumb, index) => (
38+
<>
39+
<li key={breadcrumb.id}>
40+
<StyledLink
41+
href={pageHref(pages, breadcrumb)}
42+
style={tcls(
43+
'no-underline',
44+
'hover:underline',
45+
'text-xs',
46+
'tracking-wide',
47+
'font-semibold',
48+
'uppercase',
49+
'flex',
50+
'items-center',
51+
'gap-1',
52+
)}
53+
>
54+
<PageIcon
55+
page={breadcrumb}
56+
style={tcls('size-4', 'text-base', 'leading-none')}
57+
/>
58+
{breadcrumb.title}
59+
</StyledLink>
60+
</li>
61+
{index != breadcrumbs.length - 1 && (
62+
<Icon
63+
icon="chevron-right"
64+
className={tcls(
65+
'size-3',
66+
'text-light-4',
67+
'dark:text-dark-4',
68+
)}
69+
/>
70+
)}
71+
</>
72+
))}
73+
</ol>
74+
</nav>
75+
)}
1876
{page.layout.title ? (
1977
<h1 className={tcls('text-4xl', 'font-bold', 'flex', 'items-center', 'gap-4')}>
2078
<PageIcon page={page} style={['text-dark/6', 'dark:text-light/6']} />
@@ -29,3 +87,10 @@ export function PageHeader(props: { page: RevisionPageDocument }) {
2987
</header>
3088
);
3189
}
90+
91+
function flattenPages(pages: RevisionPage[]): RevisionPage[] {
92+
return pages.reduce<RevisionPage[]>((acc, page) => {
93+
const nestedPages = 'pages' in page && page.pages ? flattenPages(page.pages) : [];
94+
return acc.concat(page, ...nestedPages);
95+
}, []);
96+
}

0 commit comments

Comments
 (0)