Skip to content

Commit 51268ec

Browse files
committed
Core: Improve Breadcrumbs API
1 parent 168760c commit 51268ec

File tree

8 files changed

+72
-56
lines changed

8 files changed

+72
-56
lines changed

.changeset/violet-parrots-press.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'fumadocs-core': patch
3+
---
4+
5+
Breadcrumbs API: Fix root folders being filtered when `includeRoot` is set to `true`.

.changeset/yummy-beans-float.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'fumadocs-core': minor
3+
---
4+
5+
Breadcrumbs API: default `includePage` to `false`.

apps/docs/app/static.json/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export async function GET(): Promise<Response> {
2222
url: page.url,
2323
title: page.data.title,
2424
description: page.data.description,
25-
breadcrumbs: items.flatMap<string>((item) =>
26-
typeof item.name === 'string' ? item.name : [],
25+
breadcrumbs: items.flatMap<string>((item, i) =>
26+
i > 0 && typeof item.name === 'string' ? item.name : [],
2727
),
2828
} satisfies OramaDocument;
2929
});

apps/docs/components/ai/search.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import type { z } from 'zod';
2020
import { DefaultChatTransport } from 'ai';
2121
import { Markdown } from './markdown';
2222
import { Presence } from '@radix-ui/react-presence';
23-
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
2423

2524
const Context = createContext<{
2625
open: boolean;
@@ -269,7 +268,7 @@ export function AISearchTrigger() {
269268
}),
270269
});
271270

272-
const onKeyPress = useEffectEvent((e: KeyboardEvent) => {
271+
const onKeyPress = (e: KeyboardEvent) => {
273272
if (e.key === 'Escape' && open) {
274273
setOpen(false);
275274
e.preventDefault();
@@ -279,12 +278,15 @@ export function AISearchTrigger() {
279278
setOpen(true);
280279
e.preventDefault();
281280
}
282-
});
281+
};
283282

283+
const onKeyPressRef = useRef(onKeyPress);
284+
onKeyPressRef.current = onKeyPress;
284285
useEffect(() => {
285-
window.addEventListener('keydown', onKeyPress);
286-
return () => window.removeEventListener('keydown', onKeyPress);
287-
}, [onKeyPress]);
286+
const listener = (e: KeyboardEvent) => onKeyPressRef.current(e);
287+
window.addEventListener('keydown', listener);
288+
return () => window.removeEventListener('keydown', listener);
289+
}, []);
288290

289291
return (
290292
<Context value={useMemo(() => ({ chat, open, setOpen }), [chat, open])}>

packages/core/src/breadcrumb.tsx

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@ export interface BreadcrumbItem {
1010

1111
export interface BreadcrumbOptions {
1212
/**
13-
* Include the root itself in the breadcrumb items array.
14-
* Specify the url by passing an object instead
13+
* Include the root folders in the breadcrumb items array.
1514
*
1615
* @defaultValue false
1716
*/
1817
includeRoot?:
1918
| boolean
2019
| {
20+
/**
21+
* Specify the url of root
22+
*/
2123
url: string;
2224
};
2325

2426
/**
2527
* Include the page itself in the breadcrumb items array
2628
*
27-
* @defaultValue true
29+
* @defaultValue false
2830
*/
2931
includePage?: boolean;
3032

@@ -64,38 +66,45 @@ export function getBreadcrumbItemsFromPath(
6466
path: PageTree.Node[],
6567
options: BreadcrumbOptions,
6668
): BreadcrumbItem[] {
67-
const { includePage = true, includeSeparator = false, includeRoot } = options;
69+
const {
70+
includePage = false,
71+
includeSeparator = false,
72+
includeRoot = false,
73+
} = options;
6874
let items: BreadcrumbItem[] = [];
69-
70-
path.forEach((item, i) => {
71-
if (item.type === 'separator' && item.name && includeSeparator) {
72-
items.push({
73-
name: item.name,
74-
});
75+
for (let i = 0; i < path.length; i++) {
76+
const item = path[i];
77+
78+
switch (item.type) {
79+
case 'page':
80+
if (includePage)
81+
items.push({
82+
name: item.name,
83+
url: item.url,
84+
});
85+
break;
86+
case 'folder':
87+
if (item.root && !includeRoot) {
88+
items = [];
89+
break;
90+
}
91+
92+
// only show the index node of folders if possible
93+
if (i === path.length - 1 || item.index !== path[i + 1]) {
94+
items.push({
95+
name: item.name,
96+
url: item.index?.url,
97+
});
98+
}
99+
break;
100+
case 'separator':
101+
if (item.name && includeSeparator)
102+
items.push({
103+
name: item.name,
104+
});
105+
break;
75106
}
76-
77-
if (item.type === 'folder') {
78-
const next = path.at(i + 1);
79-
if (next && item.index === next) return;
80-
81-
if (item.root) {
82-
items = [];
83-
return;
84-
}
85-
86-
items.push({
87-
name: item.name,
88-
url: item.index?.url,
89-
});
90-
}
91-
92-
if (item.type === 'page' && includePage) {
93-
items.push({
94-
name: item.name,
95-
url: item.url,
96-
});
97-
}
98-
});
107+
}
99108

100109
if (includeRoot) {
101110
items.unshift({
@@ -113,7 +122,7 @@ export function getBreadcrumbItemsFromPath(
113122
* - When the page doesn't exist, return null
114123
*
115124
* @returns The path to the target node from root
116-
* @internal
125+
* @internal Don't use this on your own
117126
*/
118127
export function searchPath(
119128
nodes: PageTree.Node[],

packages/core/src/utils/normalize-url.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/**
2-
* normalize URL into the Fumadocs standard form (`/slug-1/slug-2`)
2+
* normalize URL into the Fumadocs standard form (`/slug-1/slug-2`).
3+
*
4+
* This includes URLs with trailing slashes.
35
*/
46
export function normalizeUrl(url: string) {
57
if (url.startsWith('http://') || url.startsWith('https://')) return url;

packages/core/test/index.test.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,9 @@ test('Breadcrumbs', () => {
154154
],
155155
};
156156

157-
expect(getBreadcrumbItems('/docs/folder', tree)).toStrictEqual([
158-
{ name: 'World', url: '/docs/folder' },
159-
]);
157+
expect(
158+
getBreadcrumbItems('/docs/folder', tree, { includePage: true }),
159+
).toStrictEqual([{ name: 'World', url: '/docs/folder' }]);
160160

161-
expect(getBreadcrumbItems('/', tree)).toMatchInlineSnapshot(`
162-
[
163-
{
164-
"name": "Introduction",
165-
"url": "/",
166-
},
167-
]
168-
`);
161+
expect(getBreadcrumbItems('/invalid', tree)).toMatchInlineSnapshot(`[]`);
169162
});

packages/ui/src/layouts/docs/page-client.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,9 +340,9 @@ function FooterItem({ item, index }: { item: Item; index: 0 | 1 }) {
340340
export type BreadcrumbProps = BreadcrumbOptions & ComponentProps<'div'>;
341341

342342
export function PageBreadcrumb({
343-
includeRoot = false,
343+
includeRoot,
344344
includeSeparator,
345-
includePage = false,
345+
includePage,
346346
...props
347347
}: BreadcrumbProps) {
348348
const path = useTreePath();

0 commit comments

Comments
 (0)