UI: Support createRelativeLink component factory for using relative file paths in href

This commit is contained in:
Fuma Nama
2025-03-15 17:37:12 +08:00
parent 30b7bd488c
commit e7e2a2a9d3
7 changed files with 76 additions and 4 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-ui': minor
---
Support `createRelativeLink` component factory for using relative file paths in `href`
+2
View File
@@ -14,6 +14,7 @@ import {
type ReactNode,
} from 'react';
import defaultComponents from 'fumadocs-ui/mdx';
import { createRelativeLink } from 'fumadocs-ui/mdx.server';
import { Popup, PopupContent, PopupTrigger } from 'fumadocs-twoslash/ui';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';
@@ -81,6 +82,7 @@ export default async function Page(props: {
components={{
...defaultComponents,
...((await import('lucide-react')) as unknown as MDXComponents),
a: createRelativeLink(source, page),
Popup,
PopupContent,
+15 -1
View File
@@ -9,5 +9,19 @@ index: true
The default MDX components include Cards, Callouts, Code Blocks and Headings.
```ts
import defaultComponents from 'fumadocs-ui/mdx';
import defaultMdxComponents from 'fumadocs-ui/mdx';
```
### Relative Link
To support links with relative file path in `href`, override the default `a` component with:
```ts
import { createRelativeLink } from 'fumadocs-ui/mdx.server';
import { source } from '@/lib/source';
const page = source.getPage(['...']);
const a = createRelativeLink(source, page);
```
<Callout type="warn">Server Component only.</Callout>
@@ -9,6 +9,7 @@ import {
import { notFound } from 'next/navigation';
import { MDXContent } from '@content-collections/mdx/react';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { createRelativeLink } from 'fumadocs-ui/mdx.server';
export default async function Page(props: {
params: Promise<{ slug?: string[] }>;
@@ -24,7 +25,12 @@ export default async function Page(props: {
<DocsBody>
<MDXContent
code={page.data.body}
components={{ ...defaultMdxComponents }}
components={{
...defaultMdxComponents,
// this allows you to link to other pages with relative file paths
a: createRelativeLink(source, page),
// you can add other MDX components here
}}
/>
</DocsBody>
</DocsPage>
@@ -7,6 +7,7 @@ import {
} from 'fumadocs-ui/page';
import { notFound } from 'next/navigation';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { createRelativeLink } from 'fumadocs-ui/mdx.server';
export default async function Page(props: {
params: Promise<{ slug?: string[] }>;
@@ -15,14 +16,21 @@ export default async function Page(props: {
const page = source.getPage(params.slug);
if (!page) notFound();
const MDX = page.data.body;
const MDXContent = page.data.body;
return (
<DocsPage toc={page.data.toc} full={page.data.full}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX components={{ ...defaultMdxComponents }} />
<MDXContent
components={{
...defaultMdxComponents,
// this allows you to link to other pages with relative file paths
a: createRelativeLink(source, page),
// you can add other MDX components here
}}
/>
</DocsBody>
</DocsPage>
);
+4
View File
@@ -40,6 +40,10 @@
"import": "./dist/mdx.js",
"types": "./dist/mdx.d.ts"
},
"./mdx.server": {
"import": "./dist/mdx.server.js",
"types": "./dist/mdx.server.d.ts"
},
"./og": {
"import": "./dist/og.js",
"types": "./dist/og.d.ts"
+33
View File
@@ -0,0 +1,33 @@
import type { LoaderConfig, LoaderOutput, Page } from 'fumadocs-core/source';
import type { ComponentProps, FC } from 'react';
import defaultMdxComponents from '@/mdx';
import * as path from 'node:path';
/**
* Extend the default Link component to resolve relative file paths in `href`.
*
* @param page the current page
* @param source the source object
* @param OverrideLink The component to override from
*/
export function createRelativeLink(
source: LoaderOutput<LoaderConfig>,
page: Page,
OverrideLink: FC<ComponentProps<'a'>> = defaultMdxComponents.a,
): FC<ComponentProps<'a'>> {
return async function RelativeLink({ href, ...props }) {
if (
href &&
href.startsWith('.') &&
(href.endsWith('.md') || href.endsWith('.mdx'))
) {
const pages = source.getPages();
const hrefPath = path.join(page.file.dirname, href);
const targetPage = pages.find((item) => item.file.path === hrefPath);
if (targetPage) href = targetPage.url;
}
return <OverrideLink href={href} {...props} />;
};
}