1
0

add: docs

This commit is contained in:
alikia2x (寒寒) 2026-01-23 14:22:51 +08:00
parent cd2e9c589e
commit 3b6bed8c80
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG Key ID: 56209E0CCD8420C6
18 changed files with 1546 additions and 0 deletions

7
packages/docs/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
/node_modules/
# React Router
/.react-router/
/build/
.source

View File

@ -0,0 +1,3 @@
@import "tailwindcss";
@import "fumadocs-ui/css/neutral.css";
@import "fumadocs-ui/css/preset.css";

View File

@ -0,0 +1,51 @@
import browserCollections from "fumadocs-mdx:collections/browser";
import { useFumadocsLoader } from "fumadocs-core/source/client";
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/layouts/docs/page";
import defaultMdxComponents from "fumadocs-ui/mdx";
import { baseOptions } from "@/lib/layout.shared";
import { source } from "@/lib/source";
import type { Route } from "./+types/page";
export async function loader({ params }: Route.LoaderArgs) {
const slugs = params["*"].split("/").filter((v) => v.length > 0);
const page = source.getPage(slugs);
if (!page) throw new Response("Not found", { status: 404 });
return {
path: page.path,
pageTree: await source.serializePageTree(source.getPageTree()),
};
}
const clientLoader = browserCollections.docs.createClientLoader({
component(
{ toc, frontmatter, default: Mdx },
// you can define props for the `<Content />` component
props?: {
className?: string;
}
) {
return (
<DocsPage toc={toc} {...props}>
<title>{frontmatter.title}</title>
<meta name="description" content={frontmatter.description} />
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<DocsBody>
<Mdx components={{ ...defaultMdxComponents }} />
</DocsBody>
</DocsPage>
);
},
});
export default function Page({ loaderData }: Route.ComponentProps) {
const { path, pageTree } = useFumadocsLoader(loaderData);
return (
<DocsLayout {...baseOptions()} tree={pageTree}>
{clientLoader.useContent(path)}
</DocsLayout>
);
}

View File

@ -0,0 +1,12 @@
import { createFromSource } from "fumadocs-core/search/server";
import { source } from "@/lib/source";
import type { Route } from "./+types/search";
const server = createFromSource(source, {
// https://docs.orama.com/docs/orama-js/supported-languages
language: "english",
});
export async function loader({ request }: Route.LoaderArgs) {
return server.GET(request);
}

View File

@ -0,0 +1,9 @@
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: "React Router",
},
};
}

View File

@ -0,0 +1,7 @@
import { docs } from "fumadocs-mdx:collections/server";
import { loader } from "fumadocs-core/source";
export const source = loader({
source: docs.toFumadocsSource(),
baseUrl: "/docs",
});

View File

@ -0,0 +1,75 @@
import { RootProvider } from "fumadocs-ui/provider/react-router";
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import type { Route } from "./+types/root";
import "./app.css";
export const links: Route.LinksFunction = () => [
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossOrigin: "anonymous",
},
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
},
];
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="flex flex-col min-h-screen">
<RootProvider>{children}</RootProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error";
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}
return (
<main className="pt-16 p-4 w-full max-w-350 mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
}

View File

@ -0,0 +1,7 @@
import { index, type RouteConfig, route } from "@react-router/dev/routes";
export default [
index("routes/home.tsx"),
route("docs/*", "docs/page.tsx"),
route("api/search", "docs/search.ts"),
] satisfies RouteConfig;

View File

@ -0,0 +1,30 @@
import { HomeLayout } from "fumadocs-ui/layouts/home";
import { Link } from "react-router";
import { baseOptions } from "@/lib/layout.shared";
import type { Route } from "./+types/home";
export function meta(_: Route.MetaArgs) {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
}
export default function Home() {
return (
<HomeLayout {...baseOptions()}>
<div className="p-4 flex flex-col items-center justify-center text-center flex-1">
<h1 className="text-xl font-bold mb-2">Fumadocs on React Router.</h1>
<p className="text-fd-muted-foreground mb-4">
The truly flexible docs framework on React.js.
</p>
<Link
className="text-sm bg-fd-primary text-fd-primary-foreground rounded-full font-medium px-4 py-2.5"
to="/docs"
>
Open Docs
</Link>
</div>
</HomeLayout>
);
}

1180
packages/docs/bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
---
title: Hello World
description: |
Your first `document`
You'll love it!
---
Hey there! Fumadocs is the docs framework that also works on React Router!
## Heading
Hello World
<Cards>
<Card title="Learn more about React Router" href="https://reactrouter.com" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
</Cards>
```ts
console.log('I love React!');
```
### Heading
#### Heading
| Head | Description |
| ------------------------------- | ----------------------------------- |
| `hello` | Hello World |
| very **important** | Hey |
| _Surprisingly_ | Fumadocs |
| very long text that looks weird | hello world hello world hello world |

View File

@ -0,0 +1,3 @@
{
"pages": ["index", "..."]
}

View File

@ -0,0 +1,24 @@
---
title: Test
description: A document to test Fumadocs
---
Hey there!
## Cards
<Cards>
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
</Cards>
### CodeBlock
```js
console.log('Hello World');
```
#### List
- Hello
- World

View File

@ -0,0 +1,36 @@
{
"name": "docs",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"types:check": "react-router typegen && fumadocs-mdx && tsc --noEmit",
"postinstall": "fumadocs-mdx"
},
"dependencies": {
"@react-router/node": "^7.12.0",
"@react-router/serve": "^7.12.0",
"fumadocs-core": "16.4.7",
"fumadocs-mdx": "14.2.6",
"fumadocs-ui": "16.4.7",
"isbot": "^5.1.32",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-router": "^7.12.0"
},
"devDependencies": {
"@react-router/dev": "^7.12.0",
"@tailwindcss/vite": "^4.1.18",
"@types/mdx": "^2.0.13",
"@types/node": "^25.0.5",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"react-router-devtools": "^6.1.0",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-tsconfig-paths": "^6.0.4"
}
}

View File

@ -0,0 +1,23 @@
import { glob } from "node:fs/promises";
import type { Config } from "@react-router/dev/config";
import { createGetUrl, getSlugs } from "fumadocs-core/source";
const getUrl = createGetUrl("/docs");
export default {
ssr: true,
async prerender({ getStaticPaths }) {
const paths: string[] = [];
const excluded: string[] = ["/api/search"];
for (const path of getStaticPaths()) {
if (!excluded.includes(path)) paths.push(path);
}
for await (const entry of glob("**/*.mdx", { cwd: "content/docs" })) {
paths.push(getUrl(getSlugs(entry)));
}
return paths;
},
} satisfies Config;

View File

@ -0,0 +1,7 @@
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
export const docs = defineDocs({
dir: "content",
});
export default defineConfig();

View File

@ -0,0 +1,23 @@
{
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"@/*": ["./app/*"],
"fumadocs-mdx:collections/*": [".source/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
}
}

View File

@ -0,0 +1,17 @@
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import mdx from "fumadocs-mdx/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import * as MdxConfig from "./source.config";
export default defineConfig({
plugins: [
mdx(MdxConfig),
tailwindcss(),
reactRouter(),
tsconfigPaths({
root: __dirname,
}),
],
});