refactor: enhance documentation feature and update dependencies
- Updated content collections to include new schemas for better document handling. - Added new documentation files for comparisons, customization, and internationalization. - Introduced a manual installation guide and improved markdown support. - Updated package.json with new dependencies for enhanced functionality. - Added new images for documentation and improved layout components for better user experience. - Adjusted TypeScript configurations for better path management.
@ -1,5 +1,5 @@
|
|||||||
import { DEFAULT_LOCALE, LOCALES } from "@/i18n/routing";
|
import { DEFAULT_LOCALE, LOCALES } from "@/i18n/routing";
|
||||||
import { defineCollection, defineConfig } from "@content-collections/core";
|
import { defineCollection, defineConfig, z } from "@content-collections/core";
|
||||||
import {
|
import {
|
||||||
createDocSchema,
|
createDocSchema,
|
||||||
createMetaSchema,
|
createMetaSchema,
|
||||||
@ -22,7 +22,11 @@ const docs = defineCollection({
|
|||||||
name: 'docs',
|
name: 'docs',
|
||||||
directory: 'content/docs',
|
directory: 'content/docs',
|
||||||
include: '**/*.mdx',
|
include: '**/*.mdx',
|
||||||
schema: createDocSchema,
|
schema: (z) => ({
|
||||||
|
...createDocSchema(z),
|
||||||
|
preview: z.string().optional(),
|
||||||
|
index: z.boolean().default(false),
|
||||||
|
}),
|
||||||
transform: transformMDX,
|
transform: transformMDX,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
74
content/docs/comparisons.mdx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
title: Comparisons
|
||||||
|
description: How is Fumadocs different from other existing frameworks?
|
||||||
|
icon: GitCompareArrows
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nextra
|
||||||
|
|
||||||
|
Fumadocs is highly inspired by Nextra. For example, the Routing Conventions. That is why
|
||||||
|
`meta.json` also exists in Fumadocs.
|
||||||
|
|
||||||
|
Nextra is more opinionated than Fumadocs. Fumadocs is accelerated by App Router. As a result, It provides many server-side functions, and you have to
|
||||||
|
configure things manually compared to simply editing a configuration file.
|
||||||
|
|
||||||
|
Fumadocs works great if you want more control over everything, such as
|
||||||
|
adding it to an existing codebase or implementing advanced routing.
|
||||||
|
|
||||||
|
### Feature Table
|
||||||
|
|
||||||
|
| Feature | Fumadocs | Nextra |
|
||||||
|
| ------------------- | ------------ | ------------------------- |
|
||||||
|
| Static Generation | Yes | Yes |
|
||||||
|
| Cached | Yes | Yes |
|
||||||
|
| Light/Dark Mode | Yes | Yes |
|
||||||
|
| Syntax Highlighting | Yes | Yes |
|
||||||
|
| Table of Contents | Yes | Yes |
|
||||||
|
| Full-text Search | Yes | Yes |
|
||||||
|
| i18n | Yes | Yes |
|
||||||
|
| Last Git Edit Time | Yes | Yes |
|
||||||
|
| Page Icons | Yes | Yes, via `_meta.js` files |
|
||||||
|
| RSC | Yes | Yes |
|
||||||
|
| Remote Source | Yes | Yes |
|
||||||
|
| SEO | Via Metadata | Yes |
|
||||||
|
| Built-in Components | Yes | Yes |
|
||||||
|
| RTL Layout | Yes | Yes |
|
||||||
|
|
||||||
|
### Additional Features
|
||||||
|
|
||||||
|
Features supported via 3rd party libraries like [TypeDoc](https://typedoc.org) will not be listed here.
|
||||||
|
|
||||||
|
| Feature | Fumadocs | Nextra |
|
||||||
|
| -------------------------- | -------- | ------ |
|
||||||
|
| OpenAPI Integration | Yes | No |
|
||||||
|
| TypeScript Docs Generation | Yes | No |
|
||||||
|
| TypeScript Twoslash | Yes | Yes |
|
||||||
|
|
||||||
|
## Mintlify
|
||||||
|
|
||||||
|
Mintlify is a documentation service, as compared to Fumadocs, it offers a free tier but isn't completely free and open source.
|
||||||
|
|
||||||
|
Fumadocs is not as powerful as Mintlify, for example, the OpenAPI integration of Mintlify.
|
||||||
|
As the creator of Fumadocs, I wouldn't recommend switching to Fumadocs from Mintlify if you're satisfied with the current way you build docs.
|
||||||
|
However, I believe Fumadocs is a suitable tool for all Next.js developers who want to have elegant docs.
|
||||||
|
|
||||||
|
## Docusaurus
|
||||||
|
|
||||||
|
Docusaurus is a powerful framework based on React.js. It offers many cool
|
||||||
|
features with plugins and custom themes.
|
||||||
|
|
||||||
|
### Better DX
|
||||||
|
|
||||||
|
Since Fumadocs is built on the top of Next.js, you'll have to start the Next.js dev
|
||||||
|
server every time to review changes, and initial boilerplate code is relatively more
|
||||||
|
compared to Docusaurus.
|
||||||
|
|
||||||
|
For a simple docs, Docusaurus might be a better choice if you don't need any Next.js specific functionality.
|
||||||
|
|
||||||
|
However, when you want to use Next.js, or seek extra customizability like tuning default UI components, Fumadocs could be a better choice.
|
||||||
|
|
||||||
|
### Plugins
|
||||||
|
|
||||||
|
You can easily achieve many things with plugins, their ecosystem is indeed larger and maintained by many contributors.
|
||||||
|
|
||||||
|
In comparison, the flexibility of Fumadocs allows you to implement them on your own, it may take longer to tune it to your satisfaction.
|
43
content/docs/components/accordion.mdx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
title: Accordion
|
||||||
|
description: Add Accordions to your documentation
|
||||||
|
preview: accordion
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Based on
|
||||||
|
[Radix UI Accordion](https://www.radix-ui.com/primitives/docs/components/accordion), useful for FAQ sections.
|
||||||
|
|
||||||
|
```tsx twoslash
|
||||||
|
import React from 'react';
|
||||||
|
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
|
||||||
|
|
||||||
|
<Accordions type="single">
|
||||||
|
<Accordion title="My Title">My Content</Accordion>
|
||||||
|
</Accordions>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accordions
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="AccordionsProps" />
|
||||||
|
|
||||||
|
### Accordion
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="AccordionProps" />
|
||||||
|
|
||||||
|
### Linking to Accordion
|
||||||
|
|
||||||
|
You can specify an `id` for accordion. The accordion will automatically open when the user is navigating to the page with the specified `id` in hash parameter.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Accordions>
|
||||||
|
<Accordion title="My Title" id="my-title">
|
||||||
|
|
||||||
|
My Content
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
</Accordions>
|
||||||
|
```
|
||||||
|
|
||||||
|
> The value of accordion is same as title by default. When an id presents, it will be used as the value instead.
|
142
content/docs/components/auto-type-table.mdx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
---
|
||||||
|
title: Auto Type Table
|
||||||
|
description: Auto-generated type table
|
||||||
|
---
|
||||||
|
|
||||||
|
<Wrapper>
|
||||||
|
|
||||||
|
<div className="bg-fd-background p-4 rounded-xl">
|
||||||
|
|
||||||
|
<AutoTypeTable name="AutoTypeTableExample" type={`export interface AutoTypeTableExample {
|
||||||
|
/**
|
||||||
|
* Markdown syntax like links, \`code\` are supported.
|
||||||
|
*
|
||||||
|
* See https://fumadocs.vercel.app/docs/components/type-table
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We love Shiki.
|
||||||
|
*
|
||||||
|
* \`\`\`ts
|
||||||
|
* console.log("Hello World, powered by Shiki");
|
||||||
|
* \`\`\`
|
||||||
|
*/
|
||||||
|
options: Partial<{ a: unknown }>;
|
||||||
|
|
||||||
|
}`} />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Wrapper>
|
||||||
|
|
||||||
|
It generates a table for your docs based on TypeScript definitions.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```package-install
|
||||||
|
fumadocs-typescript
|
||||||
|
```
|
||||||
|
|
||||||
|
Initialize the TypeScript compiler and add it as a MDX component.
|
||||||
|
|
||||||
|
```tsx title="page.tsx"
|
||||||
|
import { createGenerator } from 'fumadocs-typescript';
|
||||||
|
import { AutoTypeTable } from 'fumadocs-typescript/ui';
|
||||||
|
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
||||||
|
|
||||||
|
const generator = createGenerator();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MDX
|
||||||
|
components={{
|
||||||
|
...defaultMdxComponents,
|
||||||
|
AutoTypeTable: (props) => (
|
||||||
|
<AutoTypeTable {...props} generator={generator} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### From File
|
||||||
|
|
||||||
|
It accepts a `path` prop that points to a typescript file, and `name` for the exported type name.
|
||||||
|
|
||||||
|
```ts title="path/to/file.ts"
|
||||||
|
export interface MyInterface {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<AutoTypeTable path="./path/to/file.ts" name="MyInterface" />
|
||||||
|
```
|
||||||
|
|
||||||
|
The path is relative to your project directory (`cwd`), because `AutoTypeTable` is a React Server Component, it cannot access build-time information like MDX file path.
|
||||||
|
|
||||||
|
<Callout title="Server Component only" type="warn">
|
||||||
|
|
||||||
|
You cannot use this in a client component.
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### From Type
|
||||||
|
|
||||||
|
You can specify the type to generate, without an actual TypeScript file.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { AutoTypeTable } from 'fumadocs-typescript/ui';
|
||||||
|
|
||||||
|
<AutoTypeTable type="{ hello: string }" />
|
||||||
|
```
|
||||||
|
|
||||||
|
When a `path` is given, it shares the same context as the TypeScript file.
|
||||||
|
|
||||||
|
```ts title="file.ts"
|
||||||
|
export type A = { hello: string };
|
||||||
|
```
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<AutoTypeTable path="file.ts" type="A & { world: string }" />
|
||||||
|
```
|
||||||
|
|
||||||
|
When `type` has multiple lines, the export statement and `name` prop are required.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<AutoTypeTable
|
||||||
|
path="file.ts"
|
||||||
|
name="B"
|
||||||
|
type={`
|
||||||
|
import { ReactNode } from "react"
|
||||||
|
export type B = ReactNode | { world: string }
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
Notice that only object type is allowed. For functions, you should wrap them into an object instead.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface MyInterface {
|
||||||
|
myFn: (input: string) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
<auto-type-table path="../props.ts" name="AutoTypeTableProps" />
|
||||||
|
|
||||||
|
### File System
|
||||||
|
|
||||||
|
It relies on the file system, hence, the page referencing this component must be built in **build time**. Rendering the component on serverless runtime may cause problems.
|
||||||
|
|
||||||
|
### Deep Dive
|
||||||
|
|
||||||
|
Under the hood, it uses the [Typescript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) to extract type information.
|
||||||
|
Your `tsconfig.json` file in the current working directory will be loaded.
|
||||||
|
|
||||||
|
To change the compiler settings, pass a `options` prop to the component.
|
||||||
|
|
||||||
|
Learn more about [Typescript Docs Generation](/docs/typescript).
|
61
content/docs/components/banner.mdx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
title: Banner
|
||||||
|
description: Add a banner to your site
|
||||||
|
preview: banner
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Put the element at the top of your root layout, you can use it for displaying announcements.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Banner } from 'fumadocs-ui/components/banner';
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<Banner>Hello World</Banner>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variant
|
||||||
|
|
||||||
|
Change the default variant.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Banner } from 'fumadocs-ui/components/banner';
|
||||||
|
|
||||||
|
<Banner variant="rainbow">Hello World</Banner>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Layout
|
||||||
|
|
||||||
|
By default, the banner uses a `style` tag to modify Fumadocs layouts (e.g. reduce the sidebar height).
|
||||||
|
You can disable it with:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Banner } from 'fumadocs-ui/components/banner';
|
||||||
|
|
||||||
|
<Banner changeLayout={false}>Hello World</Banner>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Close
|
||||||
|
|
||||||
|
To allow users to close the banner, give the banner an ID.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Banner } from 'fumadocs-ui/components/banner';
|
||||||
|
|
||||||
|
<Banner id="hello-world">Hello World</Banner>;
|
||||||
|
```
|
||||||
|
|
||||||
|
The state will be automatically persisted.
|
38
content/docs/components/dynamic-codeblock.mdx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
title: Code Block (Dynamic)
|
||||||
|
description: A codeblock that also highlights code
|
||||||
|
preview: dynamicCodeBlock
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock';
|
||||||
|
|
||||||
|
<DynamicCodeBlock lang="ts" code='console.log("Hello World")' />;
|
||||||
|
```
|
||||||
|
|
||||||
|
This component, different from the MDX [`CodeBlock`](/docs/mdx/codeblock) component, can be used without MDX.
|
||||||
|
It highlights the code with Shiki and use the default component to render it.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- Can be pre-rendered on server
|
||||||
|
- load languages and themes on browser lazily
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock';
|
||||||
|
|
||||||
|
<DynamicCodeBlock
|
||||||
|
lang="ts"
|
||||||
|
code='console.log("Hello World")'
|
||||||
|
options={{
|
||||||
|
components: {
|
||||||
|
// add/override components
|
||||||
|
},
|
||||||
|
// or Shiki options
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
35
content/docs/components/files.mdx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: Files
|
||||||
|
description: Display file structure in your documentation
|
||||||
|
preview: 'files'
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Wrap file components in `Files`.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { File, Folder, Files } from 'fumadocs-ui/components/files';
|
||||||
|
|
||||||
|
<Files>
|
||||||
|
<Folder name="app" defaultOpen>
|
||||||
|
<File name="layout.tsx" />
|
||||||
|
<File name="page.tsx" />
|
||||||
|
<File name="global.css" />
|
||||||
|
</Folder>
|
||||||
|
<Folder name="components">
|
||||||
|
<File name="button.tsx" />
|
||||||
|
<File name="tabs.tsx" />
|
||||||
|
<File name="dialog.tsx" />
|
||||||
|
</Folder>
|
||||||
|
<File name="package.json" />
|
||||||
|
</Files>
|
||||||
|
```
|
||||||
|
|
||||||
|
### File
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="FileProps" />
|
||||||
|
|
||||||
|
### Folder
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="FolderProps" />
|
45
content/docs/components/github-info.mdx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
title: GitHub Info
|
||||||
|
description: Display your GitHub repository information
|
||||||
|
preview: githubInfo
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { GithubInfo } from 'fumadocs-ui/components/github-info';
|
||||||
|
|
||||||
|
<GithubInfo
|
||||||
|
owner="fuma-nama"
|
||||||
|
repo="fumadocs"
|
||||||
|
// your own GitHub access token (optional)
|
||||||
|
token={process.env.GITHUB_TOKEN}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
It's recommended to add it to your docs layout with `links` option:
|
||||||
|
|
||||||
|
```tsx title="app/docs/layout.tsx"
|
||||||
|
import { DocsLayout, type DocsLayoutProps } from 'fumadocs-ui/layouts/notebook';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { baseOptions } from '@/app/layout.config';
|
||||||
|
import { source } from '@/lib/source';
|
||||||
|
import { GithubInfo } from 'fumadocs-ui/components/github-info';
|
||||||
|
|
||||||
|
const docsOptions: DocsLayoutProps = {
|
||||||
|
...baseOptions,
|
||||||
|
tree: source.pageTree,
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
children: (
|
||||||
|
<GithubInfo owner="fuma-nama" repo="fumadocs" className="lg:-mx-2" />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return <DocsLayout {...docsOptions}>{children}</DocsLayout>;
|
||||||
|
}
|
||||||
|
```
|
34
content/docs/components/image-zoom.mdx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: Zoomable Image
|
||||||
|
description: Allow zoom-in images in your documentation
|
||||||
|
preview: zoomImage
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Replace `img` with `ImageZoom` in your MDX components.
|
||||||
|
|
||||||
|
```tsx title="app/docs/[[...slug]]/page.tsx"
|
||||||
|
import { ImageZoom } from 'fumadocs-ui/components/image-zoom';
|
||||||
|
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MdxContent
|
||||||
|
components={{
|
||||||
|
...defaultMdxComponents,
|
||||||
|
img: (props) => <ImageZoom {...(props as any)} />,
|
||||||
|
// other Mdx components
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Now image zoom will be automatically enabled on all images.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Optimization
|
||||||
|
|
||||||
|
A default [`sizes` property](https://nextjs.org/docs/app/api-reference/components/image#sizes) will be defined for Next.js `<Image />` component if not specified.
|
5
content/docs/components/index.mdx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: Components
|
||||||
|
description: Additional components to improve your docs
|
||||||
|
index: true
|
||||||
|
---
|
31
content/docs/components/inline-toc.mdx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: Inline TOC
|
||||||
|
description: Add Inline TOC into your documentation
|
||||||
|
preview: inlineTOC
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Pass TOC items to the component.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { InlineTOC } from 'fumadocs-ui/components/inline-toc';
|
||||||
|
|
||||||
|
<InlineTOC items={toc} />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use in Pages
|
||||||
|
|
||||||
|
You can add inline TOC into every page.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DocsPage>
|
||||||
|
...
|
||||||
|
<InlineTOC items={toc} />
|
||||||
|
...
|
||||||
|
</DocsPage>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="InlineTOCProps" />
|
35
content/docs/components/root-toggle.mdx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: Root Toggle
|
||||||
|
description: Switch between page trees
|
||||||
|
preview: rootToggle
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usages
|
||||||
|
|
||||||
|
Add this component to your sidebar or other places you want.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||||
|
import { RootToggle } from 'fumadocs-ui/components/layout/root-toggle';
|
||||||
|
|
||||||
|
<DocsLayout
|
||||||
|
sidebar={{
|
||||||
|
banner: (
|
||||||
|
<RootToggle
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
title: 'Folder 1',
|
||||||
|
description: 'Pages in folder 1',
|
||||||
|
url: '/path/to/page-tree-1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Folder 2',
|
||||||
|
description: 'Pages in folder 2',
|
||||||
|
url: '/path/to/page-tree-2',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
57
content/docs/components/steps.mdx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
title: Steps
|
||||||
|
description: Adding steps to your docs
|
||||||
|
preview: steps
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Put your steps into the `Steps` container.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
|
||||||
|
### Hello World
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
|
||||||
|
### Hello World
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
```
|
||||||
|
|
||||||
|
> We recommend using Tailwind CSS utility classes directly on Tailwind CSS projects.
|
||||||
|
|
||||||
|
### Without imports
|
||||||
|
|
||||||
|
You can use the Tailwind CSS utilities without importing it.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<div className="fd-steps">
|
||||||
|
<div className="fd-step" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
It supports adding step styles to only headings with arbitrary variants.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<div className='fd-steps [&_h3]:fd-step'>
|
||||||
|
|
||||||
|
### Hello World
|
||||||
|
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
<div className='fd-steps [&_h3]:fd-step'>
|
||||||
|
|
||||||
|
### Hello World
|
||||||
|
|
||||||
|
You no longer need to use the step component anymore.
|
||||||
|
|
||||||
|
</div>
|
45
content/docs/components/tabs.client.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { buttonVariants } from '@/components/ui/button';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export function UrlBar() {
|
||||||
|
const [url, setUrl] = useState('');
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setUrl(window.location.pathname + window.location.hash);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <pre className="rounded-lg border bg-card p-2 text-sm">{url}</pre>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WithoutValueTest() {
|
||||||
|
const [items, setItems] = useState(['Item 1', 'Item 2']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tabs items={items}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Tab key={item}>{item}</Tab>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: 'secondary',
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
onClick={() => setItems(['Item 1', 'Item 3', 'Item 2'])}
|
||||||
|
>
|
||||||
|
Change Items
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
146
content/docs/components/tabs.mdx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
---
|
||||||
|
title: Tabs
|
||||||
|
description:
|
||||||
|
A Tabs component built with Radix UI, with additional features such as
|
||||||
|
persistent and shared value.
|
||||||
|
preview: tabs
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Import it in your MDX documents.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||||
|
|
||||||
|
<Tabs items={['Javascript', 'Rust']}>
|
||||||
|
<Tab value="Javascript">Javascript is weird</Tab>
|
||||||
|
<Tab value="Rust">Rust is fast</Tab>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Without `value`
|
||||||
|
|
||||||
|
Without a `value`, it detects from the children index. Note that it might cause errors on re-renders, it's not encouraged if the tabs might change.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||||
|
|
||||||
|
<Tabs items={['Javascript', 'Rust']}>
|
||||||
|
<Tab>Javascript is weird</Tab>
|
||||||
|
<Tab>Rust is fast</Tab>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Demo with Re-renders
|
||||||
|
|
||||||
|
<Tabs items={['Javascript', 'Rust']}>
|
||||||
|
<Tab>Javascript is weird</Tab>
|
||||||
|
<Tab>Rust is fast</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<WithoutValueTest />
|
||||||
|
|
||||||
|
### Shared Value
|
||||||
|
|
||||||
|
By passing an `groupId` property, you can share a value across all tabs with the same
|
||||||
|
id.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Tabs groupId="language" items={['Javascript', 'Rust']}>
|
||||||
|
<Tab value="Javascript">Javascript is weird</Tab>
|
||||||
|
<Tab value="Rust">Rust is fast</Tab>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Persistent
|
||||||
|
|
||||||
|
You can enable persistent by passing a `persist` property. The value will be
|
||||||
|
stored in `localStorage`, with its id as the key.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Tabs groupId="language" items={['Javascript', 'Rust']} persist>
|
||||||
|
<Tab value="Javascript">Javascript is weird</Tab>
|
||||||
|
<Tab value="Rust">Rust is fast</Tab>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
> Persistent only works if you have passed an `id`.
|
||||||
|
|
||||||
|
### Default Value
|
||||||
|
|
||||||
|
Set a default value by passing `defaultIndex`.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Tabs items={['Javascript', 'Rust']} defaultIndex={1}>
|
||||||
|
<Tab value="Javascript">Javascript is weird</Tab>
|
||||||
|
<Tab value="Rust">Rust is fast</Tab>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Link to Tab
|
||||||
|
|
||||||
|
Use HTML `id` attribute to link to a specific tab.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Tabs items={['Javascript', 'Rust', 'C++']}>
|
||||||
|
<Tab value="Javascript">Javascript is weird</Tab>
|
||||||
|
<Tab value="Rust">Rust is fast</Tab>
|
||||||
|
<Tab id="tab-cpp" value="C++">
|
||||||
|
`Hello World`
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can add the hash `#tab-cpp` to your URL and reload, the C++ tab will be activated.
|
||||||
|
|
||||||
|
<Tabs items={['Javascript', 'Rust', 'C++']}>
|
||||||
|
<Tab value="Javascript">Javascript is weird</Tab>
|
||||||
|
<Tab value="Rust">Rust is fast</Tab>
|
||||||
|
<Tab id="tab-cpp" value="C++">
|
||||||
|
`Hello World`
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Additionally, the `updateAnchor` property can be set to `true` in the `Tabs` component
|
||||||
|
to automatically update the URL hash whenever time a new tab is selected:
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Tabs items={['Javascript', 'Rust', 'C++']} updateAnchor>
|
||||||
|
<Tab id="tab-js" value="Javascript">
|
||||||
|
Javascript is weird
|
||||||
|
</Tab>
|
||||||
|
<Tab id="tab-rs" value="Rust">
|
||||||
|
Rust is fast
|
||||||
|
</Tab>
|
||||||
|
<Tab id="tab-cpp" value="C++">
|
||||||
|
`Hello World`
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
<UrlBar />
|
||||||
|
|
||||||
|
<Tabs items={['Hello', 'World']} updateAnchor>
|
||||||
|
<Tab id="tab-hello" value="Hello">
|
||||||
|
Hello!
|
||||||
|
</Tab>
|
||||||
|
<Tab id="tab-world" value="World">
|
||||||
|
World!
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Advanced
|
||||||
|
|
||||||
|
You can use the styled Radix UI primitive directly from exported `Primitive`.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { Primitive } from 'fumadocs-ui/components/tabs';
|
||||||
|
|
||||||
|
<Primitive.Tabs>
|
||||||
|
<Primitive.TabsList>
|
||||||
|
<Primitive.TabsTrigger />
|
||||||
|
</Primitive.TabsList>
|
||||||
|
<Primitive.TabsContent />
|
||||||
|
</Primitive.Tabs>
|
||||||
|
```
|
34
content/docs/components/type-table.mdx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: Type Table
|
||||||
|
description: A table for documenting types
|
||||||
|
preview: typeTable
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
It accepts a `type` property.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { TypeTable } from 'fumadocs-ui/components/type-table';
|
||||||
|
|
||||||
|
<TypeTable
|
||||||
|
type={{
|
||||||
|
percentage: {
|
||||||
|
description:
|
||||||
|
'The percentage of scroll position to display the roll button',
|
||||||
|
type: 'number',
|
||||||
|
default: 0.2,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
### Type Table
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="TypeTableProps" />
|
||||||
|
|
||||||
|
### Object Type
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="ObjectTypeProps" />
|
60
content/docs/customisation.mdx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: Overview
|
||||||
|
description: An overview of Fumadocs UI
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
<UiOverview />
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| ------------- | ------------------------------------------------------- |
|
||||||
|
| **Sidebar** | Display site title and navigation elements. |
|
||||||
|
| **Page Tree** | Passed by you, mainly rendered as the items of sidebar. |
|
||||||
|
| **Docs Page** | All content of the page. |
|
||||||
|
| **TOC** | Navigation within the article. |
|
||||||
|
|
||||||
|
### Page Tree
|
||||||
|
|
||||||
|
Navigation elements like sidebar take a [Page Tree](/docs/headless/page-tree) to render navigation links, it's a tree that describes all available pages and folders.
|
||||||
|
|
||||||
|
Normally, it is generated from your file structure using [`loader()`](/docs/headless/source-api), you can learn [how to organize pages](/docs/page-conventions).
|
||||||
|
|
||||||
|
## Customisation
|
||||||
|
|
||||||
|
### Layouts
|
||||||
|
|
||||||
|
You can use the exposed options of different layouts:
|
||||||
|
|
||||||
|
<Cards>
|
||||||
|
<Card title="Docs Layout" href="/docs/layouts/docs">
|
||||||
|
Layout for docs
|
||||||
|
</Card>
|
||||||
|
<Card title="Docs Page" href="/docs/layouts/page">
|
||||||
|
Layout for docs content
|
||||||
|
</Card>
|
||||||
|
<Card title="Notebook Layout" href="/docs/layouts/notebook">
|
||||||
|
A more compact version of Docs Layout
|
||||||
|
</Card>
|
||||||
|
<Card title="Home Layout" href="/docs/layouts/home-layout">
|
||||||
|
Layout for other pages
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
Fumadocs UI also offers styled components for interactive examples to enhance your docs, you can customise them with exposed props like `style` and `className`.
|
||||||
|
|
||||||
|
See [Components](/docs/components).
|
||||||
|
|
||||||
|
### Design System
|
||||||
|
|
||||||
|
Since the design system is built on Tailwind CSS, you can customise it [with CSS Variables](/docs/theme#colors).
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
If none of them suits you, Fumadocs CLI is a tool to install Fumadocs UI components and layouts to your codebase, similar to Shadcn UI. Allowing you to fully customise Fumadocs UI:
|
||||||
|
|
||||||
|
```package-install
|
||||||
|
npx fumadocs add
|
||||||
|
```
|
@ -1,214 +1,274 @@
|
|||||||
---
|
---
|
||||||
title: MkSaaS
|
title: Quick Start
|
||||||
description: MkSaaS is the best boilerplate for building AI SaaS websites.
|
description: Getting Started with Fumadocs
|
||||||
|
icon: Album
|
||||||
---
|
---
|
||||||
|
|
||||||
Until now, trying to style an article, document, or blog post with Tailwind has been a tedious task that required a keen eye for typography and a lot of complex custom CSS.
|
## Introduction
|
||||||
|
|
||||||
By default, Tailwind removes all of the default browser styling from paragraphs, headings, lists and more. This ends up being really useful for building application UIs because you spend less time undoing user-agent styles, but when you _really are_ just trying to style some content that came from a rich-text editor in a CMS or a markdown file, it can be surprising and unintuitive.
|
Fumadocs <span className='text-fd-muted-foreground text-sm'>(Foo-ma docs)</span> is a **documentation framework** based on Next.js, designed to be fast, flexible,
|
||||||
|
and composes seamlessly into Next.js App Router.
|
||||||
|
|
||||||
We get lots of complaints about it actually, with people regularly asking us things like:
|
Fumadocs has different parts:
|
||||||
|
|
||||||
> Why is Tailwind removing the default styles on my `h1` elements? How do I disable this? What do you mean I lose all the other base styles too?
|
<Cards>
|
||||||
> We hear you, but we're not convinced that simply disabling our base styles is what you really want. You don't want to have to remove annoying margins every time you use a `p` element in a piece of your dashboard UI. And I doubt you really want your blog posts to use the user-agent styles either — you want them to look _awesome_, not awful.
|
|
||||||
|
|
||||||
The `@tailwindcss/typography` plugin is our attempt to give you what you _actually_ want, without any of the downsides of doing something stupid like disabling our base styles.
|
<Card icon={<CpuIcon className="text-purple-300" />} title='Fumadocs Core'>
|
||||||
|
|
||||||
It adds a new `prose` class that you can slap on any block of vanilla HTML content and turn it into a beautiful, well-formatted document:
|
Handles most of the logic, including document search, content source adapters, and Markdown extensions.
|
||||||
|
|
||||||
```html
|
</Card>
|
||||||
<article class="prose">
|
|
||||||
<h1>Garlic bread with cheese: What the science tells us</h1>
|
<Card icon={<PanelsTopLeft className="text-blue-300" />} title='Fumadocs UI'>
|
||||||
<p>
|
|
||||||
For years parents have espoused the health benefits of eating garlic bread
|
The default theme of Fumadocs offers a beautiful look for documentation sites and interactive components.
|
||||||
with cheese to their children, with the food earning such an iconic status
|
|
||||||
in our culture that kids will often dress up as warm, cheesy loaf for
|
</Card>
|
||||||
Halloween.
|
|
||||||
</p>
|
<Card icon={<Database />} title='Content Source'>
|
||||||
<p>
|
|
||||||
But a recent study shows that the celebrated appetizer may be linked to a
|
The source of your content, can be a CMS or local data layers like [Content Collections](https://www.content-collections.dev) and [Fumadocs MDX](/docs/mdx), the official content source.
|
||||||
series of rabies cases springing up around the country.
|
|
||||||
</p>
|
</Card>
|
||||||
</article>
|
|
||||||
|
<Card icon={<Terminal />} title='Fumadocs CLI'>
|
||||||
|
|
||||||
|
A command line tool to install UI components and automate things, useful for customizing layouts.
|
||||||
|
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
</Cards>
|
||||||
|
|
||||||
|
<Callout title="Want to learn more?">
|
||||||
|
Read our in-depth [What is Fumadocs](/docs/what-is-fumadocs) introduction.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Terminology
|
||||||
|
|
||||||
|
**Markdown/MDX:** Markdown is a markup language for creating formatted text. Fumadocs supports Markdown and MDX (superset of Markdown) out-of-the-box.
|
||||||
|
|
||||||
|
Although not required, some basic knowledge of Next.js App Router would be useful for further customisations.
|
||||||
|
|
||||||
|
## Automatic Installation
|
||||||
|
|
||||||
|
A minimum version of Node.js 18 required, note that Node.js 23.1 might have problems with Next.js production build.
|
||||||
|
|
||||||
|
<Tabs groupId='package-manager' persist items={['npm', 'pnpm', 'yarn', 'bun']}>
|
||||||
|
|
||||||
|
```bash tab="npm"
|
||||||
|
npm create fumadocs-app
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about how to use the plugin and the features it includes, [read the documentation](https://github.com/tailwindcss/typography/blob/master/README.md).
|
```bash tab="pnpm"
|
||||||
|
pnpm create fumadocs-app
|
||||||
---
|
|
||||||
|
|
||||||
## What to expect from here on out
|
|
||||||
|
|
||||||
What follows from here is just a bunch of absolute nonsense I've written to dogfood the plugin itself. It includes every sensible typographic element I could think of, like **bold text**, unordered lists, ordered lists, code blocks, block quotes, _and even italics_.
|
|
||||||
|
|
||||||
It's important to cover all of these use cases for a few reasons:
|
|
||||||
|
|
||||||
1. We want everything to look good out of the box.
|
|
||||||
2. Really just the first reason, that's the whole point of the plugin.
|
|
||||||
3. Here's a third pretend reason though a list with three items looks more realistic than a list with two items.
|
|
||||||
|
|
||||||
Now we're going to try out another header style.
|
|
||||||
|
|
||||||
### Typography should be easy
|
|
||||||
|
|
||||||
So that's a header for you — with any luck if we've done our job correctly that will look pretty reasonable.
|
|
||||||
|
|
||||||
Something a wise person once told me about typography is:
|
|
||||||
|
|
||||||
> Typography is pretty important if you don't want your stuff to look like trash. Make it good then it won't be bad.
|
|
||||||
|
|
||||||
It's probably important that images look okay here by default as well:
|
|
||||||
|
|
||||||
{/* <img
|
|
||||||
src="/images/blog/mksaas-og.png"
|
|
||||||
width="718"
|
|
||||||
height="404"
|
|
||||||
alt="Image"
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.
|
|
||||||
|
|
||||||
Now I'm going to show you an example of an unordered list to make sure that looks good, too:
|
|
||||||
|
|
||||||
- So here is the first item in this list.
|
|
||||||
- In this example we're keeping the items short.
|
|
||||||
- Later, we'll use longer, more complex list items.
|
|
||||||
|
|
||||||
And that's the end of this section.
|
|
||||||
|
|
||||||
## What if we stack headings?
|
|
||||||
|
|
||||||
### We should make sure that looks good, too.
|
|
||||||
|
|
||||||
Sometimes you have headings directly underneath each other. In those cases you often have to undo the top margin on the second heading because it usually looks better for the headings to be closer together than a paragraph followed by a heading should be.
|
|
||||||
|
|
||||||
### When a heading comes after a paragraph …
|
|
||||||
|
|
||||||
When a heading comes after a paragraph, we need a bit more space, like I already mentioned above. Now let's see what a more complex list would look like.
|
|
||||||
|
|
||||||
- **I often do this thing where list items have headings.**
|
|
||||||
|
|
||||||
For some reason I think this looks cool which is unfortunate because it's pretty annoying to get the styles right.
|
|
||||||
|
|
||||||
I often have two or three paragraphs in these list items, too, so the hard part is getting the spacing between the paragraphs, list item heading, and separate list items to all make sense. Pretty tough honestly, you could make a strong argument that you just shouldn't write this way.
|
|
||||||
|
|
||||||
- **Since this is a list, I need at least two items.**
|
|
||||||
|
|
||||||
I explained what I'm doing already in the previous list item, but a list wouldn't be a list if it only had one item, and we really want this to look realistic. That's why I've added this second list item so I actually have something to look at when writing the styles.
|
|
||||||
|
|
||||||
- **It's not a bad idea to add a third item either.**
|
|
||||||
|
|
||||||
I think it probably would've been fine to just use two items but three is definitely not worse, and since I seem to be having no trouble making up arbitrary things to type, I might as well include it.
|
|
||||||
|
|
||||||
After this sort of list I usually have a closing statement or paragraph, because it kinda looks weird jumping right to a heading.
|
|
||||||
|
|
||||||
## Code should look okay by default.
|
|
||||||
|
|
||||||
I think most people are going to use [highlight.js](https://highlightjs.org/) or [Prism](https://prismjs.com/) or something if they want to style their code blocks but it wouldn't hurt to make them look _okay_ out of the box, even with no syntax highlighting.
|
|
||||||
|
|
||||||
Here's what a default `tailwind.config.js` file looks like at the time of writing:
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
purge: [],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
variants: {},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Hopefully that looks good enough to you.
|
```bash tab="yarn"
|
||||||
|
yarn create fumadocs-app
|
||||||
|
```
|
||||||
|
|
||||||
### What about nested lists?
|
```bash tab="bun"
|
||||||
|
bun create fumadocs-app
|
||||||
|
```
|
||||||
|
|
||||||
Nested lists basically always look bad which is why editors like Medium don't even let you do it, but I guess since some of you goofballs are going to do it we have to carry the burden of at least making it work.
|
</Tabs>
|
||||||
|
|
||||||
1. **Nested lists are rarely a good idea.**
|
It will ask you the framework and content source to use, a new fumadocs app should be initialized. Now you can start hacking!
|
||||||
- You might feel like you are being really "organized" or something but you are just creating a gross shape on the screen that is hard to read.
|
|
||||||
- Nested navigation in UIs is a bad idea too, keep things as flat as possible.
|
|
||||||
- Nesting tons of folders in your source code is also not helpful.
|
|
||||||
2. **Since we need to have more items, here's another one.**
|
|
||||||
- I'm not sure if we'll bother styling more than two levels deep.
|
|
||||||
- Two is already too much, three is guaranteed to be a bad idea.
|
|
||||||
- If you nest four levels deep you belong in prison.
|
|
||||||
3. **Two items isn't really a list, three is good though.**
|
|
||||||
- Again please don't nest lists if you want people to actually read your content.
|
|
||||||
- Nobody wants to look at this.
|
|
||||||
- I'm upset that we even have to bother styling this.
|
|
||||||
|
|
||||||
The most annoying thing about lists in Markdown is that `<li>` elements aren't given a child `<p>` tag unless there are multiple paragraphs in the list item. That means I have to worry about styling that annoying situation too.
|
<Callout title='From Existing Codebase?'>
|
||||||
|
|
||||||
- **For example, here's another nested list.**
|
You can follow the [Manual Installation](/docs/manual-installation) guide to get started.
|
||||||
|
|
||||||
But this time with a second paragraph.
|
</Callout>
|
||||||
|
|
||||||
- These list items won't have `<p>` tags
|
### Enjoy!
|
||||||
- Because they are only one line each
|
|
||||||
|
|
||||||
- **But in this second top-level list item, they will.**
|
Create your first MDX file in the docs folder.
|
||||||
|
|
||||||
This is especially annoying because of the spacing on this paragraph.
|
```mdx title="content/docs/index.mdx"
|
||||||
|
---
|
||||||
|
title: Hello World
|
||||||
|
---
|
||||||
|
|
||||||
- As you can see here, because I've added a second line, this list item now has a `<p>` tag.
|
## Yo what's up
|
||||||
|
```
|
||||||
|
|
||||||
This is the second line I'm talking about by the way.
|
Run the app in development mode and see http://localhost:3000/docs.
|
||||||
|
|
||||||
- Finally here's another list item so it's more like a list.
|
```package-install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
- A closing list item, but with no nested list, because why not?
|
## Explore
|
||||||
|
|
||||||
And finally a sentence to close off this section.
|
In the project, you can see:
|
||||||
|
|
||||||
## There are other elements we need to style
|
- `lib/source.ts`: Code for content source adapter, [`loader()`](/docs/headless/source-api) provides an interface to interact with your content source, and assigns URL to your pages.
|
||||||
|
- `app/layout.config.tsx`: Shared options for layouts, optional but preferred to keep.
|
||||||
|
|
||||||
I almost forgot to mention links, like [this link to the Tailwind CSS website](https://tailwindcss.com). We almost made them blue but that's so yesterday, so we went with dark gray, feels edgier.
|
| Route | Description |
|
||||||
|
| ------------------------- | ------------------------------------------------------ |
|
||||||
|
| `app/(home)` | The route group for your landing page and other pages. |
|
||||||
|
| `app/docs` | The documentation layout and pages. |
|
||||||
|
| `app/api/search/route.ts` | The Route Handler for search. |
|
||||||
|
|
||||||
We even included table styles, check it out:
|
### Writing Content
|
||||||
|
|
||||||
| Wrestler | Origin | Finisher |
|
For authoring docs, make sure to read:
|
||||||
| ----------------------- | ------------ | ------------------ |
|
|
||||||
| Bret "The Hitman" Hart | Calgary, AB | Sharpshooter |
|
|
||||||
| Stone Cold Steve Austin | Austin, TX | Stone Cold Stunner |
|
|
||||||
| Randy Savage | Sarasota, FL | Elbow Drop |
|
|
||||||
| Vader | Boulder, CO | Vader Bomb |
|
|
||||||
| Razor Ramon | Chuluota, FL | Razor's Edge |
|
|
||||||
|
|
||||||
We also need to make sure inline code looks good, like if I wanted to talk about `<span>` elements or tell you the good news about `@tailwindcss/typography`.
|
<Cards>
|
||||||
|
<Card href="/docs/markdown" title="Markdown">
|
||||||
|
Fumadocs has some additional features for authoring content too.
|
||||||
|
</Card>
|
||||||
|
<Card href="/docs/navigation" title="Navigation">
|
||||||
|
Learn how to customise navigation links/sidebar items.
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
|
||||||
### Sometimes I even use `code` in headings
|
### Content Source
|
||||||
|
|
||||||
Even though it's probably a bad idea, and historically I've had a hard time making it look good. This _"wrap the code blocks in backticks"_ trick works pretty well though really.
|
Content source handles all your content, like compiling Markdown files and validating frontmatter.
|
||||||
|
|
||||||
Another thing I've done in the past is put a `code` tag inside of a link, like if I wanted to tell you about the [`tailwindcss/docs`](https://github.com/tailwindcss/docs) repository. I don't love that there is an underline below the backticks but it is absolutely not worth the madness it would require to avoid it.
|
<Tabs items={['Fumadocs MDX', 'Custom Source']}>
|
||||||
|
|
||||||
#### We haven't used an `h4` yet
|
<Tab value='Fumadocs MDX'>
|
||||||
|
|
||||||
But now we have. Please don't use `h5` or `h6` in your content, Medium only supports two heading levels for a reason, you animals. I honestly considered using a `before` pseudo-element to scream at you if you use an `h5` or `h6`.
|
Read the [Introduction](/docs/mdx) to learn how it handles your content.
|
||||||
|
|
||||||
We don't style them at all out of the box because `h4` elements are already so small that they are the same size as the body copy. What are we supposed to do with an `h5`, make it _smaller_ than the body copy? No thanks.
|
A `source.config.ts` config file has been included, you can customise different options like frontmatter schema.
|
||||||
|
|
||||||
### We still need to think about stacked headings though.
|
</Tab>
|
||||||
|
|
||||||
#### Let's make sure we don't screw that up with `h4` elements, either.
|
<Tab value='Custom Source'>
|
||||||
|
|
||||||
Phew, with any luck we have styled the headings above this text and they look pretty good.
|
Fumadocs is not Markdown-exclusive. For other sources like Sanity, you can build a [custom content source](/docs/headless/custom-source).
|
||||||
|
|
||||||
Let's add a closing paragraph here so things end with a decently sized block of text. I can't explain why I want things to end that way but I have to assume it's because I think things will look weird or unbalanced if there is a heading too close to the end of the document.
|
</Tab>
|
||||||
|
|
||||||
What I've written here is probably long enough, but adding this final sentence can't hurt.
|
</Tabs>
|
||||||
|
|
||||||
## GitHub Flavored Markdown
|
### Customise UI
|
||||||
|
|
||||||
I've also added support for GitHub Flavored Mardown using `remark-gfm`.
|
See [Customisation Guide](/docs/customisation).
|
||||||
|
|
||||||
With `remark-gfm`, we get a few extra features in our markdown. Example: autolink literals.
|
## FAQ
|
||||||
|
|
||||||
A link like www.example.com or https://example.com would automatically be converted into an `a` tag.
|
Some common questions you may encounter.
|
||||||
|
|
||||||
This works for email links too: contact@example.com.
|
<Accordions>
|
||||||
|
<Accordion id='fix-monorepo-styling' title="How to fix stylings not being applied in Monorepo?">
|
||||||
|
|
||||||
{/* <Cards>
|
Sometimes, `fumadocs-ui` is not installed in the workspace of your Tailwind CSS configuration file. (e.g. a monorepo setup).
|
||||||
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
|
|
||||||
<Card title="Learn more about Fumadocs" href="https://fumadocs.vercel.app" />
|
You have to ensure the `fumadocs-ui` package is scanned by Tailwind CSS, and give a correct relative path to `@source`.
|
||||||
</Cards> */}
|
|
||||||
|
For example, add `../../` to point to the `node_modules` folder in root workspace.
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import 'tailwindcss';
|
||||||
|
@import 'fumadocs-ui/css/neutral.css';
|
||||||
|
@import 'fumadocs-ui/css/preset.css';
|
||||||
|
|
||||||
|
/* [!code --] */
|
||||||
|
@source '../node_modules/fumadocs-ui/dist/**/*.js';
|
||||||
|
/* [!code ++] */
|
||||||
|
@source '../../../node_modules/fumadocs-ui/dist/**/*.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
<Accordion id='change-base-url' title="How to change the base route of /docs?">
|
||||||
|
|
||||||
|
You can change the base route of docs (e.g. from `/docs/page` to `/info/page`).
|
||||||
|
Since Fumadocs uses Next.js App Router, you can simply rename the route:
|
||||||
|
|
||||||
|
<Files>
|
||||||
|
<Folder name="app/docs" defaultOpen className="opacity-50" disabled>
|
||||||
|
<File name="layout.tsx" />
|
||||||
|
</Folder>
|
||||||
|
<Folder name="app/info" defaultOpen>
|
||||||
|
<File name="layout.tsx" />
|
||||||
|
</Folder>
|
||||||
|
</Files>
|
||||||
|
|
||||||
|
And tell Fumadocs to use the new route in `source.ts`:
|
||||||
|
|
||||||
|
```ts title="lib/source.ts"
|
||||||
|
import { loader } from 'fumadocs-core/source';
|
||||||
|
|
||||||
|
export const source = loader({
|
||||||
|
baseUrl: '/info',
|
||||||
|
// other options
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
<Accordion id='dynamic-route' title="It uses Dynamic Route, will it be poor in performance?">
|
||||||
|
|
||||||
|
Next.js turns dynamic route into static routes when `generateStaticParams` is configured.
|
||||||
|
Hence, it is as fast as static pages.
|
||||||
|
|
||||||
|
You can enable Static Exports on Next.js to get a static build output. (Notice that Route Handler doesn't work with static export, you have to configure static search)
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
<Accordion id='custom-layout-docs-page' title='How to create a page in /docs without docs layout?'>
|
||||||
|
|
||||||
|
Same as managing layouts in Next.js App Router, remove the original MDX file from content directory (`/content/docs`).
|
||||||
|
This ensures duplicated pages will not cause errors.
|
||||||
|
|
||||||
|
Now, You can add the page to another route group, which isn't a descendant of docs layout.
|
||||||
|
|
||||||
|
For example, under your `app` folder:
|
||||||
|
|
||||||
|
<Files>
|
||||||
|
<File name="(home)/docs/page.tsx" />
|
||||||
|
<Folder name="docs">
|
||||||
|
<File name="layout.tsx" />
|
||||||
|
<File name="[[...slug]]/page.tsx" />
|
||||||
|
</Folder>
|
||||||
|
</Files>
|
||||||
|
|
||||||
|
will replace the `/docs` page with your `page.tsx`.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion id='multi-versions' title="How to implement docs with multi-version?">
|
||||||
|
Use a separate deployment for each version.
|
||||||
|
|
||||||
|
On Vercel, this can be done by creating another branch for a specific version on your GitHub repository.
|
||||||
|
To link to the sites of other versions, use the Links API or a custom navigation component.
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion id='multi-docs' title="How to implement multi-docs?">
|
||||||
|
We recommend to use [Sidebar Tabs](/docs/navigation/sidebar#sidebar-tabs).
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
</Accordions>
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
New to here? Don't worry, we are welcome for your questions.
|
||||||
|
|
||||||
|
If you find anything confusing, please give your feedback on [Github Discussion](https://github.com/fuma-nama/fumadocs/discussions)!
|
||||||
|
|
||||||
|
<Cards>
|
||||||
|
<Card
|
||||||
|
href="/docs/static-export"
|
||||||
|
title="Configure Static Export"
|
||||||
|
description="Learn how to enable static export on your docs"
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
href="/docs/search"
|
||||||
|
title="Customise Search"
|
||||||
|
description="Learn how to customise document search"
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
href="/docs/theme"
|
||||||
|
title="Theming"
|
||||||
|
description="Add themes to Fumadocs UI"
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
href="/docs/components"
|
||||||
|
title="Components"
|
||||||
|
description="See all available components to enhance your docs"
|
||||||
|
/>
|
||||||
|
</Cards>
|
||||||
|
@ -1,209 +0,0 @@
|
|||||||
---
|
|
||||||
title: MkSaaS
|
|
||||||
description: MkSaaS 是构建 AI SaaS 网站的最佳代码模板。
|
|
||||||
---
|
|
||||||
|
|
||||||
到目前为止,尝试使用 Tailwind 来设计文章、文档或博客文章的样式一直是一项繁琐的任务,需要对排版有敏锐的眼光,并且需要大量复杂的自定义 CSS。
|
|
||||||
|
|
||||||
默认情况下,Tailwind 会删除段落、标题、列表等所有默认的浏览器样式。这对于构建应用程序 UI 非常有用,因为您花更少的时间撤销用户代理样式,但是当您真的只是尝试设置来自 CMS 中富文本编辑器或 markdown 文件的内容的样式时,这可能会令人惊讶和不直观。
|
|
||||||
|
|
||||||
我们实际上收到了很多关于它的投诉,人们经常问我们这样的问题:
|
|
||||||
|
|
||||||
> 为什么 Tailwind 删除了我的 `h1` 元素上的默认样式?我如何禁用这个?你说我也会失去所有其他基本样式是什么意思?
|
|
||||||
> 我们听到了您的声音,但我们并不确信简单地禁用我们的基本样式就是您真正想要的。您不希望每次在仪表板 UI 的一部分中使用 `p` 元素时都必须删除烦人的边距。而且我怀疑您真的希望您的博客文章使用用户代理样式——您希望它们看起来很棒,而不是糟糕。
|
|
||||||
|
|
||||||
`@tailwindcss/typography` 插件是我们尝试给您真正想要的东西,而不会有做一些愚蠢的事情(比如禁用我们的基本样式)的任何缺点。
|
|
||||||
|
|
||||||
它添加了一个新的 `prose` 类,您可以将其应用于任何普通 HTML 内容块,并将其转变为一个美丽、格式良好的文档:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<article class="prose">
|
|
||||||
<h1>Garlic bread with cheese: What the science tells us</h1>
|
|
||||||
<p>
|
|
||||||
For years parents have espoused the health benefits of eating garlic bread
|
|
||||||
with cheese to their children, with the food earning such an iconic status
|
|
||||||
in our culture that kids will often dress up as warm, cheesy loaf for
|
|
||||||
Halloween.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
But a recent study shows that the celebrated appetizer may be linked to a
|
|
||||||
series of rabies cases springing up around the country.
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
```
|
|
||||||
|
|
||||||
有关如何使用该插件及其包含的功能的更多信息,[阅读文档](https://github.com/tailwindcss/typography/blob/master/README.md)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 从现在开始期待什么
|
|
||||||
|
|
||||||
从这里开始的是我写的一堆绝对无意义的内容,用来测试插件本身。它包括我能想到的每一个合理的排版元素,如**粗体文本**、无序列表、有序列表、代码块、块引用,_甚至斜体_。
|
|
||||||
|
|
||||||
涵盖所有这些用例很重要,原因如下:
|
|
||||||
|
|
||||||
1. 我们希望一切开箱即用看起来都很好。
|
|
||||||
2. 实际上只是第一个原因,这是插件的全部意义。
|
|
||||||
3. 这里有第三个假装的原因,尽管一个有三个项目的列表看起来比一个有两个项目的列表更真实。
|
|
||||||
|
|
||||||
现在我们将尝试另一种标题样式。
|
|
||||||
|
|
||||||
### 排版应该很简单
|
|
||||||
|
|
||||||
所以这是给你的一个标题——如果我们做得正确,那应该看起来相当合理。
|
|
||||||
|
|
||||||
一位智者曾经告诉我关于排版的一件事是:
|
|
||||||
|
|
||||||
> 如果你不希望你的东西看起来像垃圾,排版是非常重要的。做好它,那么它就不会糟糕。
|
|
||||||
|
|
||||||
默认情况下,图片在这里看起来也应该不错:
|
|
||||||
|
|
||||||
{/* <img
|
|
||||||
src="/images/blog/mksaas-og.png"
|
|
||||||
width="718"
|
|
||||||
height="404"
|
|
||||||
alt="图片"
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
与普遍的看法相反,Lorem Ipsum 并不是简单的随机文本。它起源于公元前 45 年的一段古典拉丁文学,使其有超过 2000 年的历史。
|
|
||||||
|
|
||||||
现在我将向您展示一个无序列表的例子,以确保它看起来也不错:
|
|
||||||
|
|
||||||
- 所以这是这个列表中的第一项。
|
|
||||||
- 在这个例子中,我们保持项目简短。
|
|
||||||
- 稍后,我们将使用更长、更复杂的列表项。
|
|
||||||
|
|
||||||
这就是本节的结尾。
|
|
||||||
|
|
||||||
## 如果我们堆叠标题怎么办?
|
|
||||||
|
|
||||||
### 我们也应该确保这看起来不错。
|
|
||||||
|
|
||||||
有时候你有直接堆叠在一起的标题。在这些情况下,你通常必须取消第二个标题上的顶部边距,因为标题彼此靠得更近通常看起来比段落后面跟着标题要好。
|
|
||||||
|
|
||||||
### 当标题在段落之后出现时……
|
|
||||||
|
|
||||||
当标题在段落之后出现时,我们需要更多的空间,就像我上面已经提到的那样。现在让我们看看一个更复杂的列表会是什么样子。
|
|
||||||
|
|
||||||
- **我经常做这种事,列表项有标题。**
|
|
||||||
|
|
||||||
由于某种原因,我认为这看起来很酷,这很不幸,因为要让样式正确是相当烦人的。
|
|
||||||
|
|
||||||
我在这些列表项中通常也有两到三个段落,所以困难的部分是让段落之间的间距、列表项标题和单独的列表项都有意义。老实说,这很困难,你可以提出一个强有力的论点,认为你根本不应该这样写。
|
|
||||||
|
|
||||||
- **由于这是一个列表,我至少需要两个项目。**
|
|
||||||
|
|
||||||
我已经在前面的列表项中解释了我在做什么,但是如果一个列表只有一个项目,那就不是一个列表,我们真的希望这看起来真实。这就是为什么我添加了这第二个列表项,所以我在写样式时实际上有东西可以看。
|
|
||||||
|
|
||||||
- **添加第三项也不是一个坏主意。**
|
|
||||||
|
|
||||||
我认为只使用两个项目可能已经足够了,但三个肯定不会更糟,而且由于我似乎在编造任意的东西时没有遇到麻烦,我不妨包括它。
|
|
||||||
|
|
||||||
在这种列表之后,我通常会有一个结束语或段落,因为直接跳到标题看起来有点奇怪。
|
|
||||||
|
|
||||||
## 代码默认应该看起来不错。
|
|
||||||
|
|
||||||
我认为大多数人如果想要设置他们的代码块的样式,会使用 [highlight.js](https://highlightjs.org/) 或 [Prism](https://prismjs.com/) 或其他东西,但是让它们开箱即用看起来_还不错_,即使没有语法高亮,也不会有害。
|
|
||||||
|
|
||||||
以下是撰写本文时默认的 `tailwind.config.js` 文件的样子:
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
purge: [],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
variants: {},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
希望这对你来说看起来足够好。
|
|
||||||
|
|
||||||
### 嵌套列表怎么办?
|
|
||||||
|
|
||||||
嵌套列表基本上总是看起来很糟糕,这就是为什么像 Medium 这样的编辑器甚至不让你这样做,但我猜既然你们中的一些傻瓜要这样做,我们至少要承担让它工作的负担。
|
|
||||||
|
|
||||||
1. **嵌套列表很少是一个好主意。**
|
|
||||||
- 你可能觉得你真的很"有组织"或者什么的,但你只是在屏幕上创建一个难以阅读的粗糙形状。
|
|
||||||
- UI 中的嵌套导航也是一个坏主意,尽可能保持扁平。
|
|
||||||
- 在源代码中嵌套大量文件夹也没有帮助。
|
|
||||||
2. **既然我们需要有更多的项目,这里有另一个。**
|
|
||||||
- 我不确定我们是否会费心设置超过两级深度的样式。
|
|
||||||
- 两级已经太多了,三级肯定是一个坏主意。
|
|
||||||
- 如果你嵌套四级深度,你应该进监狱。
|
|
||||||
3. **两个项目并不是真正的列表,三个项目就好了。**
|
|
||||||
- 再次请不要嵌套列表,如果你希望人们真正阅读你的内容。
|
|
||||||
- 没有人想看这个。
|
|
||||||
- 我很不高兴我们甚至必须费心设置这个样式。
|
|
||||||
|
|
||||||
Markdown 中列表最烦人的事情是,除非列表项中有多个段落,否则 `<li>` 元素不会被赋予子 `<p>` 标签。这意味着我也必须担心设置那种烦人情况的样式。
|
|
||||||
|
|
||||||
- **例如,这里是另一个嵌套列表。**
|
|
||||||
|
|
||||||
但这次有第二段。
|
|
||||||
|
|
||||||
- 这些列表项不会有 `<p>` 标签
|
|
||||||
- 因为它们每个只有一行
|
|
||||||
|
|
||||||
- **但在这第二个顶级列表项中,它们会有。**
|
|
||||||
|
|
||||||
这特别烦人,因为这段话的间距。
|
|
||||||
|
|
||||||
- 正如你在这里看到的,因为我添加了第二行,这个列表项现在有一个 `<p>` 标签。
|
|
||||||
|
|
||||||
顺便说一下,这是我说的第二行。
|
|
||||||
|
|
||||||
- 最后这里有另一个列表项,所以它更像一个列表。
|
|
||||||
|
|
||||||
- 一个结束列表项,但没有嵌套列表,为什么不呢?
|
|
||||||
|
|
||||||
最后一句话结束这一节。
|
|
||||||
|
|
||||||
## 还有其他我们需要设置样式的元素
|
|
||||||
|
|
||||||
我几乎忘了提到链接,比如[这个链接到 Tailwind CSS 网站](https://tailwindcss.com)。我们几乎把它们变成蓝色,但那是昨天的事了,所以我们选择了深灰色,感觉更前卫。
|
|
||||||
|
|
||||||
我们甚至包括了表格样式,看看:
|
|
||||||
|
|
||||||
| 摔跤手 | 出生地 | 终结技 |
|
|
||||||
| ----------------------- | ------------- | ------------------- |
|
|
||||||
| Bret "The Hitman" Hart | Calgary, AB | Sharpshooter |
|
|
||||||
| Stone Cold Steve Austin | Austin, TX | Stone Cold Stunner |
|
|
||||||
| Randy Savage | Sarasota, FL | Elbow Drop |
|
|
||||||
| Vader | Boulder, CO | Vader Bomb |
|
|
||||||
| Razor Ramon | Chuluota, FL | Razor's Edge |
|
|
||||||
|
|
||||||
我们还需要确保内联代码看起来不错,比如如果我想谈论 `<span>` 元素或者告诉你关于 `@tailwindcss/typography` 的好消息。
|
|
||||||
|
|
||||||
### 有时我甚至在标题中使用 `code`
|
|
||||||
|
|
||||||
尽管这可能是一个坏主意,而且历史上我一直很难让它看起来不错。不过这个_"将代码块包裹在反引号中"_的技巧效果相当不错。
|
|
||||||
|
|
||||||
我过去做过的另一件事是在链接中放置一个 `code` 标签,比如如果我想告诉你关于 [`tailwindcss/docs`](https://github.com/tailwindcss/docs) 仓库的事情。我不喜欢反引号下面有下划线,但为了避免它而导致的疯狂绝对不值得。
|
|
||||||
|
|
||||||
#### 我们还没有使用 `h4`
|
|
||||||
|
|
||||||
但现在我们有了。请不要在你的内容中使用 `h5` 或 `h6`,Medium 只支持两个标题级别是有原因的,你们这些动物。我老实说考虑过使用 `before` 伪元素,如果你使用 `h5` 或 `h6` 就对你大喊大叫。
|
|
||||||
|
|
||||||
我们根本不会为它们设置样式,因为 `h4` 元素已经很小,与正文大小相同。我们应该怎么处理 `h5`,让它比正文更_小_?不,谢谢。
|
|
||||||
|
|
||||||
### 不过我们仍然需要考虑堆叠的标题。
|
|
||||||
|
|
||||||
#### 让我们确保我们也不会用 `h4` 元素搞砸这个。
|
|
||||||
|
|
||||||
呼,运气好的话,我们已经设置了上面这段文字的标题样式,它们看起来相当不错。
|
|
||||||
|
|
||||||
让我们在这里添加一个结束段落,这样事情就会以一个相当大小的文本块结束。我无法解释为什么我希望事情以这种方式结束,但我必须假设这是因为我认为如果文档末尾太靠近标题,事情会看起来奇怪或不平衡。
|
|
||||||
|
|
||||||
我在这里写的可能已经足够长了,但添加这最后一句话不会有害。
|
|
||||||
|
|
||||||
## GitHub 风格的 Markdown
|
|
||||||
|
|
||||||
我还添加了对使用 `remark-gfm` 的 GitHub 风格 Markdown 的支持。
|
|
||||||
|
|
||||||
使用 `remark-gfm`,我们在 markdown 中获得了一些额外的功能。例如:自动链接文字。
|
|
||||||
|
|
||||||
像 www.example.com 或 https://example.com 这样的链接会自动转换为 `a` 标签。
|
|
||||||
|
|
||||||
这对电子邮件链接也有效:contact@example.com。
|
|
222
content/docs/internationalization.mdx
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
---
|
||||||
|
title: Internationalization
|
||||||
|
description: Support multiple languages in your documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
<Callout title='Before you get started'>
|
||||||
|
|
||||||
|
Fumadocs is not a full-powered i18n library, it manages only its own components and utilities.
|
||||||
|
|
||||||
|
You can use other libraries like [next-intl](https://github.com/amannn/next-intl) for the rest of your app.
|
||||||
|
Read the [Next.js Docs](https://nextjs.org/docs/app/building-your-application/routing/internationalization) to learn more about implementing I18n in Next.js.
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## Manual Setup
|
||||||
|
|
||||||
|
Define the i18n configurations in a file, we will import it with `@/ilb/i18n` in this guide.
|
||||||
|
|
||||||
|
<include cwd meta='title="lib/i18n.ts"'>
|
||||||
|
../../examples/i18n/lib/i18n.ts
|
||||||
|
</include>
|
||||||
|
|
||||||
|
Pass it to the source loader.
|
||||||
|
|
||||||
|
```ts title="lib/source.ts"
|
||||||
|
import { i18n } from '@/lib/i18n';
|
||||||
|
import { loader } from 'fumadocs-core/source';
|
||||||
|
|
||||||
|
export const source = loader({
|
||||||
|
i18n, // [!code highlight]
|
||||||
|
// other options
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
And update Fumadocs UI layout options.
|
||||||
|
|
||||||
|
```tsx title="app/layout.config.tsx"
|
||||||
|
import { i18n } from '@/lib/i18n';
|
||||||
|
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
||||||
|
|
||||||
|
export function baseOptions(locale: string): BaseLayoutProps {
|
||||||
|
return {
|
||||||
|
i18n,
|
||||||
|
// different props based on `locale`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Middleware
|
||||||
|
|
||||||
|
Create a middleware that redirects users to appropriate locale.
|
||||||
|
|
||||||
|
```json doc-gen:file
|
||||||
|
{
|
||||||
|
"file": "../../examples/i18n/middleware.ts",
|
||||||
|
"codeblock": {
|
||||||
|
"lang": "ts",
|
||||||
|
"meta": "title=\"middleware.ts\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Middleware](/docs/headless/internationalization#middleware) for customisable options.
|
||||||
|
|
||||||
|
> Note that this is optional, you can also use your own middleware or the one provided by i18n libraries.
|
||||||
|
|
||||||
|
### Routing
|
||||||
|
|
||||||
|
Create a `/app/[lang]` folder, and move all files (e.g. `page.tsx`, `layout.tsx`) from `/app` to the folder.
|
||||||
|
|
||||||
|
Wrap the root provider inside `I18nProvider`, and provide available languages & translations to it.
|
||||||
|
Note that only English translations are provided by default.
|
||||||
|
|
||||||
|
```tsx title="app/[lang]/layout.tsx"
|
||||||
|
import { RootProvider } from 'fumadocs-ui/provider';
|
||||||
|
import { I18nProvider, type Translations } from 'fumadocs-ui/i18n';
|
||||||
|
|
||||||
|
const cn: Partial<Translations> = {
|
||||||
|
search: 'Translated Content',
|
||||||
|
// other translations
|
||||||
|
};
|
||||||
|
|
||||||
|
// available languages that will be displayed on UI
|
||||||
|
// make sure `locale` is consistent with your i18n config
|
||||||
|
const locales = [
|
||||||
|
{
|
||||||
|
name: 'English',
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chinese',
|
||||||
|
locale: 'cn',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
params,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ lang: string }>;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const lang = (await params).lang;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={lang}>
|
||||||
|
<body>
|
||||||
|
<I18nProvider
|
||||||
|
locale={lang}
|
||||||
|
locales={locales}
|
||||||
|
translations={{ cn }[lang]}
|
||||||
|
>
|
||||||
|
<RootProvider>{children}</RootProvider>
|
||||||
|
</I18nProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pass Locale
|
||||||
|
|
||||||
|
Pass the locale to Fumadocs in your pages and layouts.
|
||||||
|
|
||||||
|
```tsx title="/app/[lang]/(home)/layout.tsx" tab="Home Layout"
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { HomeLayout } from 'fumadocs-ui/layouts/home';
|
||||||
|
import { baseOptions } from '@/app/layout.config';
|
||||||
|
|
||||||
|
export default async function Layout({
|
||||||
|
params,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ lang: string }>;
|
||||||
|
children: ReactNode;
|
||||||
|
}) {
|
||||||
|
const { lang } = await params;
|
||||||
|
|
||||||
|
return <HomeLayout {...baseOptions(lang)}>{children}</HomeLayout>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx title="/app/[lang]/docs/layout.tsx" tab="Docs Layout"
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { source } from '@/lib/source';
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||||
|
import { baseOptions } from '@/app/layout.config';
|
||||||
|
|
||||||
|
export default async function Layout({
|
||||||
|
params,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ lang: string }>;
|
||||||
|
children: ReactNode;
|
||||||
|
}) {
|
||||||
|
const { lang } = await params;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DocsLayout {...baseOptions(lang)} tree={source.pageTree[lang]}>
|
||||||
|
{children}
|
||||||
|
</DocsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts title="page.tsx" tab="Docs Page"
|
||||||
|
import { source } from '@/lib/source';
|
||||||
|
|
||||||
|
export default async function Page({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ lang: string; slug?: string[] }>;
|
||||||
|
}) {
|
||||||
|
const { slug, lang } = await params;
|
||||||
|
// get page
|
||||||
|
source.getPage(slug); // [!code --]
|
||||||
|
source.getPage(slug, lang); // [!code ++]
|
||||||
|
|
||||||
|
// get pages
|
||||||
|
source.getPages(); // [!code --]
|
||||||
|
source.getPages(lang); // [!code ++]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
Configure i18n on your search solution.
|
||||||
|
|
||||||
|
- **Built-in Search (Orama):**
|
||||||
|
For [Supported Languages](https://docs.orama.com/open-source/supported-languages#officially-supported-languages), no further changes are needed.
|
||||||
|
|
||||||
|
Otherwise, additional config is required (e.g. Chinese & Japanese). See [Special Languages](/docs/headless/search/orama#special-languages).
|
||||||
|
|
||||||
|
- **Cloud Solutions (e.g. Algolia):**
|
||||||
|
They usually have official support for multilingual.
|
||||||
|
|
||||||
|
## Writing Documents
|
||||||
|
|
||||||
|
<include>../../shared/page-conventions.i18n.mdx</include>
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
Fumadocs only handles navigation for its own layouts (e.g. sidebar).
|
||||||
|
For other places, you can use the `useParams` hook to get the locale from url, and attend it to `href`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
|
||||||
|
const { lang } = useParams();
|
||||||
|
|
||||||
|
return <Link href={`/${lang}/another-page`}>This is a link</Link>;
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition, the [`fumadocs-core/dynamic-link`](/docs/headless/components/link#dynamic-hrefs) component supports dynamic hrefs, you can use it to attend the locale prefix.
|
||||||
|
It is useful for Markdown/MDX content.
|
||||||
|
|
||||||
|
```mdx title="content.mdx"
|
||||||
|
import { DynamicLink } from 'fumadocs-core/dynamic-link';
|
||||||
|
|
||||||
|
<DynamicLink href="/[lang]/another-page">This is a link</DynamicLink>
|
||||||
|
```
|
@ -1,214 +0,0 @@
|
|||||||
---
|
|
||||||
title: Introduction
|
|
||||||
description: Introduction to MkSaaS
|
|
||||||
---
|
|
||||||
|
|
||||||
Until now, trying to style an article, document, or blog post with Tailwind has been a tedious task that required a keen eye for typography and a lot of complex custom CSS.
|
|
||||||
|
|
||||||
By default, Tailwind removes all of the default browser styling from paragraphs, headings, lists and more. This ends up being really useful for building application UIs because you spend less time undoing user-agent styles, but when you _really are_ just trying to style some content that came from a rich-text editor in a CMS or a markdown file, it can be surprising and unintuitive.
|
|
||||||
|
|
||||||
We get lots of complaints about it actually, with people regularly asking us things like:
|
|
||||||
|
|
||||||
> Why is Tailwind removing the default styles on my `h1` elements? How do I disable this? What do you mean I lose all the other base styles too?
|
|
||||||
> We hear you, but we're not convinced that simply disabling our base styles is what you really want. You don't want to have to remove annoying margins every time you use a `p` element in a piece of your dashboard UI. And I doubt you really want your blog posts to use the user-agent styles either — you want them to look _awesome_, not awful.
|
|
||||||
|
|
||||||
The `@tailwindcss/typography` plugin is our attempt to give you what you _actually_ want, without any of the downsides of doing something stupid like disabling our base styles.
|
|
||||||
|
|
||||||
It adds a new `prose` class that you can slap on any block of vanilla HTML content and turn it into a beautiful, well-formatted document:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<article class="prose">
|
|
||||||
<h1>Garlic bread with cheese: What the science tells us</h1>
|
|
||||||
<p>
|
|
||||||
For years parents have espoused the health benefits of eating garlic bread
|
|
||||||
with cheese to their children, with the food earning such an iconic status
|
|
||||||
in our culture that kids will often dress up as warm, cheesy loaf for
|
|
||||||
Halloween.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
But a recent study shows that the celebrated appetizer may be linked to a
|
|
||||||
series of rabies cases springing up around the country.
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information about how to use the plugin and the features it includes, [read the documentation](https://github.com/tailwindcss/typography/blob/master/README.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What to expect from here on out
|
|
||||||
|
|
||||||
What follows from here is just a bunch of absolute nonsense I've written to dogfood the plugin itself. It includes every sensible typographic element I could think of, like **bold text**, unordered lists, ordered lists, code blocks, block quotes, _and even italics_.
|
|
||||||
|
|
||||||
It's important to cover all of these use cases for a few reasons:
|
|
||||||
|
|
||||||
1. We want everything to look good out of the box.
|
|
||||||
2. Really just the first reason, that's the whole point of the plugin.
|
|
||||||
3. Here's a third pretend reason though a list with three items looks more realistic than a list with two items.
|
|
||||||
|
|
||||||
Now we're going to try out another header style.
|
|
||||||
|
|
||||||
### Typography should be easy
|
|
||||||
|
|
||||||
So that's a header for you — with any luck if we've done our job correctly that will look pretty reasonable.
|
|
||||||
|
|
||||||
Something a wise person once told me about typography is:
|
|
||||||
|
|
||||||
> Typography is pretty important if you don't want your stuff to look like trash. Make it good then it won't be bad.
|
|
||||||
|
|
||||||
It's probably important that images look okay here by default as well:
|
|
||||||
|
|
||||||
{/* <img
|
|
||||||
src="/images/blog/mksaas-og.png"
|
|
||||||
width="718"
|
|
||||||
height="404"
|
|
||||||
alt="Image"
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.
|
|
||||||
|
|
||||||
Now I'm going to show you an example of an unordered list to make sure that looks good, too:
|
|
||||||
|
|
||||||
- So here is the first item in this list.
|
|
||||||
- In this example we're keeping the items short.
|
|
||||||
- Later, we'll use longer, more complex list items.
|
|
||||||
|
|
||||||
And that's the end of this section.
|
|
||||||
|
|
||||||
## What if we stack headings?
|
|
||||||
|
|
||||||
### We should make sure that looks good, too.
|
|
||||||
|
|
||||||
Sometimes you have headings directly underneath each other. In those cases you often have to undo the top margin on the second heading because it usually looks better for the headings to be closer together than a paragraph followed by a heading should be.
|
|
||||||
|
|
||||||
### When a heading comes after a paragraph …
|
|
||||||
|
|
||||||
When a heading comes after a paragraph, we need a bit more space, like I already mentioned above. Now let's see what a more complex list would look like.
|
|
||||||
|
|
||||||
- **I often do this thing where list items have headings.**
|
|
||||||
|
|
||||||
For some reason I think this looks cool which is unfortunate because it's pretty annoying to get the styles right.
|
|
||||||
|
|
||||||
I often have two or three paragraphs in these list items, too, so the hard part is getting the spacing between the paragraphs, list item heading, and separate list items to all make sense. Pretty tough honestly, you could make a strong argument that you just shouldn't write this way.
|
|
||||||
|
|
||||||
- **Since this is a list, I need at least two items.**
|
|
||||||
|
|
||||||
I explained what I'm doing already in the previous list item, but a list wouldn't be a list if it only had one item, and we really want this to look realistic. That's why I've added this second list item so I actually have something to look at when writing the styles.
|
|
||||||
|
|
||||||
- **It's not a bad idea to add a third item either.**
|
|
||||||
|
|
||||||
I think it probably would've been fine to just use two items but three is definitely not worse, and since I seem to be having no trouble making up arbitrary things to type, I might as well include it.
|
|
||||||
|
|
||||||
After this sort of list I usually have a closing statement or paragraph, because it kinda looks weird jumping right to a heading.
|
|
||||||
|
|
||||||
## Code should look okay by default.
|
|
||||||
|
|
||||||
I think most people are going to use [highlight.js](https://highlightjs.org/) or [Prism](https://prismjs.com/) or something if they want to style their code blocks but it wouldn't hurt to make them look _okay_ out of the box, even with no syntax highlighting.
|
|
||||||
|
|
||||||
Here's what a default `tailwind.config.js` file looks like at the time of writing:
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
purge: [],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
variants: {},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Hopefully that looks good enough to you.
|
|
||||||
|
|
||||||
### What about nested lists?
|
|
||||||
|
|
||||||
Nested lists basically always look bad which is why editors like Medium don't even let you do it, but I guess since some of you goofballs are going to do it we have to carry the burden of at least making it work.
|
|
||||||
|
|
||||||
1. **Nested lists are rarely a good idea.**
|
|
||||||
- You might feel like you are being really "organized" or something but you are just creating a gross shape on the screen that is hard to read.
|
|
||||||
- Nested navigation in UIs is a bad idea too, keep things as flat as possible.
|
|
||||||
- Nesting tons of folders in your source code is also not helpful.
|
|
||||||
2. **Since we need to have more items, here's another one.**
|
|
||||||
- I'm not sure if we'll bother styling more than two levels deep.
|
|
||||||
- Two is already too much, three is guaranteed to be a bad idea.
|
|
||||||
- If you nest four levels deep you belong in prison.
|
|
||||||
3. **Two items isn't really a list, three is good though.**
|
|
||||||
- Again please don't nest lists if you want people to actually read your content.
|
|
||||||
- Nobody wants to look at this.
|
|
||||||
- I'm upset that we even have to bother styling this.
|
|
||||||
|
|
||||||
The most annoying thing about lists in Markdown is that `<li>` elements aren't given a child `<p>` tag unless there are multiple paragraphs in the list item. That means I have to worry about styling that annoying situation too.
|
|
||||||
|
|
||||||
- **For example, here's another nested list.**
|
|
||||||
|
|
||||||
But this time with a second paragraph.
|
|
||||||
|
|
||||||
- These list items won't have `<p>` tags
|
|
||||||
- Because they are only one line each
|
|
||||||
|
|
||||||
- **But in this second top-level list item, they will.**
|
|
||||||
|
|
||||||
This is especially annoying because of the spacing on this paragraph.
|
|
||||||
|
|
||||||
- As you can see here, because I've added a second line, this list item now has a `<p>` tag.
|
|
||||||
|
|
||||||
This is the second line I'm talking about by the way.
|
|
||||||
|
|
||||||
- Finally here's another list item so it's more like a list.
|
|
||||||
|
|
||||||
- A closing list item, but with no nested list, because why not?
|
|
||||||
|
|
||||||
And finally a sentence to close off this section.
|
|
||||||
|
|
||||||
## There are other elements we need to style
|
|
||||||
|
|
||||||
I almost forgot to mention links, like [this link to the Tailwind CSS website](https://tailwindcss.com). We almost made them blue but that's so yesterday, so we went with dark gray, feels edgier.
|
|
||||||
|
|
||||||
We even included table styles, check it out:
|
|
||||||
|
|
||||||
| Wrestler | Origin | Finisher |
|
|
||||||
| ----------------------- | ------------ | ------------------ |
|
|
||||||
| Bret "The Hitman" Hart | Calgary, AB | Sharpshooter |
|
|
||||||
| Stone Cold Steve Austin | Austin, TX | Stone Cold Stunner |
|
|
||||||
| Randy Savage | Sarasota, FL | Elbow Drop |
|
|
||||||
| Vader | Boulder, CO | Vader Bomb |
|
|
||||||
| Razor Ramon | Chuluota, FL | Razor's Edge |
|
|
||||||
|
|
||||||
We also need to make sure inline code looks good, like if I wanted to talk about `<span>` elements or tell you the good news about `@tailwindcss/typography`.
|
|
||||||
|
|
||||||
### Sometimes I even use `code` in headings
|
|
||||||
|
|
||||||
Even though it's probably a bad idea, and historically I've had a hard time making it look good. This _"wrap the code blocks in backticks"_ trick works pretty well though really.
|
|
||||||
|
|
||||||
Another thing I've done in the past is put a `code` tag inside of a link, like if I wanted to tell you about the [`tailwindcss/docs`](https://github.com/tailwindcss/docs) repository. I don't love that there is an underline below the backticks but it is absolutely not worth the madness it would require to avoid it.
|
|
||||||
|
|
||||||
#### We haven't used an `h4` yet
|
|
||||||
|
|
||||||
But now we have. Please don't use `h5` or `h6` in your content, Medium only supports two heading levels for a reason, you animals. I honestly considered using a `before` pseudo-element to scream at you if you use an `h5` or `h6`.
|
|
||||||
|
|
||||||
We don't style them at all out of the box because `h4` elements are already so small that they are the same size as the body copy. What are we supposed to do with an `h5`, make it _smaller_ than the body copy? No thanks.
|
|
||||||
|
|
||||||
### We still need to think about stacked headings though.
|
|
||||||
|
|
||||||
#### Let's make sure we don't screw that up with `h4` elements, either.
|
|
||||||
|
|
||||||
Phew, with any luck we have styled the headings above this text and they look pretty good.
|
|
||||||
|
|
||||||
Let's add a closing paragraph here so things end with a decently sized block of text. I can't explain why I want things to end that way but I have to assume it's because I think things will look weird or unbalanced if there is a heading too close to the end of the document.
|
|
||||||
|
|
||||||
What I've written here is probably long enough, but adding this final sentence can't hurt.
|
|
||||||
|
|
||||||
## GitHub Flavored Markdown
|
|
||||||
|
|
||||||
I've also added support for GitHub Flavored Mardown using `remark-gfm`.
|
|
||||||
|
|
||||||
With `remark-gfm`, we get a few extra features in our markdown. Example: autolink literals.
|
|
||||||
|
|
||||||
A link like www.example.com or https://example.com would automatically be converted into an `a` tag.
|
|
||||||
|
|
||||||
This works for email links too: contact@example.com.
|
|
||||||
|
|
||||||
{/* <Cards>
|
|
||||||
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
|
|
||||||
<Card title="Learn more about Fumadocs" href="https://fumadocs.vercel.app" />
|
|
||||||
</Cards> */}
|
|
@ -1,209 +0,0 @@
|
|||||||
---
|
|
||||||
title: 介绍
|
|
||||||
description: MkSaaS 是构建 AI SaaS 网站的最佳代码模板。
|
|
||||||
---
|
|
||||||
|
|
||||||
到目前为止,尝试使用 Tailwind 来设计文章、文档或博客文章的样式一直是一项繁琐的任务,需要对排版有敏锐的眼光,并且需要大量复杂的自定义 CSS。
|
|
||||||
|
|
||||||
默认情况下,Tailwind 会删除段落、标题、列表等所有默认的浏览器样式。这对于构建应用程序 UI 非常有用,因为您花更少的时间撤销用户代理样式,但是当您真的只是尝试设置来自 CMS 中富文本编辑器或 markdown 文件的内容的样式时,这可能会令人惊讶和不直观。
|
|
||||||
|
|
||||||
我们实际上收到了很多关于它的投诉,人们经常问我们这样的问题:
|
|
||||||
|
|
||||||
> 为什么 Tailwind 删除了我的 `h1` 元素上的默认样式?我如何禁用这个?你说我也会失去所有其他基本样式是什么意思?
|
|
||||||
> 我们听到了您的声音,但我们并不确信简单地禁用我们的基本样式就是您真正想要的。您不希望每次在仪表板 UI 的一部分中使用 `p` 元素时都必须删除烦人的边距。而且我怀疑您真的希望您的博客文章使用用户代理样式——您希望它们看起来很棒,而不是糟糕。
|
|
||||||
|
|
||||||
`@tailwindcss/typography` 插件是我们尝试给您真正想要的东西,而不会有做一些愚蠢的事情(比如禁用我们的基本样式)的任何缺点。
|
|
||||||
|
|
||||||
它添加了一个新的 `prose` 类,您可以将其应用于任何普通 HTML 内容块,并将其转变为一个美丽、格式良好的文档:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<article class="prose">
|
|
||||||
<h1>Garlic bread with cheese: What the science tells us</h1>
|
|
||||||
<p>
|
|
||||||
For years parents have espoused the health benefits of eating garlic bread
|
|
||||||
with cheese to their children, with the food earning such an iconic status
|
|
||||||
in our culture that kids will often dress up as warm, cheesy loaf for
|
|
||||||
Halloween.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
But a recent study shows that the celebrated appetizer may be linked to a
|
|
||||||
series of rabies cases springing up around the country.
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
```
|
|
||||||
|
|
||||||
有关如何使用该插件及其包含的功能的更多信息,[阅读文档](https://github.com/tailwindcss/typography/blob/master/README.md)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 从现在开始期待什么
|
|
||||||
|
|
||||||
从这里开始的是我写的一堆绝对无意义的内容,用来测试插件本身。它包括我能想到的每一个合理的排版元素,如**粗体文本**、无序列表、有序列表、代码块、块引用,_甚至斜体_。
|
|
||||||
|
|
||||||
涵盖所有这些用例很重要,原因如下:
|
|
||||||
|
|
||||||
1. 我们希望一切开箱即用看起来都很好。
|
|
||||||
2. 实际上只是第一个原因,这是插件的全部意义。
|
|
||||||
3. 这里有第三个假装的原因,尽管一个有三个项目的列表看起来比一个有两个项目的列表更真实。
|
|
||||||
|
|
||||||
现在我们将尝试另一种标题样式。
|
|
||||||
|
|
||||||
### 排版应该很简单
|
|
||||||
|
|
||||||
所以这是给你的一个标题——如果我们做得正确,那应该看起来相当合理。
|
|
||||||
|
|
||||||
一位智者曾经告诉我关于排版的一件事是:
|
|
||||||
|
|
||||||
> 如果你不希望你的东西看起来像垃圾,排版是非常重要的。做好它,那么它就不会糟糕。
|
|
||||||
|
|
||||||
默认情况下,图片在这里看起来也应该不错:
|
|
||||||
|
|
||||||
{/* <img
|
|
||||||
src="/images/blog/mksaas-og.png"
|
|
||||||
width="718"
|
|
||||||
height="404"
|
|
||||||
alt="图片"
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
与普遍的看法相反,Lorem Ipsum 并不是简单的随机文本。它起源于公元前 45 年的一段古典拉丁文学,使其有超过 2000 年的历史。
|
|
||||||
|
|
||||||
现在我将向您展示一个无序列表的例子,以确保它看起来也不错:
|
|
||||||
|
|
||||||
- 所以这是这个列表中的第一项。
|
|
||||||
- 在这个例子中,我们保持项目简短。
|
|
||||||
- 稍后,我们将使用更长、更复杂的列表项。
|
|
||||||
|
|
||||||
这就是本节的结尾。
|
|
||||||
|
|
||||||
## 如果我们堆叠标题怎么办?
|
|
||||||
|
|
||||||
### 我们也应该确保这看起来不错。
|
|
||||||
|
|
||||||
有时候你有直接堆叠在一起的标题。在这些情况下,你通常必须取消第二个标题上的顶部边距,因为标题彼此靠得更近通常看起来比段落后面跟着标题要好。
|
|
||||||
|
|
||||||
### 当标题在段落之后出现时……
|
|
||||||
|
|
||||||
当标题在段落之后出现时,我们需要更多的空间,就像我上面已经提到的那样。现在让我们看看一个更复杂的列表会是什么样子。
|
|
||||||
|
|
||||||
- **我经常做这种事,列表项有标题。**
|
|
||||||
|
|
||||||
由于某种原因,我认为这看起来很酷,这很不幸,因为要让样式正确是相当烦人的。
|
|
||||||
|
|
||||||
我在这些列表项中通常也有两到三个段落,所以困难的部分是让段落之间的间距、列表项标题和单独的列表项都有意义。老实说,这很困难,你可以提出一个强有力的论点,认为你根本不应该这样写。
|
|
||||||
|
|
||||||
- **由于这是一个列表,我至少需要两个项目。**
|
|
||||||
|
|
||||||
我已经在前面的列表项中解释了我在做什么,但是如果一个列表只有一个项目,那就不是一个列表,我们真的希望这看起来真实。这就是为什么我添加了这第二个列表项,所以我在写样式时实际上有东西可以看。
|
|
||||||
|
|
||||||
- **添加第三项也不是一个坏主意。**
|
|
||||||
|
|
||||||
我认为只使用两个项目可能已经足够了,但三个肯定不会更糟,而且由于我似乎在编造任意的东西时没有遇到麻烦,我不妨包括它。
|
|
||||||
|
|
||||||
在这种列表之后,我通常会有一个结束语或段落,因为直接跳到标题看起来有点奇怪。
|
|
||||||
|
|
||||||
## 代码默认应该看起来不错。
|
|
||||||
|
|
||||||
我认为大多数人如果想要设置他们的代码块的样式,会使用 [highlight.js](https://highlightjs.org/) 或 [Prism](https://prismjs.com/) 或其他东西,但是让它们开箱即用看起来_还不错_,即使没有语法高亮,也不会有害。
|
|
||||||
|
|
||||||
以下是撰写本文时默认的 `tailwind.config.js` 文件的样子:
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
purge: [],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
variants: {},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
希望这对你来说看起来足够好。
|
|
||||||
|
|
||||||
### 嵌套列表怎么办?
|
|
||||||
|
|
||||||
嵌套列表基本上总是看起来很糟糕,这就是为什么像 Medium 这样的编辑器甚至不让你这样做,但我猜既然你们中的一些傻瓜要这样做,我们至少要承担让它工作的负担。
|
|
||||||
|
|
||||||
1. **嵌套列表很少是一个好主意。**
|
|
||||||
- 你可能觉得你真的很"有组织"或者什么的,但你只是在屏幕上创建一个难以阅读的粗糙形状。
|
|
||||||
- UI 中的嵌套导航也是一个坏主意,尽可能保持扁平。
|
|
||||||
- 在源代码中嵌套大量文件夹也没有帮助。
|
|
||||||
2. **既然我们需要有更多的项目,这里有另一个。**
|
|
||||||
- 我不确定我们是否会费心设置超过两级深度的样式。
|
|
||||||
- 两级已经太多了,三级肯定是一个坏主意。
|
|
||||||
- 如果你嵌套四级深度,你应该进监狱。
|
|
||||||
3. **两个项目并不是真正的列表,三个项目就好了。**
|
|
||||||
- 再次请不要嵌套列表,如果你希望人们真正阅读你的内容。
|
|
||||||
- 没有人想看这个。
|
|
||||||
- 我很不高兴我们甚至必须费心设置这个样式。
|
|
||||||
|
|
||||||
Markdown 中列表最烦人的事情是,除非列表项中有多个段落,否则 `<li>` 元素不会被赋予子 `<p>` 标签。这意味着我也必须担心设置那种烦人情况的样式。
|
|
||||||
|
|
||||||
- **例如,这里是另一个嵌套列表。**
|
|
||||||
|
|
||||||
但这次有第二段。
|
|
||||||
|
|
||||||
- 这些列表项不会有 `<p>` 标签
|
|
||||||
- 因为它们每个只有一行
|
|
||||||
|
|
||||||
- **但在这第二个顶级列表项中,它们会有。**
|
|
||||||
|
|
||||||
这特别烦人,因为这段话的间距。
|
|
||||||
|
|
||||||
- 正如你在这里看到的,因为我添加了第二行,这个列表项现在有一个 `<p>` 标签。
|
|
||||||
|
|
||||||
顺便说一下,这是我说的第二行。
|
|
||||||
|
|
||||||
- 最后这里有另一个列表项,所以它更像一个列表。
|
|
||||||
|
|
||||||
- 一个结束列表项,但没有嵌套列表,为什么不呢?
|
|
||||||
|
|
||||||
最后一句话结束这一节。
|
|
||||||
|
|
||||||
## 还有其他我们需要设置样式的元素
|
|
||||||
|
|
||||||
我几乎忘了提到链接,比如[这个链接到 Tailwind CSS 网站](https://tailwindcss.com)。我们几乎把它们变成蓝色,但那是昨天的事了,所以我们选择了深灰色,感觉更前卫。
|
|
||||||
|
|
||||||
我们甚至包括了表格样式,看看:
|
|
||||||
|
|
||||||
| 摔跤手 | 出生地 | 终结技 |
|
|
||||||
| ----------------------- | ------------- | ------------------- |
|
|
||||||
| Bret "The Hitman" Hart | Calgary, AB | Sharpshooter |
|
|
||||||
| Stone Cold Steve Austin | Austin, TX | Stone Cold Stunner |
|
|
||||||
| Randy Savage | Sarasota, FL | Elbow Drop |
|
|
||||||
| Vader | Boulder, CO | Vader Bomb |
|
|
||||||
| Razor Ramon | Chuluota, FL | Razor's Edge |
|
|
||||||
|
|
||||||
我们还需要确保内联代码看起来不错,比如如果我想谈论 `<span>` 元素或者告诉你关于 `@tailwindcss/typography` 的好消息。
|
|
||||||
|
|
||||||
### 有时我甚至在标题中使用 `code`
|
|
||||||
|
|
||||||
尽管这可能是一个坏主意,而且历史上我一直很难让它看起来不错。不过这个_"将代码块包裹在反引号中"_的技巧效果相当不错。
|
|
||||||
|
|
||||||
我过去做过的另一件事是在链接中放置一个 `code` 标签,比如如果我想告诉你关于 [`tailwindcss/docs`](https://github.com/tailwindcss/docs) 仓库的事情。我不喜欢反引号下面有下划线,但为了避免它而导致的疯狂绝对不值得。
|
|
||||||
|
|
||||||
#### 我们还没有使用 `h4`
|
|
||||||
|
|
||||||
但现在我们有了。请不要在你的内容中使用 `h5` 或 `h6`,Medium 只支持两个标题级别是有原因的,你们这些动物。我老实说考虑过使用 `before` 伪元素,如果你使用 `h5` 或 `h6` 就对你大喊大叫。
|
|
||||||
|
|
||||||
我们根本不会为它们设置样式,因为 `h4` 元素已经很小,与正文大小相同。我们应该怎么处理 `h5`,让它比正文更_小_?不,谢谢。
|
|
||||||
|
|
||||||
### 不过我们仍然需要考虑堆叠的标题。
|
|
||||||
|
|
||||||
#### 让我们确保我们也不会用 `h4` 元素搞砸这个。
|
|
||||||
|
|
||||||
呼,运气好的话,我们已经设置了上面这段文字的标题样式,它们看起来相当不错。
|
|
||||||
|
|
||||||
让我们在这里添加一个结束段落,这样事情就会以一个相当大小的文本块结束。我无法解释为什么我希望事情以这种方式结束,但我必须假设这是因为我认为如果文档末尾太靠近标题,事情会看起来奇怪或不平衡。
|
|
||||||
|
|
||||||
我在这里写的可能已经足够长了,但添加这最后一句话不会有害。
|
|
||||||
|
|
||||||
## GitHub 风格的 Markdown
|
|
||||||
|
|
||||||
我还添加了对使用 `remark-gfm` 的 GitHub 风格 Markdown 的支持。
|
|
||||||
|
|
||||||
使用 `remark-gfm`,我们在 markdown 中获得了一些额外的功能。例如:自动链接文字。
|
|
||||||
|
|
||||||
像 www.example.com 或 https://example.com 这样的链接会自动转换为 `a` 标签。
|
|
||||||
|
|
||||||
这对电子邮件链接也有效:contact@example.com。
|
|
166
content/docs/layouts/docs.mdx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
---
|
||||||
|
title: Docs Layout
|
||||||
|
description: The layout of documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
The layout of documentation pages, it includes a sidebar and mobile-only navbar.
|
||||||
|
|
||||||
|
> It is a server component, you should not reference it in a client component.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Pass your page tree to the component.
|
||||||
|
|
||||||
|
```tsx title="layout.tsx"
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||||
|
import { baseOptions } from '@/app/layout.config';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<DocsLayout {...baseOptions} tree={tree}>
|
||||||
|
{children}
|
||||||
|
</DocsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<AutoTypeTable
|
||||||
|
path="./content/docs/props.ts"
|
||||||
|
type="Omit<DocsLayoutProps, 'children' | 'disableThemeSwitch'>"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Sidebar
|
||||||
|
|
||||||
|
```tsx title="layout.tsx"
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||||
|
|
||||||
|
<DocsLayout
|
||||||
|
sidebar={{
|
||||||
|
// sidebar options:
|
||||||
|
enabled: true,
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="SidebarProps" />
|
||||||
|
|
||||||
|
### Sidebar Tabs
|
||||||
|
|
||||||
|
See [Navigation Guide](/docs/navigation/sidebar#sidebar-tabs) for usages.
|
||||||
|
|
||||||
|
#### Decoration
|
||||||
|
|
||||||
|
Change the icon/styles of tabs.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||||
|
|
||||||
|
<DocsLayout
|
||||||
|
sidebar={{
|
||||||
|
tabs: {
|
||||||
|
transform: (option, node) => ({
|
||||||
|
...option,
|
||||||
|
icon: 'my icon',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nav
|
||||||
|
|
||||||
|
A mobile-only navbar, we recommend to customise it from `baseOptions`.
|
||||||
|
|
||||||
|
<div className='max-w-[460px] mx-auto'>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
||||||
|
|
||||||
|
export const baseOptions: BaseLayoutProps = {
|
||||||
|
githubUrl: 'https://github.com/fuma-nama/fumadocs',
|
||||||
|
nav: {
|
||||||
|
title: 'My App',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
<AutoTypeTable
|
||||||
|
path="./content/docs/props.ts"
|
||||||
|
type="Omit<NavbarProps, 'children'>"
|
||||||
|
/>
|
||||||
|
|
||||||
|
### Transparent Mode
|
||||||
|
|
||||||
|
To make the navbar background transparent, you can configure transparent mode.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
||||||
|
|
||||||
|
export const baseOptions: BaseLayoutProps = {
|
||||||
|
nav: {
|
||||||
|
transparentMode: 'top',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
| Mode | Description |
|
||||||
|
| -------- | ---------------------------------------- |
|
||||||
|
| `always` | Always use a transparent background |
|
||||||
|
| `top` | When at the top of page |
|
||||||
|
| `none` | Disable transparent background (default) |
|
||||||
|
|
||||||
|
### Replace Navbar
|
||||||
|
|
||||||
|
To replace the navbar in Docs Layout, set `nav.component` to your own component.
|
||||||
|
|
||||||
|
```tsx title="layout.tsx"
|
||||||
|
import { baseOptions } from '@/app/layout.config';
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/notebook';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<DocsLayout
|
||||||
|
{...baseOptions}
|
||||||
|
nav={{
|
||||||
|
component: <CustomNavbar />,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DocsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Fumadocs uses **CSS Variables** to share the size of layout components, and fit each layout component into appropriate position.
|
||||||
|
|
||||||
|
You need to override `--fd-nav-height` to the exact height of your custom navbar, this can be done with a CSS stylesheet (e.g. in `global.css`):
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--fd-nav-height: 80px !important;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
### Disable Prefetching
|
||||||
|
|
||||||
|
By default, it uses the Next.js Link component with prefetch enabled.
|
||||||
|
When the link component appears into the browser viewport, the content (RSC payload) will be prefetched.
|
||||||
|
|
||||||
|
On Vercel, this may cause a high usage of serverless functions and Data Cache.
|
||||||
|
It can also hit the limits of some other hosting platforms.
|
||||||
|
|
||||||
|
You can disable prefetching to reduce the amount of RSC requests.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||||
|
|
||||||
|
<DocsLayout sidebar={{ prefetch: false }} />;
|
||||||
|
```
|
33
content/docs/layouts/home-layout.mdx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: Home Layout
|
||||||
|
description: Shared layout for other pages
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add a navbar and search dialog across other pages.
|
||||||
|
|
||||||
|
```tsx title="/app/(home)/layout.tsx"
|
||||||
|
import { HomeLayout } from 'fumadocs-ui/layouts/home';
|
||||||
|
import { baseOptions } from '@/app/layout.config';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return <HomeLayout {...baseOptions}>{children}</HomeLayout>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a [Route Group](https://nextjs.org/docs/app/building-your-application/routing/route-groups) to share the same layout across multiple pages.
|
||||||
|
|
||||||
|
<Files>
|
||||||
|
<Folder name="(home)" defaultOpen>
|
||||||
|
<File name="page.tsx" />
|
||||||
|
<File name="layout.tsx" />
|
||||||
|
</Folder>
|
||||||
|
<Folder name="/docs">
|
||||||
|
<Folder name={'[[..slugs]]'}>
|
||||||
|
<File name="page.tsx" />
|
||||||
|
</Folder>
|
||||||
|
<File name="layout.tsx" />
|
||||||
|
</Folder>
|
||||||
|
</Files>
|
32
content/docs/layouts/notebook.mdx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: Notebook
|
||||||
|
description: A more compact version of Docs Layout
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Enable the notebook layout with `fumadocs-ui/layouts/notebook`, it's a more compact layout than the default one.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```tsx title="layout.tsx"
|
||||||
|
import { DocsLayout } from 'fumadocs-ui/layouts/notebook';
|
||||||
|
import { baseOptions } from '@/app/layout.config';
|
||||||
|
import { source } from '@/lib/source';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<DocsLayout
|
||||||
|
{...baseOptions}
|
||||||
|
// the position of navbar
|
||||||
|
nav={{ ...baseOptions.nav, mode: 'top' }}
|
||||||
|
// the position of Sidebar Tabs
|
||||||
|
tabMode="navbar"
|
||||||
|
tree={source.pageTree}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DocsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
217
content/docs/layouts/page.mdx
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
---
|
||||||
|
title: Docs Page
|
||||||
|
description: A page in your documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
Page is the base element of a documentation, it includes Table of contents,
|
||||||
|
Footer, and Breadcrumb.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```tsx title="page.tsx"
|
||||||
|
import {
|
||||||
|
DocsPage,
|
||||||
|
DocsDescription,
|
||||||
|
DocsTitle,
|
||||||
|
DocsBody,
|
||||||
|
} from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage>
|
||||||
|
<DocsTitle>title</DocsTitle>
|
||||||
|
<DocsDescription>description</DocsDescription>
|
||||||
|
<DocsBody>...</DocsBody>
|
||||||
|
</DocsPage>;
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type='info' title='Good to know'>
|
||||||
|
|
||||||
|
Instead of rendering the title with `DocsTitle` in `page.tsx`, you can put the title into MDX file.
|
||||||
|
This will render the title in the MDX body.
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
It applies the [Typography](/docs/theme#typography) styles, wrap your content inside.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsBody } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsBody>
|
||||||
|
<h1>This heading looks good!</h1>
|
||||||
|
</DocsBody>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Category
|
||||||
|
|
||||||
|
Optional, link the other pages in its (page tree) folder with cards.
|
||||||
|
|
||||||
|
> You can use this component without `<DocsPage />`.
|
||||||
|
|
||||||
|
```tsx title="page.tsx"
|
||||||
|
import { source } from '@/lib/source';
|
||||||
|
import { DocsCategory } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
const page = source.getPage(['...']);
|
||||||
|
|
||||||
|
<DocsCategory page={page} from={source} />;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Demo:**
|
||||||
|
|
||||||
|
{/* DocsCategory is not supported */}
|
||||||
|
{/* <DocsCategory /> */}
|
||||||
|
|
||||||
|
## Configurations
|
||||||
|
|
||||||
|
### Full Mode
|
||||||
|
|
||||||
|
To extend the page to fill up all available space, pass `full` to the page component.
|
||||||
|
This will force TOC to be shown as a popover.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage full>...</DocsPage>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Table of Contents
|
||||||
|
|
||||||
|
An overview of all the headings in your article, it requires an array of headings.
|
||||||
|
|
||||||
|
For Markdown and MDX documents, You can obtain it using the
|
||||||
|
[TOC Utility](/docs/headless/utils/get-toc). Content sources like Fumadocs MDX offer this out-of-the-box.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage toc={headings}>...</DocsPage>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Customise or disable TOC from your documentation with the `tableOfContent` option.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage tableOfContent={options}>...</DocsPage>;
|
||||||
|
```
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="TOCProps" />
|
||||||
|
|
||||||
|
#### Style
|
||||||
|
|
||||||
|
You can choose another style for TOC, like `clerk` inspired by https://clerk.com:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage
|
||||||
|
tableOfContent={{
|
||||||
|
style: 'clerk',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</DocsPage>;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Popover Mode
|
||||||
|
|
||||||
|
On smaller devices, it is shown on a popover instead.
|
||||||
|
Customise it with the `tableOfContentPopover` option.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage tableOfContentPopover={options}>...</DocsPage>;
|
||||||
|
```
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="TOCPopoverProps" />
|
||||||
|
|
||||||
|
### Last Updated Time
|
||||||
|
|
||||||
|
Display last updated time of the page.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage lastUpdate={new Date(lastModifiedTime)} />;
|
||||||
|
```
|
||||||
|
|
||||||
|
Since you might have different version controls (e.g. Github) or it's from
|
||||||
|
remote sources like Sanity, Fumadocs UI doesn't display the last updated time by
|
||||||
|
default.
|
||||||
|
|
||||||
|
For Github hosted documents, you can use
|
||||||
|
the [`getGithubLastEdit`](/docs/headless/utils/git-last-edit) utility.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage } from 'fumadocs-ui/page';
|
||||||
|
import { getGithubLastEdit } from 'fumadocs-core/server';
|
||||||
|
|
||||||
|
const time = await getGithubLastEdit({
|
||||||
|
owner: 'fuma-nama',
|
||||||
|
repo: 'fumadocs',
|
||||||
|
path: `content/docs/${page.file.path}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
<DocsPage lastUpdate={new Date(time)} />;
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type='info' title='Note'>
|
||||||
|
|
||||||
|
You can also specify the last updated time of documents (e.g. using frontmatter).
|
||||||
|
Don't forget to [update the schema type](/docs/mdx/collections#schema) on Fumadocs MDX first.
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Edit on GitHub
|
||||||
|
|
||||||
|
Add "Edit on GitHub" button to the page.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage
|
||||||
|
editOnGithub={{
|
||||||
|
owner: 'fuma-nama',
|
||||||
|
repo: 'fumadocs',
|
||||||
|
sha: 'main',
|
||||||
|
// file path, make sure it's valid
|
||||||
|
path: `content/docs/${page.file.path}`,
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Footer
|
||||||
|
|
||||||
|
Footer is a navigation element that has two buttons to jump to the next and previous pages. When not specified, it shows the neighbour pages found from page tree.
|
||||||
|
|
||||||
|
Customise the footer with the `footer` option.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DocsPage, DocsBody } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
<DocsPage footer={options}>
|
||||||
|
<DocsBody>...</DocsBody>
|
||||||
|
</DocsPage>;
|
||||||
|
```
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="FooterProps" />
|
||||||
|
|
||||||
|
### Breadcrumb
|
||||||
|
|
||||||
|
A navigation element, shown only when user is navigating in folders.
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="BreadcrumbProps" />
|
||||||
|
|
||||||
|
### MDX Page
|
||||||
|
|
||||||
|
In conjunction of Fumadocs MDX, you may create a `page.mdx` file and add the following.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
export { withArticle as default } from 'fumadocs-ui/page';
|
||||||
|
|
||||||
|
## Hello World
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a page with MDX, with proper typography styles applied.
|
54
content/docs/layouts/root-provider.mdx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: Root Provider
|
||||||
|
description: The context provider of Fumadocs UI.
|
||||||
|
---
|
||||||
|
|
||||||
|
The context provider of all the components, including `next-themes` and context
|
||||||
|
for search dialog. It should be located at the root layout.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { RootProvider } from 'fumadocs-ui/provider';
|
||||||
|
|
||||||
|
export default function Layout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<RootProvider>{children}</RootProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search Dialog
|
||||||
|
|
||||||
|
Customize or disable the search dialog with `search` option.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<RootProvider
|
||||||
|
search={{
|
||||||
|
enabled: false,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn more from [Search](/docs/search).
|
||||||
|
|
||||||
|
### Theme Provider
|
||||||
|
|
||||||
|
Fumadocs supports light/dark modes with [`next-themes`](https://github.com/pacocoursey/next-themes).
|
||||||
|
Customise or disable it with `theme` option.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<RootProvider
|
||||||
|
theme={{
|
||||||
|
enabled: false,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>
|
||||||
|
```
|
185
content/docs/manual-installation.mdx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
---
|
||||||
|
title: Manual Installation
|
||||||
|
description: Create a new fumadocs project from scratch.
|
||||||
|
---
|
||||||
|
|
||||||
|
> Read the [Quick Start](/docs) guide first for basic concept.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Create a new Next.js application with `create-next-app`, and install required packages.
|
||||||
|
|
||||||
|
```package-install
|
||||||
|
fumadocs-ui fumadocs-core
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Source
|
||||||
|
|
||||||
|
Fumadocs supports different content sources, you can choose one you prefer.
|
||||||
|
|
||||||
|
There is a list of officially supported sources:
|
||||||
|
|
||||||
|
- [Setup Fumadocs MDX](/docs/mdx)
|
||||||
|
- [Setup Content Collections](/docs/headless/content-collections)
|
||||||
|
|
||||||
|
Make sure to configure the library correctly following their setup guide before continuing, we will import the source adapter using `@/lib/source.ts` in this guide.
|
||||||
|
|
||||||
|
### Root Layout
|
||||||
|
|
||||||
|
Wrap the entire application inside [Root Provider](/docs/layouts/root-provider), and add required styles to `body`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { RootProvider } from 'fumadocs-ui/provider';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<body
|
||||||
|
// you can use Tailwind CSS too
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
minHeight: '100vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RootProvider>{children}</RootProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Styles
|
||||||
|
|
||||||
|
Setup Tailwind CSS v4 on your Next.js app, add the following to `global.css`.
|
||||||
|
|
||||||
|
```css title="Tailwind CSS"
|
||||||
|
@import 'tailwindcss';
|
||||||
|
@import 'fumadocs-ui/css/neutral.css';
|
||||||
|
@import 'fumadocs-ui/css/preset.css';
|
||||||
|
|
||||||
|
/* path of `fumadocs-ui` relative to the CSS file */
|
||||||
|
@source '../node_modules/fumadocs-ui/dist/**/*.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
> It doesn't come with a default font, you may choose one from `next/font`.
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
|
||||||
|
Create a `app/layout.config.tsx` file to put the shared options for our layouts.
|
||||||
|
|
||||||
|
```json doc-gen:file
|
||||||
|
{
|
||||||
|
"file": "../../examples/next-mdx/app/layout.config.tsx",
|
||||||
|
"codeblock": {
|
||||||
|
"meta": "title=\"app/layout.config.tsx\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a folder `/app/docs` for our docs, and give it a proper layout.
|
||||||
|
|
||||||
|
```json doc-gen:file
|
||||||
|
{
|
||||||
|
"file": "../../examples/next-mdx/app/docs/layout.tsx",
|
||||||
|
"codeblock": {
|
||||||
|
"meta": "title=\"app/docs/layout.tsx\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> `pageTree` refers to Page Tree, it should be provided by your content source.
|
||||||
|
|
||||||
|
### Page
|
||||||
|
|
||||||
|
Create a catch-all route `/app/docs/[[...slug]]` for docs pages.
|
||||||
|
|
||||||
|
In the page, wrap your content in the [Page](/docs/layouts/page) component.
|
||||||
|
It may vary depending on your content source. You should configure static rendering with `generateStaticParams` and metadata with `generateMetadata`.
|
||||||
|
|
||||||
|
<Tabs groupId='content-source' items={['Fumadocs MDX', 'Content Collections']}>
|
||||||
|
|
||||||
|
```json doc-gen:file
|
||||||
|
{
|
||||||
|
"file": "../../examples/next-mdx/app/docs/[[...slug]]/page.tsx",
|
||||||
|
"codeblock": {
|
||||||
|
"meta": "title=\"app/docs/[[...slug]]/page.tsx\" tab=\"Fumadocs MDX\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json doc-gen:file
|
||||||
|
{
|
||||||
|
"file": "../../examples/content-collections/app/docs/[[...slug]]/page.tsx",
|
||||||
|
"codeblock": {
|
||||||
|
"meta": "title=\"app/docs/[[...slug]]/page.tsx\" tab=\"Content Collections\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
Use the default document search based on Orama.
|
||||||
|
|
||||||
|
<Tabs groupId='content-source' items={['Fumadocs MDX', 'Content Collections']}>
|
||||||
|
|
||||||
|
```json doc-gen:file
|
||||||
|
{
|
||||||
|
"file": "../../examples/next-mdx/app/api/search/route.ts",
|
||||||
|
"codeblock": {
|
||||||
|
"meta": "title=\"app/api/search/route.ts\" tab=\"Fumadocs MDX\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json doc-gen:file
|
||||||
|
{
|
||||||
|
"file": "../../examples/content-collections/app/api/search/route.ts",
|
||||||
|
"codeblock": {
|
||||||
|
"meta": "title=\"app/api/search/route.ts\" tab=\"Content Collections\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Learn more about [Document Search](/docs/headless/search).
|
||||||
|
|
||||||
|
### Done
|
||||||
|
|
||||||
|
You can start the dev server and create MDX files.
|
||||||
|
|
||||||
|
```mdx title="content/docs/index.mdx"
|
||||||
|
---
|
||||||
|
title: Hello World
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
I love Anime.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customise
|
||||||
|
|
||||||
|
You can use [Home Layout](/docs/layouts/home-layout) for other pages of the site, it includes a navbar with theme toggle.
|
||||||
|
|
||||||
|
## Deploying
|
||||||
|
|
||||||
|
It should work out-of-the-box with Vercel & Netlify.
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
|
||||||
|
If you want to deploy your Fumadocs app using Docker with **Fumadocs MDX configured**, make sure to add the `source.config.ts` file to the `WORKDIR` in the Dockerfile.
|
||||||
|
The following snippet is taken from the official [Next.js Dockerfile Example](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile):
|
||||||
|
|
||||||
|
```zsh title="Dockerfile"
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies based on the preferred package manager
|
||||||
|
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* source.config.ts ./
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures Fumadocs MDX can access your configuration file during builds.
|
368
content/docs/markdown.mdx
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
---
|
||||||
|
title: Markdown
|
||||||
|
description: How to write documents
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Fumadocs provides many useful extensions to MDX, a markup language. Here is a brief introduction to the default MDX syntax of Fumadocs UI.
|
||||||
|
|
||||||
|
> MDX is not the only supported format of Fumadocs. In fact, you can use any renderers such as `next-mdx-remote` or CMS.
|
||||||
|
|
||||||
|
## Markdown
|
||||||
|
|
||||||
|
We use GFM (GitHub Flavored Markdown), a superset of Markdown (CommonMark).
|
||||||
|
See [GFM Specification](https://github.github.com/gfm).
|
||||||
|
|
||||||
|
````md
|
||||||
|
# Heading
|
||||||
|
|
||||||
|
## Heading
|
||||||
|
|
||||||
|
### Heading
|
||||||
|
|
||||||
|
#### Heading
|
||||||
|
|
||||||
|
Hello World, **Bold**, _Italic_, ~~Hidden~~
|
||||||
|
|
||||||
|
```js
|
||||||
|
console.log('Hello World');
|
||||||
|
```
|
||||||
|
|
||||||
|
1. First
|
||||||
|
2. Second
|
||||||
|
3. Third
|
||||||
|
|
||||||
|
- Item 1
|
||||||
|
- Item 2
|
||||||
|
|
||||||
|
> Quote here
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
| Table | Description |
|
||||||
|
| ----- | ----------- |
|
||||||
|
| Hello | World |
|
||||||
|
````
|
||||||
|
|
||||||
|
### Auto Links
|
||||||
|
|
||||||
|
Internal links use the `next/link` component to allow prefetching and avoid hard-reload.
|
||||||
|
|
||||||
|
External links will get the default `rel="noreferrer noopener" target="_blank"` attributes for security.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
[My Link](https://github.github.com/gfm)
|
||||||
|
|
||||||
|
This also works: https://github.github.com/gfm.
|
||||||
|
```
|
||||||
|
|
||||||
|
## MDX
|
||||||
|
|
||||||
|
MDX is a superset of Markdown, with support of JSX syntax.
|
||||||
|
It allows you to import components, and use them right in the document, or even export values.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { Component } from './component';
|
||||||
|
|
||||||
|
<Component name="Hello" />
|
||||||
|
```
|
||||||
|
|
||||||
|
see [MDX Syntax](https://mdxjs.com/docs/what-is-mdx/#mdx-syntax) to learn more.
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
|
||||||
|
Useful for adding links, it is included by default.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Cards>
|
||||||
|
<Card
|
||||||
|
href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating"
|
||||||
|
title="Fetching, Caching, and Revalidating"
|
||||||
|
>
|
||||||
|
Learn more about caching in Next.js
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Cards>
|
||||||
|
<Card
|
||||||
|
href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating"
|
||||||
|
title="Fetching, Caching, and Revalidating"
|
||||||
|
>
|
||||||
|
Learn more about caching in Next.js
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
|
||||||
|
#### Icon
|
||||||
|
|
||||||
|
You can specify an icon to cards.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { HomeIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
<Cards>
|
||||||
|
<Card icon={<HomeIcon />} href="/" title="Home">
|
||||||
|
Go back to home
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Cards>
|
||||||
|
<Card icon={<HomeIcon />} href="/" title="Go back to home">
|
||||||
|
The home page of Fumadocs.
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
|
||||||
|
#### Without href
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Cards>
|
||||||
|
<Card title="Fetching, Caching, and Revalidating">
|
||||||
|
Learn more about `fetch` in Next.js.
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Cards>
|
||||||
|
<Card title="Fetching, Caching, and Revalidating">
|
||||||
|
Learn more about `fetch` in Next.js.
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
|
||||||
|
### Callouts
|
||||||
|
|
||||||
|
Useful for adding tips/warnings, it is included by default.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Callout>Hello World</Callout>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout>Hello World</Callout>
|
||||||
|
|
||||||
|
#### Title
|
||||||
|
|
||||||
|
Specify a callout title.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Callout title="Title">Hello World</Callout>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout title="Title">Hello World</Callout>
|
||||||
|
|
||||||
|
#### Types
|
||||||
|
|
||||||
|
You can specify the type of callout.
|
||||||
|
|
||||||
|
- `info` (default)
|
||||||
|
- `warn`
|
||||||
|
- `error`
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Callout title="Title" type="error">
|
||||||
|
Hello World
|
||||||
|
</Callout>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout title="Title" type="error">
|
||||||
|
Hello World
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Customise Components
|
||||||
|
|
||||||
|
See [all MDX components and available options](/docs/mdx).
|
||||||
|
|
||||||
|
## Headings
|
||||||
|
|
||||||
|
An anchor is automatically applied to each heading, it sanitizes invalid characters like spaces. (e.g. `Hello World` to `hello-world`)
|
||||||
|
|
||||||
|
```md
|
||||||
|
# Hello `World`
|
||||||
|
```
|
||||||
|
|
||||||
|
### TOC Settings
|
||||||
|
|
||||||
|
The table of contents (TOC) will be generated based on headings, you can also customise the effects of headings:
|
||||||
|
|
||||||
|
```md
|
||||||
|
# Heading [!toc]
|
||||||
|
|
||||||
|
This heading will be hidden from TOC.
|
||||||
|
|
||||||
|
# Another Heading [toc]
|
||||||
|
|
||||||
|
This heading will **only** be visible in TOC, you can use it to add additional TOC items.
|
||||||
|
Like headings rendered in a React component:
|
||||||
|
|
||||||
|
<MyComp />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Anchor
|
||||||
|
|
||||||
|
You can add `[#slug]` to customise heading anchors.
|
||||||
|
|
||||||
|
```md
|
||||||
|
# heading [#my-heading-id]
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also chain it with TOC settings like:
|
||||||
|
|
||||||
|
```md
|
||||||
|
# heading [toc] [#my-heading-id]
|
||||||
|
```
|
||||||
|
|
||||||
|
To link people to a specific heading, add the heading id to hash fragment: `/page#my-heading-id`.
|
||||||
|
|
||||||
|
## Frontmatter
|
||||||
|
|
||||||
|
We support YAML frontmatter. It is a way to specify common information of the document (e.g. title).
|
||||||
|
Place it at the top of document.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
---
|
||||||
|
title: Hello World
|
||||||
|
---
|
||||||
|
|
||||||
|
## Title
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Page Conventions](/docs/page-conventions#frontmatter) for a list of properties available for frontmatter.
|
||||||
|
|
||||||
|
## Codeblock
|
||||||
|
|
||||||
|
Syntax Highlighting is supported by default using [Rehype Code](/docs/headless/mdx/rehype-code).
|
||||||
|
|
||||||
|
````mdx
|
||||||
|
```js
|
||||||
|
console.log('Hello World');
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
You can add a title to the codeblock.
|
||||||
|
|
||||||
|
````mdx
|
||||||
|
```js title="My Title"
|
||||||
|
console.log('Hello World');
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
### Highlight Lines
|
||||||
|
|
||||||
|
You can highlight specific lines by adding `[!code highlight]`.
|
||||||
|
|
||||||
|
````md
|
||||||
|
```tsx
|
||||||
|
<div>Hello World</div> // [\!code highlight]
|
||||||
|
<div>Hello World</div>
|
||||||
|
<div>Goodbye</div>
|
||||||
|
<div>Hello World</div>
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
### Highlight Words
|
||||||
|
|
||||||
|
You can highlight a specific word by adding `[!code word:<match>]`.
|
||||||
|
|
||||||
|
````md
|
||||||
|
```js
|
||||||
|
// [\!code word:config]
|
||||||
|
const config = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
### Diffs
|
||||||
|
|
||||||
|
````mdx
|
||||||
|
```ts
|
||||||
|
console.log('hewwo'); // [\!code --]
|
||||||
|
console.log('hello'); // [\!code ++]
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```ts
|
||||||
|
console.log('hewwo'); // [!code --]
|
||||||
|
console.log('hello'); // [!code ++]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tab Groups
|
||||||
|
|
||||||
|
You can use code blocks with the `<Tab />` component.
|
||||||
|
|
||||||
|
````mdx
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||||
|
|
||||||
|
```ts tab="Tab 1"
|
||||||
|
console.log('A');
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts tab="Tab 2"
|
||||||
|
console.log('B');
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
> Note that you can add MDX components instead of importing them in MDX files.
|
||||||
|
|
||||||
|
```ts tab="Tab 1"
|
||||||
|
console.log('A');
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts tab="Tab 2"
|
||||||
|
console.log('B');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Typescript Twoslash
|
||||||
|
|
||||||
|
Write Typescript codeblocks with hover type information and detected types errors.
|
||||||
|
|
||||||
|
Not enabled by default. See [Twoslash](/docs/twoslash).
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
All built-in content sources handle images properly.
|
||||||
|
Images are automatically optimized for `next/image`.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
Some optional plugins you can enable.
|
||||||
|
|
||||||
|
### Math Equations
|
||||||
|
|
||||||
|
Write math equations with TeX.
|
||||||
|
|
||||||
|
````md
|
||||||
|
```math
|
||||||
|
f(x) = x * e^{2 pi i \xi x}
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```math
|
||||||
|
f(x) = x * e^{2 pi i \xi x}
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable, see [Math Integration](/docs/math).
|
||||||
|
|
||||||
|
### Package Install
|
||||||
|
|
||||||
|
Generate code blocks for installing packages via package managers (JS/Node.js).
|
||||||
|
|
||||||
|
````md
|
||||||
|
```package-install
|
||||||
|
npm i next -D
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```package-install
|
||||||
|
npm i next -D
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable, see [Remark Install](/docs/headless/mdx/install).
|
||||||
|
|
||||||
|
### More
|
||||||
|
|
||||||
|
You can see [a list of plugins](/docs/headless/mdx) supported by Fumadocs.
|
25
content/docs/mdx/callout.mdx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: Callout
|
||||||
|
description: Add callout to your docs
|
||||||
|
preview: callout
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add it to your MDX components.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Callout } from 'fumadocs-ui/components/callout';
|
||||||
|
|
||||||
|
<MDX
|
||||||
|
components={{
|
||||||
|
Callout,
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Markdown](/docs/markdown#callouts) for usages.
|
||||||
|
|
||||||
|
### Reference
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="CalloutProps" />
|
56
content/docs/mdx/card.mdx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: Card
|
||||||
|
description: Use the Card component in your MDX documentation
|
||||||
|
preview: card
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add it to your MDX components.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Card, Cards } from 'fumadocs-ui/components/card';
|
||||||
|
|
||||||
|
<MDX
|
||||||
|
components={{
|
||||||
|
Card,
|
||||||
|
Cards,
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Markdown](/docs/markdown#cards) for usages.
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
|
||||||
|
The container of cards.
|
||||||
|
|
||||||
|
### Card
|
||||||
|
|
||||||
|
Based on Next.js `<Link />`.
|
||||||
|
|
||||||
|
<AutoTypeTable path="./content/docs/props.ts" name="CardProps" />
|
||||||
|
|
||||||
|
<Callout title="Tree Shaking on icons" type="warn">
|
||||||
|
|
||||||
|
If you're not using Fumadocs MDX for rendering MDX (e.g. using Contentlayer), ensure that
|
||||||
|
tree shaking is working properly.
|
||||||
|
|
||||||
|
Most of the icon libraries support importing icons individually.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import HomeIcon from 'lucide-react/dist/esm/icons/home';
|
||||||
|
```
|
||||||
|
|
||||||
|
As a workaround, you can pass icons to MDX Components too. (this uses Next.js bundler instead of content source)
|
||||||
|
|
||||||
|
```tsx title="page.tsx"
|
||||||
|
import { HomeIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
...defaultComponents,
|
||||||
|
HomeIcon,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
</Callout>
|
79
content/docs/mdx/codeblock.mdx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
title: Code Block
|
||||||
|
description: Adding code blocks to your docs
|
||||||
|
---
|
||||||
|
|
||||||
|
<Wrapper>
|
||||||
|
<div className="bg-fd-background rounded-lg prose-no-margin">
|
||||||
|
|
||||||
|
```js title="config.js"
|
||||||
|
import createMDX from 'fumadocs-mdx/config';
|
||||||
|
|
||||||
|
const withMDX = createMDX();
|
||||||
|
|
||||||
|
// [!code word:config]
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const config = {
|
||||||
|
// [!code highlight]
|
||||||
|
reactStrictMode: true, // [!code highlight]
|
||||||
|
}; // [!code highlight]
|
||||||
|
|
||||||
|
export default withMDX(config);
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
|
||||||
|
Display code blocks, added by default.
|
||||||
|
|
||||||
|
- Copy button
|
||||||
|
- Custom titles and icons
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Wrap the pre element in `<CodeBlock />`, which acts as the wrapper of code block.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Pre, CodeBlock } from 'fumadocs-ui/components/codeblock';
|
||||||
|
|
||||||
|
<MDX
|
||||||
|
components={{
|
||||||
|
// HTML `ref` attribute conflicts with `forwardRef`
|
||||||
|
pre: ({ ref: _ref, ...props }) => (
|
||||||
|
<CodeBlock {...props}>
|
||||||
|
<Pre>{props.children}</Pre> {/* [!code highlight] */}
|
||||||
|
</CodeBlock>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Markdown](/docs/markdown#codeblock) for usages.
|
||||||
|
|
||||||
|
### Keep Background
|
||||||
|
|
||||||
|
Use the background color generated by Shiki (the Rehype Code plugin).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Pre, CodeBlock } from 'fumadocs-ui/components/codeblock';
|
||||||
|
|
||||||
|
<MDX
|
||||||
|
components={{
|
||||||
|
pre: ({ ref: _ref, ...props }) => (
|
||||||
|
<CodeBlock keepBackground {...props}>
|
||||||
|
<Pre>{props.children}</Pre>
|
||||||
|
</CodeBlock>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Icons
|
||||||
|
|
||||||
|
Specify a custom icon by passing an `icon` prop to `CodeBlock` component.
|
||||||
|
|
||||||
|
By default, the icon will be injected by the custom Shiki transformer.
|
||||||
|
|
||||||
|
```js title="config.js"
|
||||||
|
console.log('js');
|
||||||
|
```
|
26
content/docs/mdx/heading.mdx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: Heading
|
||||||
|
description: Heading components for your MDX documentation
|
||||||
|
preview: heading
|
||||||
|
---
|
||||||
|
|
||||||
|
The heading component which automatically adds the `id` prop.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add it to your MDX components, from `h1` to `h6`.
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import { Heading } from 'fumadocs-ui/components/heading';
|
||||||
|
|
||||||
|
<MDX
|
||||||
|
components={{
|
||||||
|
h1: (props) => <Heading as="h1" {...props} />,
|
||||||
|
h2: (props) => <Heading as="h2" {...props} />,
|
||||||
|
h3: (props) => <Heading as="h3" {...props} />,
|
||||||
|
h4: (props) => <Heading as="h4" {...props} />,
|
||||||
|
h5: (props) => <Heading as="h5" {...props} />,
|
||||||
|
h6: (props) => <Heading as="h6" {...props} />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
41
content/docs/mdx/index.mdx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: MDX
|
||||||
|
description: Default MDX Components
|
||||||
|
index: true
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The default MDX components include Cards, Callouts, Code Blocks and Headings.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Relative Link
|
||||||
|
|
||||||
|
To support links with relative file path in `href`, override the default `a` component with:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { createRelativeLink } from 'fumadocs-ui/mdx';
|
||||||
|
import { source } from '@/lib/source';
|
||||||
|
|
||||||
|
const page = source.getPage(['...']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MdxContent
|
||||||
|
components={{
|
||||||
|
// override the `a` tag
|
||||||
|
a: createRelativeLink(source, page),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
[My Link](./file.mdx)
|
||||||
|
```
|
||||||
|
|
||||||
|
[Example: `../(integrations)/open-graph.mdx`](<../(integrations)/open-graph.mdx>)
|
||||||
|
|
||||||
|
<Callout type="warn">Server Component only.</Callout>
|
24
content/docs/meta.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"title": "Framework",
|
||||||
|
"description": "The docs framework",
|
||||||
|
"icon": "Building2",
|
||||||
|
"root": true,
|
||||||
|
"pages": [
|
||||||
|
"---Introduction---",
|
||||||
|
"index",
|
||||||
|
"what-is-fumadocs",
|
||||||
|
"comparisons",
|
||||||
|
"---Setup---",
|
||||||
|
"manual-installation",
|
||||||
|
"static-export",
|
||||||
|
"---Writing---",
|
||||||
|
"markdown",
|
||||||
|
"---UI---",
|
||||||
|
"customisation",
|
||||||
|
"theme",
|
||||||
|
"search",
|
||||||
|
"components",
|
||||||
|
"mdx",
|
||||||
|
"layouts"
|
||||||
|
]
|
||||||
|
}
|
77
content/docs/props.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import type { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
|
||||||
|
import type { Callout } from 'fumadocs-ui/components/callout';
|
||||||
|
import type { File, Folder } from 'fumadocs-ui/components/files';
|
||||||
|
import type { InlineTOC } from 'fumadocs-ui/components/inline-toc';
|
||||||
|
import type { TypeTable } from 'fumadocs-ui/components/type-table';
|
||||||
|
import type { Card } from 'fumadocs-ui/components/card';
|
||||||
|
import type { DocsLayoutProps } from 'fumadocs-ui/layouts/docs';
|
||||||
|
import type {
|
||||||
|
AnchorHTMLAttributes,
|
||||||
|
ComponentPropsWithoutRef,
|
||||||
|
HTMLAttributes,
|
||||||
|
} from 'react';
|
||||||
|
import type { DocsPageProps } from 'fumadocs-ui/page';
|
||||||
|
import type { AutoTypeTable } from 'fumadocs-typescript/ui';
|
||||||
|
|
||||||
|
export type AccordionsProps = Omit<
|
||||||
|
ComponentPropsWithoutRef<typeof Accordions>,
|
||||||
|
keyof ComponentPropsWithoutRef<'div'> | 'value' | 'onValueChange'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type AccordionProps = Omit<
|
||||||
|
ComponentPropsWithoutRef<typeof Accordion>,
|
||||||
|
keyof ComponentPropsWithoutRef<'div'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type CalloutProps = Omit<
|
||||||
|
ComponentPropsWithoutRef<typeof Callout>,
|
||||||
|
keyof ComponentPropsWithoutRef<'div'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type FileProps = Omit<
|
||||||
|
ComponentPropsWithoutRef<typeof File>,
|
||||||
|
keyof ComponentPropsWithoutRef<'div'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type FolderProps = Omit<
|
||||||
|
ComponentPropsWithoutRef<typeof Folder>,
|
||||||
|
keyof ComponentPropsWithoutRef<'div'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type InlineTOCProps = Omit<
|
||||||
|
ComponentPropsWithoutRef<typeof InlineTOC>,
|
||||||
|
keyof ComponentPropsWithoutRef<'div'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type CardProps = Omit<
|
||||||
|
ComponentPropsWithoutRef<typeof Card>,
|
||||||
|
keyof Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type TypeTableProps = ComponentPropsWithoutRef<typeof TypeTable>;
|
||||||
|
|
||||||
|
export type ObjectTypeProps = ComponentPropsWithoutRef<
|
||||||
|
typeof TypeTable
|
||||||
|
>['type'][string];
|
||||||
|
|
||||||
|
export type { DocsLayoutProps };
|
||||||
|
|
||||||
|
export type NavbarProps = NonNullable<DocsLayoutProps['nav']>;
|
||||||
|
|
||||||
|
export type SidebarProps = Omit<
|
||||||
|
NonNullable<DocsLayoutProps['sidebar']>,
|
||||||
|
keyof HTMLAttributes<HTMLElement>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type PageProps = DocsPageProps;
|
||||||
|
|
||||||
|
export type BreadcrumbProps = NonNullable<DocsPageProps['breadcrumb']>;
|
||||||
|
|
||||||
|
export type TOCProps = NonNullable<DocsPageProps['tableOfContent']>;
|
||||||
|
export type TOCPopoverProps = NonNullable<
|
||||||
|
DocsPageProps['tableOfContentPopover']
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type FooterProps = NonNullable<DocsPageProps['footer']>;
|
||||||
|
|
||||||
|
export type AutoTypeTableProps = ComponentPropsWithoutRef<typeof AutoTypeTable>;
|
275
content/docs/search.mdx
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
---
|
||||||
|
title: Search
|
||||||
|
description: Implement document search in your docs
|
||||||
|
---
|
||||||
|
|
||||||
|
Fumadocs UI provides a good-looking search UI for your docs, the search functionality is instead provided and documented on Fumadocs Core.
|
||||||
|
|
||||||
|
See [Document Search](/docs/headless/search).
|
||||||
|
|
||||||
|
## Search UI
|
||||||
|
|
||||||
|
Open with <kbd>⌘</kbd> <kbd>K</kbd> or <kbd>Ctrl</kbd> <kbd>K</kbd>.
|
||||||
|
|
||||||
|
### Configurations
|
||||||
|
|
||||||
|
You can customize search UI from the [Root Provider](/docs/layouts/root-provider) component in root layout.
|
||||||
|
|
||||||
|
When not specified, it uses the Default [`fetch` Search Client](/docs/headless/search/orama) powered by Orama.
|
||||||
|
|
||||||
|
### Custom Links
|
||||||
|
|
||||||
|
Add custom link items to search dialog.
|
||||||
|
They are shown as fallbacks when the query is empty.
|
||||||
|
|
||||||
|
```tsx title="app/layout.tsx"
|
||||||
|
import { RootProvider } from 'fumadocs-ui/root-provider';
|
||||||
|
|
||||||
|
<RootProvider
|
||||||
|
search={{
|
||||||
|
links: [
|
||||||
|
['Home', '/'],
|
||||||
|
['Docs', '/docs'],
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable Search
|
||||||
|
|
||||||
|
To opt-out of document search, disable it from root provider.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { RootProvider } from 'fumadocs-ui/root-provider';
|
||||||
|
|
||||||
|
<RootProvider
|
||||||
|
search={{
|
||||||
|
enabled: false,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hot Keys
|
||||||
|
|
||||||
|
Customise the hot keys to trigger search dialog.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { RootProvider } from 'fumadocs-ui/root-provider';
|
||||||
|
|
||||||
|
<RootProvider
|
||||||
|
search={{
|
||||||
|
hotKey: [
|
||||||
|
{
|
||||||
|
display: 'K',
|
||||||
|
key: 'k', // key code, or a function determining whether the key is pressed
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tag Filter
|
||||||
|
|
||||||
|
Add UI to change filters.
|
||||||
|
Make sure to configure [Tag Filter](/docs/headless/search/orama#tag-filter) on search server first.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { RootProvider } from 'fumadocs-ui/root-provider';
|
||||||
|
|
||||||
|
<RootProvider
|
||||||
|
search={{
|
||||||
|
options: {
|
||||||
|
defaultTag: 'value',
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
name: 'Tag Name',
|
||||||
|
value: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search Options
|
||||||
|
|
||||||
|
Pass options to the search client, like changing the API endpoint for Orama search server:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { RootProvider } from 'fumadocs-ui/root-provider';
|
||||||
|
|
||||||
|
<RootProvider
|
||||||
|
search={{
|
||||||
|
options: {
|
||||||
|
api: '/api/search/docs',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace Search Dialog
|
||||||
|
|
||||||
|
You can replace the default Search Dialog with:
|
||||||
|
|
||||||
|
```tsx title="components/search.tsx"
|
||||||
|
'use client';
|
||||||
|
import SearchDialog from 'fumadocs-ui/components/dialog/search-default';
|
||||||
|
import type { SharedProps } from 'fumadocs-ui/components/dialog/search';
|
||||||
|
|
||||||
|
export default function CustomDialog(props: SharedProps) {
|
||||||
|
// your own logic here
|
||||||
|
return <SearchDialog {...props} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To pass it to the Root Provider, you need a wrapper with `use client` directive.
|
||||||
|
|
||||||
|
```tsx title="provider.tsx"
|
||||||
|
'use client';
|
||||||
|
import { RootProvider } from 'fumadocs-ui/provider';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
const SearchDialog = dynamic(() => import('@/components/search')); // lazy load
|
||||||
|
|
||||||
|
export function Provider({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<RootProvider
|
||||||
|
search={{
|
||||||
|
SearchDialog,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use it instead of your previous Root Provider
|
||||||
|
|
||||||
|
```tsx title="layout.tsx"
|
||||||
|
import { Provider } from './provider';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<Provider>{children}</Provider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other Solutions
|
||||||
|
|
||||||
|
### Algolia
|
||||||
|
|
||||||
|
For the setup guide, see [Integrate Algolia Search](/docs/headless/search/algolia).
|
||||||
|
|
||||||
|
While generally we recommend building your own search with their client-side
|
||||||
|
SDK, you can also plug the built-in dialog interface.
|
||||||
|
|
||||||
|
```tsx title="components/search.tsx"
|
||||||
|
'use client';
|
||||||
|
import algo from 'algoliasearch/lite';
|
||||||
|
import type { SharedProps } from 'fumadocs-ui/components/dialog/search';
|
||||||
|
import SearchDialog from 'fumadocs-ui/components/dialog/search-algolia';
|
||||||
|
|
||||||
|
const client = algo('appId', 'apiKey');
|
||||||
|
const index = client.initIndex('indexName');
|
||||||
|
|
||||||
|
export default function CustomSearchDialog(props: SharedProps) {
|
||||||
|
return <SearchDialog index={index} {...props} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Replace `appId`, `apiKey` and `indexName` with your desired values.
|
||||||
|
|
||||||
|
2. [Replace the default search dialog](#replace-search-dialog) with your new component.
|
||||||
|
|
||||||
|
<Callout title="Note" className='mt-4'>
|
||||||
|
|
||||||
|
The built-in implementation doesn't use instant search (their official
|
||||||
|
javascript client).
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
#### Tag Filter
|
||||||
|
|
||||||
|
Same as default search client, you can configure [Tag Filter](/docs/headless/search/algolia#tag-filter) on the dialog.
|
||||||
|
|
||||||
|
```tsx title="components/search.tsx"
|
||||||
|
import SearchDialog from 'fumadocs-ui/components/dialog/search-algolia';
|
||||||
|
|
||||||
|
<SearchDialog
|
||||||
|
defaultTag="value"
|
||||||
|
tags={[
|
||||||
|
{
|
||||||
|
name: 'Tag Name',
|
||||||
|
value: 'value',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Orama Cloud
|
||||||
|
|
||||||
|
For the setup guide, see [Integrate Orama Cloud](/docs/headless/search/orama-cloud).
|
||||||
|
|
||||||
|
```tsx title="components/search.tsx"
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { OramaClient } from '@oramacloud/client';
|
||||||
|
import type { SharedProps } from 'fumadocs-ui/components/dialog/search';
|
||||||
|
import SearchDialog from 'fumadocs-ui/components/dialog/search-orama';
|
||||||
|
|
||||||
|
const client = new OramaClient({
|
||||||
|
endpoint: 'endpoint',
|
||||||
|
api_key: 'apiKey',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function CustomSearchDialog(props: SharedProps) {
|
||||||
|
return <SearchDialog {...props} client={client} showOrama />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Replace `endpoint`, `apiKey` with your desired values.
|
||||||
|
2. [Replace the default search dialog](#replace-search-dialog) with your new component.
|
||||||
|
|
||||||
|
### Community Integrations
|
||||||
|
|
||||||
|
A list of integrations maintained by community.
|
||||||
|
|
||||||
|
- [Trieve Search](/docs/headless/search/trieve)
|
||||||
|
|
||||||
|
## Built-in UI
|
||||||
|
|
||||||
|
If you want to use the built-in search dialog UI instead of building your own,
|
||||||
|
you may use the `SearchDialog` component.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import {
|
||||||
|
SearchDialog,
|
||||||
|
type SharedProps,
|
||||||
|
} from 'fumadocs-ui/components/dialog/search';
|
||||||
|
|
||||||
|
export default function CustomSearchDialog(props: SharedProps) {
|
||||||
|
return <SearchDialog {...props} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="warn" title="Unstable">
|
||||||
|
It is an internal API, might break during iterations
|
||||||
|
</Callout>
|
55
content/docs/static-export.mdx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: Static Export
|
||||||
|
description: Enable static export with Fumadocs
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Fumadocs is fully compatible with Next.js static export, allowing you to export the app as a static HTML site without a Node.js server.
|
||||||
|
|
||||||
|
```js title="next.config.mjs"
|
||||||
|
/**
|
||||||
|
* @type {import('next').NextConfig}
|
||||||
|
*/
|
||||||
|
const nextConfig = {
|
||||||
|
output: 'export',
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Search
|
||||||
|
|
||||||
|
### Cloud Solutions
|
||||||
|
|
||||||
|
Since the search functionality is powered by remote servers, static export works without configuration.
|
||||||
|
|
||||||
|
### Built-in Search
|
||||||
|
|
||||||
|
The default search config of Orama Search uses route handlers, which is not supported by static export.
|
||||||
|
|
||||||
|
Instead, you can build the search indexes statically following the [Orama Search](/docs/headless/search/orama#static-export) guide.
|
||||||
|
And enable static mode on search client from Root Provider:
|
||||||
|
|
||||||
|
```tsx title="app/layout.tsx"
|
||||||
|
import { RootProvider } from 'fumadocs-ui/provider';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<body>
|
||||||
|
<RootProvider
|
||||||
|
search={{
|
||||||
|
options: {
|
||||||
|
type: 'static', // [!code highlight]
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows the route handler to be statically cached into a single file, and search will be computed on browser instead.
|
25
content/docs/theme.client.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { buttonVariants } from '@/components/ui/button';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { type ReactElement, useState } from 'react';
|
||||||
|
|
||||||
|
export function WidthTrigger(): ReactElement {
|
||||||
|
const [enabled, setEnabled] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cn(buttonVariants({ variant: 'secondary' }))}
|
||||||
|
onClick={() => {
|
||||||
|
setEnabled((prev) => !prev);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{enabled ? <style>{`:root { --fd-layout-width: 1400px; }`}</style> : null}
|
||||||
|
Trigger Width:
|
||||||
|
<span className="ms-1.5 text-fd-muted-foreground">
|
||||||
|
{enabled ? '1400px' : 'default'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
166
content/docs/theme.mdx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
---
|
||||||
|
title: Themes
|
||||||
|
description: Add Theme to Fumadocs UI
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Note only Tailwind CSS v4 is supported:
|
||||||
|
|
||||||
|
```css title="Tailwind CSS"
|
||||||
|
@import 'tailwindcss';
|
||||||
|
@import 'fumadocs-ui/css/neutral.css';
|
||||||
|
@import 'fumadocs-ui/css/preset.css';
|
||||||
|
|
||||||
|
/* path of `fumadocs-ui` relative to the CSS file */
|
||||||
|
@source '../node_modules/fumadocs-ui/dist/**/*.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preflight Changes
|
||||||
|
|
||||||
|
By using the Tailwind CSS plugin, or the pre-built stylesheet, your default border, text and background
|
||||||
|
colors will be changed.
|
||||||
|
|
||||||
|
### Light/Dark Modes
|
||||||
|
|
||||||
|
Fumadocs supports light/dark modes with [`next-themes`](https://github.com/pacocoursey/next-themes), it is included in Root Provider.
|
||||||
|
|
||||||
|
See [Root Provider](/docs/layouts/root-provider#theme-provider) to learn more.
|
||||||
|
|
||||||
|
### RTL Layout
|
||||||
|
|
||||||
|
RTL (Right-to-left) layout is supported.
|
||||||
|
|
||||||
|
To enable RTL, set the `dir` prop to `rtl` in body and root provider (required for Radix UI).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { RootProvider } from 'fumadocs-ui/provider';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<body dir="rtl">
|
||||||
|
<RootProvider dir="rtl">{children}</RootProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prefix
|
||||||
|
|
||||||
|
Fumadocs UI has its own colors, animations, and utilities.
|
||||||
|
By default, it adds a `fd-` prefix to avoid conflicts with Shadcn UI or your own CSS variables.
|
||||||
|
|
||||||
|
You can use them without the prefix by adding some aliases:
|
||||||
|
|
||||||
|
```css title="Tailwind CSS"
|
||||||
|
@theme {
|
||||||
|
--color-primary: var(--color-fd-primary);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> You can use it with CSS media queries for responsive design.
|
||||||
|
|
||||||
|
### Layout Width
|
||||||
|
|
||||||
|
Customise the max width of docs layout with CSS Variables.
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--fd-layout-width: 1400px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<WidthTrigger />
|
||||||
|
|
||||||
|
## Tailwind CSS Preset
|
||||||
|
|
||||||
|
The Tailwind CSS preset introduces new colors and extra utilities including `fd-steps`.
|
||||||
|
|
||||||
|
### Themes
|
||||||
|
|
||||||
|
It comes with many themes out-of-the-box, you can pick one you prefer.
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import 'fumadocs-ui/css/<theme>.css';
|
||||||
|
|
||||||
|
/* Example */
|
||||||
|
@import 'fumadocs-ui/css/black.css';
|
||||||
|
```
|
||||||
|
|
||||||
|
<Tabs items={['neutral', 'black', 'vitepress', 'dusk', 'catppuccin', 'ocean', 'purple']}>
|
||||||
|
|
||||||
|
<Tab value='neutral'>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value='black'>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value='vitepress'>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value='dusk'>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value='Catppuccin'>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value='ocean'>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value='purple'>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Colors
|
||||||
|
|
||||||
|
The design system was inspired by [Shadcn UI](https://ui.shadcn.com), you can easily customize the colors using CSS variables.
|
||||||
|
|
||||||
|
```css title="global.css"
|
||||||
|
:root {
|
||||||
|
--color-fd-background: hsl(0, 0%, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--color-fd-background: hsl(0, 0%, 0%);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
We have a built-in plugin forked from [Tailwind CSS Typography](https://tailwindcss.com/docs/typography-plugin).
|
||||||
|
|
||||||
|
The plugin adds a `prose` class and variants to customise it.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="prose">
|
||||||
|
<h1>Good Heading</h1>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
> The plugin works with and only with Fumadocs UI's MDX components, it may conflict with `@tailwindcss/typography`.
|
||||||
|
> If you need to use `@tailwindcss/typography` over the default plugin, [set a class name option](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/README.md#changing-the-default-class-name) to avoid conflicts.
|
58
content/docs/what-is-fumadocs.mdx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
title: What is Fumadocs
|
||||||
|
description: Introducing Fumadocs, a docs framework that you can break.
|
||||||
|
icon: CircleHelp
|
||||||
|
---
|
||||||
|
|
||||||
|
Fumadocs was created because I wanted a more customisable experience for building docs, to be a docs framework that is not opinionated, **a "framework" that you can break**.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
**Less Abstraction:** Fumadocs expects you to write code and cooperate with the rest of your software.
|
||||||
|
While most frameworks are configured with a configuration file, they usually lack flexibility when you hope to tune its details.
|
||||||
|
You can’t control how they render the page nor the internal logic. Fumadocs shows you how the app works, instead of a single configuration file.
|
||||||
|
|
||||||
|
**Next.js Fundamentals:** It gives you the utilities and a good-looking UI.
|
||||||
|
You are still using features of Next.js App Router, like **Static Site Generation**. There is nothing new for Next.js developers, so you can use it with confidence.
|
||||||
|
|
||||||
|
**Opinionated on UI:** The only thing Fumadocs UI (the default theme) offers is **User Interface**. The UI is opinionated for bringing better mobile responsiveness and user experience.
|
||||||
|
Instead, we use a much more flexible approach inspired by Shadcn UI — [Fumadocs CLI](/docs/cli), so we can iterate our design quick, and welcome for more feedback about the UI.
|
||||||
|
|
||||||
|
## Why Fumadocs
|
||||||
|
|
||||||
|
Fumadocs is designed with flexibility in mind.
|
||||||
|
|
||||||
|
You can use `fumadocs-core` as a headless UI library and bring your own styles.
|
||||||
|
Fumadocs MDX is also a useful library to handle MDX content in Next.js. It also includes:
|
||||||
|
|
||||||
|
- Many built-in components.
|
||||||
|
- Typescript Twoslash, OpenAPI, and Math (KaTeX) integrations.
|
||||||
|
- Fast and optimized by default, natively built on App Router.
|
||||||
|
- Tight integration with Next.js, you can add it to an existing Next.js project easily.
|
||||||
|
|
||||||
|
You can read [Comparisons](/docs/comparisons) if you're interested.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
Fumadocs focuses on **authoring experience**, it provides a beautiful theme and many docs automation tools.
|
||||||
|
|
||||||
|
It helps you to iterate your codebase faster while never leaving your docs behind.
|
||||||
|
You can take this site as an example of docs site built with Fumadocs.
|
||||||
|
|
||||||
|
### Blog sites
|
||||||
|
|
||||||
|
Since Next.js is already a powerful framework, most features can be implemented with **just Next.js**.
|
||||||
|
|
||||||
|
Fumadocs provides additional tooling for Next.js, including syntax highlighting, document search, and a default theme (Fumadocs UI).
|
||||||
|
It helps you to avoid reinventing the wheels.
|
||||||
|
|
||||||
|
## When to use Fumadocs
|
||||||
|
|
||||||
|
For most of the web applications, vanilla React.js is no longer enough.
|
||||||
|
Nowadays, we also wish to have a blog, a showcase page, a FAQ page, etc. With a
|
||||||
|
fancy UI that's breathtaking, in these cases, Fumadocs can help you build the
|
||||||
|
docs easier, with less boilerplate.
|
||||||
|
|
||||||
|
Fumadocs is maintained by Fuma and many contributors, with care on the maintainability of codebase.
|
||||||
|
While we don't aim to offer every functionality people wanted, we're more focused on making basic features perfect and well-maintained.
|
||||||
|
You can also help Fumadocs to be more useful by contributing!
|
@ -76,6 +76,8 @@
|
|||||||
"embla-carousel-react": "^8.5.2",
|
"embla-carousel-react": "^8.5.2",
|
||||||
"framer-motion": "^12.4.7",
|
"framer-motion": "^12.4.7",
|
||||||
"fumadocs-core": "^15.1.2",
|
"fumadocs-core": "^15.1.2",
|
||||||
|
"fumadocs-twoslash": "^3.1.0",
|
||||||
|
"fumadocs-typescript": "^4.0.1",
|
||||||
"fumadocs-ui": "^15.1.2",
|
"fumadocs-ui": "^15.1.2",
|
||||||
"geist": "^1.3.1",
|
"geist": "^1.3.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
@ -120,6 +122,7 @@
|
|||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@content-collections/cli": "^0.1.6",
|
"@content-collections/cli": "^0.1.6",
|
||||||
"@tailwindcss/postcss": "^4.0.14",
|
"@tailwindcss/postcss": "^4.0.14",
|
||||||
|
"@types/mdx": "^2.0.13",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/pg": "^8.11.11",
|
"@types/pg": "^8.11.11",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
|
201
pnpm-lock.yaml
generated
@ -203,6 +203,12 @@ importers:
|
|||||||
fumadocs-core:
|
fumadocs-core:
|
||||||
specifier: ^15.1.2
|
specifier: ^15.1.2
|
||||||
version: 15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
fumadocs-twoslash:
|
||||||
|
specifier: ^3.1.0
|
||||||
|
version: 3.1.0(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-ui@15.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-core@15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tailwindcss@4.0.14))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(shiki@2.4.2)(typescript@5.7.3)
|
||||||
|
fumadocs-typescript:
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1(typescript@5.7.3)
|
||||||
fumadocs-ui:
|
fumadocs-ui:
|
||||||
specifier: ^15.1.2
|
specifier: ^15.1.2
|
||||||
version: 15.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-core@15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tailwindcss@4.0.14)
|
version: 15.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-core@15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tailwindcss@4.0.14)
|
||||||
@ -330,6 +336,9 @@ importers:
|
|||||||
'@tailwindcss/postcss':
|
'@tailwindcss/postcss':
|
||||||
specifier: ^4.0.14
|
specifier: ^4.0.14
|
||||||
version: 4.0.14
|
version: 4.0.14
|
||||||
|
'@types/mdx':
|
||||||
|
specifier: ^2.0.13
|
||||||
|
version: 2.0.13
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20
|
specifier: ^20
|
||||||
version: 20.17.19
|
version: 20.17.19
|
||||||
@ -1813,6 +1822,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
|
resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
|
||||||
engines: {node: ^14.21.3 || >=16}
|
engines: {node: ^14.21.3 || >=16}
|
||||||
|
|
||||||
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
'@nodelib/fs.stat@2.0.5':
|
||||||
|
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
'@nodelib/fs.walk@1.2.8':
|
||||||
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
'@one-ini/wasm@0.1.1':
|
'@one-ini/wasm@0.1.1':
|
||||||
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
|
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
|
||||||
|
|
||||||
@ -2715,6 +2736,11 @@ packages:
|
|||||||
'@shikijs/transformers@3.2.1':
|
'@shikijs/transformers@3.2.1':
|
||||||
resolution: {integrity: sha512-oIT40p8LOPV/6XLnUrVPeRtJtbu0Mpl+BjGFuMXw870eX9zTSQlidg7CsksFDVyUiSAOC/CH1RQm+ldZp0/6eQ==}
|
resolution: {integrity: sha512-oIT40p8LOPV/6XLnUrVPeRtJtbu0Mpl+BjGFuMXw870eX9zTSQlidg7CsksFDVyUiSAOC/CH1RQm+ldZp0/6eQ==}
|
||||||
|
|
||||||
|
'@shikijs/twoslash@3.2.1':
|
||||||
|
resolution: {integrity: sha512-2ZiL9xXY8JRXHG5BdJXE9KoIeSsyH9/yK+YTN90/SUIKkq7Nf5dWqXp5wJ6+4SL0FQO8mq2HUutwqU+gamOgOA==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=5.5.0'
|
||||||
|
|
||||||
'@shikijs/types@2.4.2':
|
'@shikijs/types@2.4.2':
|
||||||
resolution: {integrity: sha512-e28aFDPwVgK8H2nPrEA5CexLa5yumBvb5aF6nN4SlmqaBFOuGQdxX/Cfh8rwRFALepJtlj0P3wvJ4oL+ndxgSA==}
|
resolution: {integrity: sha512-e28aFDPwVgK8H2nPrEA5CexLa5yumBvb5aF6nN4SlmqaBFOuGQdxX/Cfh8rwRFALepJtlj0P3wvJ4oL+ndxgSA==}
|
||||||
|
|
||||||
@ -3051,6 +3077,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==}
|
resolution: {integrity: sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@ts-morph/common@0.26.1':
|
||||||
|
resolution: {integrity: sha512-Sn28TGl/4cFpcM+jwsH1wLncYq3FtN/BIpem+HOygfBWPT5pAeS5dB4VFVzV8FbnOKHpDLZmvAl4AjPEev5idA==}
|
||||||
|
|
||||||
'@turf/boolean-point-in-polygon@6.5.0':
|
'@turf/boolean-point-in-polygon@6.5.0':
|
||||||
resolution: {integrity: sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==}
|
resolution: {integrity: sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==}
|
||||||
|
|
||||||
@ -3158,6 +3187,11 @@ packages:
|
|||||||
'@types/unist@3.0.3':
|
'@types/unist@3.0.3':
|
||||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||||
|
|
||||||
|
'@typescript/vfs@1.6.1':
|
||||||
|
resolution: {integrity: sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '*'
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
|
||||||
@ -3346,6 +3380,9 @@ packages:
|
|||||||
react: ^18 || ^19 || ^19.0.0-rc
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
react-dom: ^18 || ^19 || ^19.0.0-rc
|
react-dom: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
|
||||||
|
code-block-writer@13.0.3:
|
||||||
|
resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==}
|
||||||
|
|
||||||
collapse-white-space@2.1.0:
|
collapse-white-space@2.1.0:
|
||||||
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
|
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
|
||||||
|
|
||||||
@ -3804,10 +3841,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
|
resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
fast-glob@3.3.3:
|
||||||
|
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
||||||
|
engines: {node: '>=8.6.0'}
|
||||||
|
|
||||||
fast-xml-parser@4.4.1:
|
fast-xml-parser@4.4.1:
|
||||||
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
|
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
fastq@1.19.1:
|
||||||
|
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
||||||
|
|
||||||
fault@2.0.1:
|
fault@2.0.1:
|
||||||
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
|
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
|
||||||
|
|
||||||
@ -3870,6 +3914,18 @@ packages:
|
|||||||
react-dom:
|
react-dom:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
fumadocs-twoslash@3.1.0:
|
||||||
|
resolution: {integrity: sha512-5O+lFOv4cTQqBevS/yxCrjKZjBhZNggZQis14QzGXzyL75pe+Uv7UjcMsxo4Jmt8jJOLt0pGVLhJwFf11u/9BA==}
|
||||||
|
peerDependencies:
|
||||||
|
fumadocs-ui: ^15.0.0
|
||||||
|
react: 18.x.x || 19.x.x
|
||||||
|
shiki: 1.x.x || 2.x.x || 3.x.x
|
||||||
|
|
||||||
|
fumadocs-typescript@4.0.1:
|
||||||
|
resolution: {integrity: sha512-z9FUaCRbesjukJaSbg46FpTRHyfhfI4ydFHOm6Lpp7GDQsX4aJJwj7bI5PfyN1BqJAfoTym2QYv4+PHmqyNZiw==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '*'
|
||||||
|
|
||||||
fumadocs-ui@15.1.2:
|
fumadocs-ui@15.1.2:
|
||||||
resolution: {integrity: sha512-Jel9zhg85BfMfzDW2YX0lE0qeqnQmWj4s9QpbdKnPnrqeq2jRJJnpJExk3kaeWGG5KousEWG5ihsz3sFQvYCYw==}
|
resolution: {integrity: sha512-Jel9zhg85BfMfzDW2YX0lE0qeqnQmWj4s9QpbdKnPnrqeq2jRJJnpJExk3kaeWGG5KousEWG5ihsz3sFQvYCYw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3916,6 +3972,10 @@ packages:
|
|||||||
github-slugger@2.0.0:
|
github-slugger@2.0.0:
|
||||||
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
||||||
|
|
||||||
|
glob-parent@5.1.2:
|
||||||
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
glob@10.3.4:
|
glob@10.3.4:
|
||||||
resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==}
|
resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
@ -4330,6 +4390,10 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
esbuild: 0.*
|
esbuild: 0.*
|
||||||
|
|
||||||
|
merge2@1.4.1:
|
||||||
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
mgrs@1.0.0:
|
mgrs@1.0.0:
|
||||||
resolution: {integrity: sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA==}
|
resolution: {integrity: sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA==}
|
||||||
|
|
||||||
@ -4667,6 +4731,9 @@ packages:
|
|||||||
parseley@0.12.1:
|
parseley@0.12.1:
|
||||||
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
|
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
|
||||||
|
|
||||||
|
path-browserify@1.0.1:
|
||||||
|
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||||
|
|
||||||
path-key@3.1.1:
|
path-key@3.1.1:
|
||||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -4823,6 +4890,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
queue-microtask@1.2.3:
|
||||||
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
randombytes@2.1.0:
|
randombytes@2.1.0:
|
||||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||||
|
|
||||||
@ -5032,9 +5102,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
|
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
reusify@1.1.0:
|
||||||
|
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||||
|
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||||
|
|
||||||
rou3@0.5.1:
|
rou3@0.5.1:
|
||||||
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
|
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
|
||||||
|
|
||||||
|
run-parallel@1.2.0:
|
||||||
|
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||||
|
|
||||||
rxjs@7.8.1:
|
rxjs@7.8.1:
|
||||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||||
|
|
||||||
@ -5296,6 +5373,9 @@ packages:
|
|||||||
trough@2.2.0:
|
trough@2.2.0:
|
||||||
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
|
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
|
||||||
|
|
||||||
|
ts-morph@25.0.1:
|
||||||
|
resolution: {integrity: sha512-QJEiTdnz1YjrB3JFhd626gX4rKHDLSjSVMvGGG4v7ONc3RBwa0Eei98G9AT9uNFDMtV54JyuXsFeC+OH0n6bXQ==}
|
||||||
|
|
||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
@ -5307,6 +5387,14 @@ packages:
|
|||||||
tw-animate-css@1.2.4:
|
tw-animate-css@1.2.4:
|
||||||
resolution: {integrity: sha512-yt+HkJB41NAvOffe4NweJU6fLqAlVx/mBX6XmHRp15kq0JxTtOKaIw8pVSWM1Z+n2nXtyi7cW6C9f0WG/F/QAQ==}
|
resolution: {integrity: sha512-yt+HkJB41NAvOffe4NweJU6fLqAlVx/mBX6XmHRp15kq0JxTtOKaIw8pVSWM1Z+n2nXtyi7cW6C9f0WG/F/QAQ==}
|
||||||
|
|
||||||
|
twoslash-protocol@0.3.1:
|
||||||
|
resolution: {integrity: sha512-BMePTL9OkuNISSyyMclBBhV2s9++DiOCyhhCoV5Kaht6eaWLwVjCCUJHY33eZJPsyKeZYS8Wzz0h+XI01VohVw==}
|
||||||
|
|
||||||
|
twoslash@0.3.1:
|
||||||
|
resolution: {integrity: sha512-OGqMTGvqXTcb92YQdwGfEdK0nZJA64Aj/ChLOelbl3TfYch2IoBST0Yx4C0LQ7Lzyqm9RpgcpgDxeXQIz4p2Kg==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: ^5.5.0
|
||||||
|
|
||||||
type-fest@4.35.0:
|
type-fest@4.35.0:
|
||||||
resolution: {integrity: sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==}
|
resolution: {integrity: sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@ -6964,6 +7052,18 @@ snapshots:
|
|||||||
|
|
||||||
'@noble/hashes@1.7.1': {}
|
'@noble/hashes@1.7.1': {}
|
||||||
|
|
||||||
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
|
dependencies:
|
||||||
|
'@nodelib/fs.stat': 2.0.5
|
||||||
|
run-parallel: 1.2.0
|
||||||
|
|
||||||
|
'@nodelib/fs.stat@2.0.5': {}
|
||||||
|
|
||||||
|
'@nodelib/fs.walk@1.2.8':
|
||||||
|
dependencies:
|
||||||
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
|
fastq: 1.19.1
|
||||||
|
|
||||||
'@one-ini/wasm@0.1.1': {}
|
'@one-ini/wasm@0.1.1': {}
|
||||||
|
|
||||||
'@opentelemetry/api@1.9.0': {}
|
'@opentelemetry/api@1.9.0': {}
|
||||||
@ -7926,6 +8026,15 @@ snapshots:
|
|||||||
'@shikijs/core': 3.2.1
|
'@shikijs/core': 3.2.1
|
||||||
'@shikijs/types': 3.2.1
|
'@shikijs/types': 3.2.1
|
||||||
|
|
||||||
|
'@shikijs/twoslash@3.2.1(typescript@5.7.3)':
|
||||||
|
dependencies:
|
||||||
|
'@shikijs/core': 3.2.1
|
||||||
|
'@shikijs/types': 3.2.1
|
||||||
|
twoslash: 0.3.1(typescript@5.7.3)
|
||||||
|
typescript: 5.7.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@shikijs/types@2.4.2':
|
'@shikijs/types@2.4.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@shikijs/vscode-textmate': 10.0.2
|
'@shikijs/vscode-textmate': 10.0.2
|
||||||
@ -8368,6 +8477,12 @@ snapshots:
|
|||||||
|
|
||||||
'@tanstack/table-core@8.21.2': {}
|
'@tanstack/table-core@8.21.2': {}
|
||||||
|
|
||||||
|
'@ts-morph/common@0.26.1':
|
||||||
|
dependencies:
|
||||||
|
fast-glob: 3.3.3
|
||||||
|
minimatch: 9.0.5
|
||||||
|
path-browserify: 1.0.1
|
||||||
|
|
||||||
'@turf/boolean-point-in-polygon@6.5.0':
|
'@turf/boolean-point-in-polygon@6.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@turf/helpers': 6.5.0
|
'@turf/helpers': 6.5.0
|
||||||
@ -8479,6 +8594,13 @@ snapshots:
|
|||||||
|
|
||||||
'@types/unist@3.0.3': {}
|
'@types/unist@3.0.3': {}
|
||||||
|
|
||||||
|
'@typescript/vfs@1.6.1(typescript@5.7.3)':
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.0
|
||||||
|
typescript: 5.7.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
abbrev@3.0.0: {}
|
abbrev@3.0.0: {}
|
||||||
@ -8667,6 +8789,8 @@ snapshots:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
- '@types/react-dom'
|
- '@types/react-dom'
|
||||||
|
|
||||||
|
code-block-writer@13.0.3: {}
|
||||||
|
|
||||||
collapse-white-space@2.1.0: {}
|
collapse-white-space@2.1.0: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
@ -9142,10 +9266,22 @@ snapshots:
|
|||||||
|
|
||||||
fast-equals@5.2.2: {}
|
fast-equals@5.2.2: {}
|
||||||
|
|
||||||
|
fast-glob@3.3.3:
|
||||||
|
dependencies:
|
||||||
|
'@nodelib/fs.stat': 2.0.5
|
||||||
|
'@nodelib/fs.walk': 1.2.8
|
||||||
|
glob-parent: 5.1.2
|
||||||
|
merge2: 1.4.1
|
||||||
|
micromatch: 4.0.8
|
||||||
|
|
||||||
fast-xml-parser@4.4.1:
|
fast-xml-parser@4.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
strnum: 1.1.2
|
strnum: 1.1.2
|
||||||
|
|
||||||
|
fastq@1.19.1:
|
||||||
|
dependencies:
|
||||||
|
reusify: 1.1.0
|
||||||
|
|
||||||
fault@2.0.1:
|
fault@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
format: 0.2.2
|
format: 0.2.2
|
||||||
@ -9202,6 +9338,40 @@ snapshots:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
fumadocs-twoslash@3.1.0(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-ui@15.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-core@15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tailwindcss@4.0.14))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(shiki@2.4.2)(typescript@5.7.3):
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-popover': 1.1.6(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
'@shikijs/twoslash': 3.2.1(typescript@5.7.3)
|
||||||
|
fumadocs-ui: 15.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-core@15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tailwindcss@4.0.14)
|
||||||
|
mdast-util-from-markdown: 2.0.2
|
||||||
|
mdast-util-gfm: 3.1.0
|
||||||
|
mdast-util-to-hast: 13.2.0
|
||||||
|
react: 19.0.0
|
||||||
|
shiki: 2.4.2
|
||||||
|
tailwind-merge: 3.0.2
|
||||||
|
twoslash: 0.3.1(typescript@5.7.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- '@types/react-dom'
|
||||||
|
- react-dom
|
||||||
|
- supports-color
|
||||||
|
- typescript
|
||||||
|
|
||||||
|
fumadocs-typescript@4.0.1(typescript@5.7.3):
|
||||||
|
dependencies:
|
||||||
|
estree-util-value-to-estree: 3.3.2
|
||||||
|
fast-glob: 3.3.3
|
||||||
|
hast-util-to-estree: 3.1.3
|
||||||
|
hast-util-to-jsx-runtime: 2.3.6
|
||||||
|
remark: 15.0.1
|
||||||
|
remark-rehype: 11.1.1
|
||||||
|
shiki: 3.2.1
|
||||||
|
ts-morph: 25.0.1
|
||||||
|
typescript: 5.7.3
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
fumadocs-ui@15.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-core@15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tailwindcss@4.0.14):
|
fumadocs-ui@15.1.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(fumadocs-core@15.1.2(@types/react@19.0.9)(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tailwindcss@4.0.14):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-accordion': 1.2.3(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
'@radix-ui/react-accordion': 1.2.3(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
@ -9266,6 +9436,10 @@ snapshots:
|
|||||||
|
|
||||||
github-slugger@2.0.0: {}
|
github-slugger@2.0.0: {}
|
||||||
|
|
||||||
|
glob-parent@5.1.2:
|
||||||
|
dependencies:
|
||||||
|
is-glob: 4.0.3
|
||||||
|
|
||||||
glob@10.3.4:
|
glob@10.3.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
foreground-child: 3.3.0
|
foreground-child: 3.3.0
|
||||||
@ -9881,6 +10055,8 @@ snapshots:
|
|||||||
- acorn
|
- acorn
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
merge2@1.4.1: {}
|
||||||
|
|
||||||
mgrs@1.0.0: {}
|
mgrs@1.0.0: {}
|
||||||
|
|
||||||
micromark-core-commonmark@2.0.2:
|
micromark-core-commonmark@2.0.2:
|
||||||
@ -10365,6 +10541,8 @@ snapshots:
|
|||||||
leac: 0.6.0
|
leac: 0.6.0
|
||||||
peberminta: 0.9.0
|
peberminta: 0.9.0
|
||||||
|
|
||||||
|
path-browserify@1.0.1: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
path-key@3.1.1: {}
|
||||||
|
|
||||||
path-parse@1.0.7: {}
|
path-parse@1.0.7: {}
|
||||||
@ -10512,6 +10690,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
|
|
||||||
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
randombytes@2.1.0:
|
randombytes@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
@ -10834,8 +11014,14 @@ snapshots:
|
|||||||
onetime: 5.1.2
|
onetime: 5.1.2
|
||||||
signal-exit: 3.0.7
|
signal-exit: 3.0.7
|
||||||
|
|
||||||
|
reusify@1.1.0: {}
|
||||||
|
|
||||||
rou3@0.5.1: {}
|
rou3@0.5.1: {}
|
||||||
|
|
||||||
|
run-parallel@1.2.0:
|
||||||
|
dependencies:
|
||||||
|
queue-microtask: 1.2.3
|
||||||
|
|
||||||
rxjs@7.8.1:
|
rxjs@7.8.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@ -11139,6 +11325,11 @@ snapshots:
|
|||||||
|
|
||||||
trough@2.2.0: {}
|
trough@2.2.0: {}
|
||||||
|
|
||||||
|
ts-morph@25.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@ts-morph/common': 0.26.1
|
||||||
|
code-block-writer: 13.0.3
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
tsx@4.19.3:
|
tsx@4.19.3:
|
||||||
@ -11150,6 +11341,16 @@ snapshots:
|
|||||||
|
|
||||||
tw-animate-css@1.2.4: {}
|
tw-animate-css@1.2.4: {}
|
||||||
|
|
||||||
|
twoslash-protocol@0.3.1: {}
|
||||||
|
|
||||||
|
twoslash@0.3.1(typescript@5.7.3):
|
||||||
|
dependencies:
|
||||||
|
'@typescript/vfs': 1.6.1(typescript@5.7.3)
|
||||||
|
twoslash-protocol: 0.3.1
|
||||||
|
typescript: 5.7.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
type-fest@4.35.0: {}
|
type-fest@4.35.0: {}
|
||||||
|
|
||||||
type-flag@3.0.0: {}
|
type-flag@3.0.0: {}
|
||||||
|
BIN
public/images/docs/banner.png
Normal file
After Width: | Height: | Size: 358 KiB |
BIN
public/images/docs/docs-nav.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/images/docs/nav.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
public/images/docs/notebook.png
Normal file
After Width: | Height: | Size: 536 KiB |
BIN
public/images/docs/sidebar-tabs.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/docs/sidebar.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/images/docs/themes/black.png
vendored
Normal file
After Width: | Height: | Size: 538 KiB |
BIN
public/images/docs/themes/catppuccin.png
vendored
Normal file
After Width: | Height: | Size: 577 KiB |
BIN
public/images/docs/themes/dusk.png
vendored
Normal file
After Width: | Height: | Size: 573 KiB |
BIN
public/images/docs/themes/neutral.png
vendored
Normal file
After Width: | Height: | Size: 533 KiB |
BIN
public/images/docs/themes/ocean.png
vendored
Normal file
After Width: | Height: | Size: 611 KiB |
BIN
public/images/docs/themes/purple.png
vendored
Normal file
After Width: | Height: | Size: 600 KiB |
BIN
public/images/docs/themes/vitepress.png
vendored
Normal file
After Width: | Height: | Size: 588 KiB |
BIN
public/images/docs/webhook.png
Normal file
After Width: | Height: | Size: 166 KiB |
@ -1,16 +1,26 @@
|
|||||||
|
import { UiOverview } from '@/components/docs/ui-overview';
|
||||||
|
import { Wrapper } from '@/components/docs/preview/wrapper';
|
||||||
|
import { HoverCard, HoverCardContent, HoverCardTrigger, } from '@/components/ui/hover-card';
|
||||||
import { LOCALES } from '@/i18n/routing';
|
import { LOCALES } from '@/i18n/routing';
|
||||||
import { source } from '@/lib/docs/source';
|
import { source } from '@/lib/docs/source';
|
||||||
import { MDXContent } from '@content-collections/mdx/react';
|
import { MDXContent } from '@content-collections/mdx/react';
|
||||||
import defaultMdxComponents, { createRelativeLink } from 'fumadocs-ui/mdx';
|
import Link from 'fumadocs-core/link';
|
||||||
import {
|
import { Popup, PopupContent, PopupTrigger } from 'fumadocs-twoslash/ui';
|
||||||
DocsBody,
|
import { createGenerator } from 'fumadocs-typescript';
|
||||||
DocsDescription,
|
import { AutoTypeTable } from 'fumadocs-typescript/ui';
|
||||||
DocsPage,
|
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
|
||||||
DocsTitle
|
import { Callout } from 'fumadocs-ui/components/callout';
|
||||||
} from 'fumadocs-ui/page';
|
import { File, Files, Folder } from 'fumadocs-ui/components/files';
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||||
|
import { TypeTable } from 'fumadocs-ui/components/type-table';
|
||||||
|
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
||||||
|
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page';
|
||||||
|
import type { MDXComponents } from 'mdx/types';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Locale } from 'next-intl';
|
import { Locale } from 'next-intl';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
import { ComponentProps, FC, ReactNode } from 'react';
|
||||||
|
import * as Preview from '@/components/docs/preview';
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
const locales = LOCALES;
|
const locales = LOCALES;
|
||||||
@ -42,6 +52,19 @@ export async function generateMetadata({
|
|||||||
} satisfies Metadata;
|
} satisfies Metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PreviewRenderer({ preview }: { preview: string }): ReactNode {
|
||||||
|
if (preview && preview in Preview) {
|
||||||
|
const Comp = Preview[preview as keyof typeof Preview];
|
||||||
|
return <Comp />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generator = createGenerator();
|
||||||
|
|
||||||
|
export const revalidate = false;
|
||||||
|
|
||||||
interface DocPageProps {
|
interface DocPageProps {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
slug?: string[];
|
slug?: string[];
|
||||||
@ -49,7 +72,13 @@ interface DocPageProps {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({
|
/**
|
||||||
|
* Doc Page
|
||||||
|
*
|
||||||
|
* ref:
|
||||||
|
* https://github.com/fuma-nama/fumadocs/blob/dev/apps/docs/app/docs/%5B...slug%5D/page.tsx
|
||||||
|
*/
|
||||||
|
export default async function DocPage({
|
||||||
params,
|
params,
|
||||||
}: DocPageProps) {
|
}: DocPageProps) {
|
||||||
const { slug, locale } = await params;
|
const { slug, locale } = await params;
|
||||||
@ -61,6 +90,8 @@ export default async function Page({
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const preview = page.data.preview;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocsPage toc={page.data.toc}
|
<DocsPage toc={page.data.toc}
|
||||||
full={page.data.full}
|
full={page.data.full}
|
||||||
@ -75,13 +106,63 @@ export default async function Page({
|
|||||||
{page.data.description}
|
{page.data.description}
|
||||||
</DocsDescription>
|
</DocsDescription>
|
||||||
<DocsBody>
|
<DocsBody>
|
||||||
|
{/* Preview Rendered Component */}
|
||||||
|
{preview ? <PreviewRenderer preview={preview} /> : null}
|
||||||
|
|
||||||
|
{/* MDX Content */}
|
||||||
<MDXContent
|
<MDXContent
|
||||||
code={page.data.body}
|
code={page.data.body}
|
||||||
components={{
|
components={{
|
||||||
...defaultMdxComponents,
|
...defaultMdxComponents,
|
||||||
// this allows you to link to other pages with relative file paths
|
...((await import('lucide-react')) as unknown as MDXComponents),
|
||||||
a: createRelativeLink(source, page),
|
a: ({ href, ...props }) => {
|
||||||
// you can add other MDX components here
|
const found = source.getPageByHref(href ?? '', {
|
||||||
|
dir: page.file.dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found) return <Link href={href} {...props} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HoverCard>
|
||||||
|
<HoverCardTrigger asChild>
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
found.hash
|
||||||
|
? `${found.page.url}#${found.hash}`
|
||||||
|
: found.page.url
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</HoverCardTrigger>
|
||||||
|
<HoverCardContent className="text-sm">
|
||||||
|
<p className="font-medium">{found.page.data.title}</p>
|
||||||
|
<p className="text-fd-muted-foreground">
|
||||||
|
{found.page.data.description}
|
||||||
|
</p>
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCard>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Popup,
|
||||||
|
PopupContent,
|
||||||
|
PopupTrigger,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
TypeTable,
|
||||||
|
AutoTypeTable: (props) => (
|
||||||
|
<AutoTypeTable generator={generator} {...props} />
|
||||||
|
),
|
||||||
|
Accordion,
|
||||||
|
Accordions,
|
||||||
|
Wrapper,
|
||||||
|
File,
|
||||||
|
Folder,
|
||||||
|
Files,
|
||||||
|
blockquote: Callout as unknown as FC<ComponentProps<'blockquote'>>,
|
||||||
|
UiOverview,
|
||||||
|
|
||||||
|
...(await import('@/content/docs/components/tabs.client')),
|
||||||
|
...(await import('@/content/docs/theme.client')),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DocsBody>
|
</DocsBody>
|
||||||
|
@ -32,13 +32,13 @@ interface DocsLayoutProps {
|
|||||||
* https://fumadocs.vercel.app/docs/ui/navigation/links
|
* https://fumadocs.vercel.app/docs/ui/navigation/links
|
||||||
* https://fumadocs.vercel.app/docs/ui/navigation/sidebar
|
* https://fumadocs.vercel.app/docs/ui/navigation/sidebar
|
||||||
*
|
*
|
||||||
* example:
|
* ref:
|
||||||
* https://github.com/fuma-nama/fumadocs/blob/dev/apps/docs/app/layout.config.tsx
|
* https://github.com/fuma-nama/fumadocs/blob/dev/apps/docs/app/layout.config.tsx
|
||||||
*
|
*
|
||||||
* 2. Organizing Pages
|
* 2. Organizing Pages
|
||||||
* https://fumadocs.vercel.app/docs/ui/page-conventions
|
* https://fumadocs.vercel.app/docs/ui/page-conventions
|
||||||
*
|
*
|
||||||
* example:
|
* ref:
|
||||||
* https://github.com/fuma-nama/fumadocs/blob/dev/apps/docs/content/docs/ui/meta.json
|
* https://github.com/fuma-nama/fumadocs/blob/dev/apps/docs/content/docs/ui/meta.json
|
||||||
*/
|
*/
|
||||||
export default async function DocsRootLayout({ children, params }: DocsLayoutProps) {
|
export default async function DocsRootLayout({ children, params }: DocsLayoutProps) {
|
||||||
|
34
src/components/docs/preview/dynamic-codeblock.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { bundledLanguages } from 'shiki';
|
||||||
|
|
||||||
|
export default function Example() {
|
||||||
|
const [lang, setLang] = useState('js');
|
||||||
|
const [code, setCode] = useState('console.log("This is pre-rendered")');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="prose flex flex-col gap-4 rounded-lg bg-fd-background p-4">
|
||||||
|
<div className="not-prose flex flex-col rounded-lg bg-fd-secondary text-fd-secondary-foreground">
|
||||||
|
<select
|
||||||
|
value={lang}
|
||||||
|
onChange={(e) => setLang(e.target.value)}
|
||||||
|
className="w-fit bg-transparent px-4 py-2 text-sm focus-visible:outline-none"
|
||||||
|
>
|
||||||
|
{Object.keys(bundledLanguages).map((lang) => (
|
||||||
|
<option value={lang} key={lang}>
|
||||||
|
{lang}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<textarea
|
||||||
|
value={code}
|
||||||
|
onChange={(e) => setCode(e.target.value)}
|
||||||
|
className="bg-transparent px-4 py-2 text-sm focus-visible:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DynamicCodeBlock lang={lang} code={code} options={{}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
271
src/components/docs/preview/index.tsx
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
import {
|
||||||
|
Banner,
|
||||||
|
DynamicCodeBlock,
|
||||||
|
File,
|
||||||
|
Files,
|
||||||
|
Folder,
|
||||||
|
ImageZoom,
|
||||||
|
InlineTOC,
|
||||||
|
} from '@/components/docs/preview/lazy';
|
||||||
|
import BannerImage from '@/public/images/docs/banner.png';
|
||||||
|
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
|
||||||
|
import { Callout } from 'fumadocs-ui/components/callout';
|
||||||
|
import { Card } from 'fumadocs-ui/components/card';
|
||||||
|
import { Heading } from 'fumadocs-ui/components/heading';
|
||||||
|
import { RootToggle } from 'fumadocs-ui/components/layout/root-toggle';
|
||||||
|
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||||
|
import { TypeTable } from 'fumadocs-ui/components/type-table';
|
||||||
|
import { Home } from 'lucide-react';
|
||||||
|
import { type ReactNode } from 'react';
|
||||||
|
import { Wrapper } from './wrapper';
|
||||||
|
// import { owner, repo } from '@/lib/github';
|
||||||
|
// import { GithubInfo } from 'fumadocs-ui/components/github-info';
|
||||||
|
|
||||||
|
export function heading(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<div className="rounded-lg bg-fd-background p-4 prose-no-margin">
|
||||||
|
<Heading id="preview" as="h3">
|
||||||
|
Hello World
|
||||||
|
</Heading>
|
||||||
|
<Heading id="preview" as="h3">
|
||||||
|
Hello <code>World</code> Everyone!
|
||||||
|
</Heading>
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function card(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<div className="rounded-lg bg-fd-background">
|
||||||
|
<Card
|
||||||
|
href="#"
|
||||||
|
icon={<Home />}
|
||||||
|
title="Hello World"
|
||||||
|
description="Learn More about Caching and Revalidation"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tabs(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<div className="space-y-4 rounded-xl bg-fd-background p-4 text-sm">
|
||||||
|
<Tabs
|
||||||
|
groupId="language"
|
||||||
|
persist
|
||||||
|
items={['Javascript', 'Rust', 'Typescript']}
|
||||||
|
>
|
||||||
|
<Tab value="Javascript">Hello World in Javascript</Tab>
|
||||||
|
<Tab value="Rust">Hello World in Rust</Tab>
|
||||||
|
<Tab value="Typescript">Also works if items are not the same</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<Tabs groupId="language" persist items={['Javascript', 'Rust']}>
|
||||||
|
<Tab value="Javascript">
|
||||||
|
Value is shared! Try refresh and see if the value is persisted
|
||||||
|
</Tab>
|
||||||
|
<Tab value="Rust">
|
||||||
|
Value is shared! Try refresh and see if the value is persisted
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function typeTable(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<div className="rounded-xl bg-fd-background">
|
||||||
|
<TypeTable
|
||||||
|
type={{
|
||||||
|
percentage: {
|
||||||
|
description:
|
||||||
|
'The percentage of scroll position to display the roll button',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.2',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function zoomImage(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<ImageZoom
|
||||||
|
alt="banner"
|
||||||
|
src={BannerImage}
|
||||||
|
className="!my-0 rounded-xl bg-fd-background"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function accordion(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Accordions type="single" collapsible>
|
||||||
|
<Accordion id="what-is-fumadocs" title="What is Fumadocs?">
|
||||||
|
A framework for building documentations
|
||||||
|
</Accordion>
|
||||||
|
<Accordion id="ux" title="What do we love?">
|
||||||
|
We love websites with a good user experience
|
||||||
|
</Accordion>
|
||||||
|
</Accordions>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function callout(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Callout title="Title">Hello World</Callout>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function files(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Files>
|
||||||
|
<Folder name="app" defaultOpen>
|
||||||
|
<Folder name="[id]" defaultOpen>
|
||||||
|
<File name="page.tsx" />
|
||||||
|
</Folder>
|
||||||
|
<File name="layout.tsx" />
|
||||||
|
<File name="page.tsx" />
|
||||||
|
<File name="global.css" />
|
||||||
|
</Folder>
|
||||||
|
<Folder name="components">
|
||||||
|
<File name="button.tsx" />
|
||||||
|
<File name="tabs.tsx" />
|
||||||
|
<File name="dialog.tsx" />
|
||||||
|
<Folder name="empty" />
|
||||||
|
</Folder>
|
||||||
|
<File name="package.json" />
|
||||||
|
</Files>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inlineTOC(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<InlineTOC
|
||||||
|
items={[
|
||||||
|
{ title: 'Welcome', url: '#welcome', depth: 2 },
|
||||||
|
{ title: 'Getting Started', url: '#getting-started', depth: 3 },
|
||||||
|
{ title: 'Usage', url: '#usage', depth: 3 },
|
||||||
|
{ title: 'Styling', url: '#styling', depth: 3 },
|
||||||
|
{ title: 'Reference', url: '#reference', depth: 2 },
|
||||||
|
{ title: 'Components', url: '#components', depth: 3 },
|
||||||
|
{ title: 'APIs', url: '#api', depth: 3 },
|
||||||
|
{ title: 'Credits', url: '#credits', depth: 2 },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function steps(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<div className="rounded-xl bg-fd-background p-3">
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
<h4>Buy Coffee</h4>
|
||||||
|
<p>Some text here</p>
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
<h4>Go to Office Some text here</h4>
|
||||||
|
<p>Some text here</p>
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
<h4>Have a meeting Some text here</h4>
|
||||||
|
<p>Some text here</p>
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rootToggle(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<div className="not-prose mx-auto grid max-w-[240px] rounded-lg bg-fd-background">
|
||||||
|
<RootToggle
|
||||||
|
className="p-3"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
title: 'Hello World',
|
||||||
|
description: 'The example item of root toggle',
|
||||||
|
url: '/docs/ui',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Other page',
|
||||||
|
description: 'The example item of root toggle',
|
||||||
|
url: '/docs/headless',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dynamicCodeBlock() {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<DynamicCodeBlock />
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function banner(): ReactNode {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Banner className="z-0" changeLayout={false}>
|
||||||
|
Be careful, Fumadocs v99 has released
|
||||||
|
</Banner>
|
||||||
|
|
||||||
|
<Banner
|
||||||
|
className="z-0"
|
||||||
|
id="test-rainbow"
|
||||||
|
variant="rainbow"
|
||||||
|
changeLayout={false}
|
||||||
|
>
|
||||||
|
Using the <code>rainbow</code> variant
|
||||||
|
</Banner>
|
||||||
|
|
||||||
|
<Banner className="z-0" id="test" changeLayout={false}>
|
||||||
|
Be careful, this banner can be closed
|
||||||
|
</Banner>
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function githubInfo() {
|
||||||
|
// return (
|
||||||
|
// <Wrapper>
|
||||||
|
// <GithubInfo
|
||||||
|
// owner={owner}
|
||||||
|
// repo={repo}
|
||||||
|
// token={process.env.GITHUB_TOKEN}
|
||||||
|
// className="not-prose bg-fd-card"
|
||||||
|
// />
|
||||||
|
// </Wrapper>
|
||||||
|
// );
|
||||||
|
// }
|
25
src/components/docs/preview/lazy.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
export const DynamicCodeBlock = dynamic(() => import('./dynamic-codeblock'));
|
||||||
|
export const Banner = dynamic(() =>
|
||||||
|
import('fumadocs-ui/components/banner').then((res) => res.Banner),
|
||||||
|
);
|
||||||
|
export const InlineTOC = dynamic(() =>
|
||||||
|
import('fumadocs-ui/components/inline-toc').then((res) => res.InlineTOC),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const File = dynamic(() =>
|
||||||
|
import('fumadocs-ui/components/files').then((res) => res.File),
|
||||||
|
);
|
||||||
|
export const Files = dynamic(() =>
|
||||||
|
import('fumadocs-ui/components/files').then((res) => res.Files),
|
||||||
|
);
|
||||||
|
export const Folder = dynamic(() =>
|
||||||
|
import('fumadocs-ui/components/files').then((res) => res.Folder),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ImageZoom = dynamic(() =>
|
||||||
|
import('fumadocs-ui/components/image-zoom').then((res) => res.ImageZoom),
|
||||||
|
);
|
16
src/components/docs/preview/wrapper.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import type { HTMLAttributes } from 'react';
|
||||||
|
|
||||||
|
export function Wrapper(props: HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
'rounded-lg bg-black/20 p-4 border prose-no-margin',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
111
src/components/docs/ui-overview.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||||
|
|
||||||
|
export function UiOverview() {
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
items={['Docs Layout', 'Docs Layout (Mobile)']}
|
||||||
|
className="bg-fd-background"
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
value="Docs Layout"
|
||||||
|
className="not-prose text-sm text-center text-fd-muted-foreground overflow-auto"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row gap-4 min-w-[600px] min-h-[400px]">
|
||||||
|
<div className="flex flex-col gap-2 w-1/4">
|
||||||
|
<p className="text-xs">Sidebar</p>
|
||||||
|
<div className="border p-2 bg-fd-muted">Title</div>
|
||||||
|
<div className="border p-2 bg-fd-muted">Sidebar Tabs</div>
|
||||||
|
<div className="border p-2 bg-fd-muted">Search</div>
|
||||||
|
<div className="flex items-center justify-center border p-2 flex-1 bg-fd-muted">
|
||||||
|
Page Tree
|
||||||
|
</div>
|
||||||
|
<div className="border p-2 mt-auto bg-fd-muted">Footer</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 flex-1">
|
||||||
|
<p className="text-xs">Docs Page</p>
|
||||||
|
<div className="bg-fd-muted border p-2">Article Title</div>
|
||||||
|
<div className="bg-fd-muted border p-2">Description</div>
|
||||||
|
<div className="bg-fd-muted border flex items-center justify-center flex-1 py-2">
|
||||||
|
Body
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="border p-2 bg-fd-muted">Edit on GitHub</div>
|
||||||
|
<div className="border p-2 bg-fd-muted">Last Updated</div>
|
||||||
|
</div>
|
||||||
|
<div className="border p-2">
|
||||||
|
<p className="text-xs mb-2">Footer</p>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="border p-2 bg-fd-muted">Previous</div>
|
||||||
|
<div className="border p-2 bg-fd-muted">Next</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 w-1/4">
|
||||||
|
<p className="text-xs">TOC</p>
|
||||||
|
<div className="border p-2 bg-fd-muted">Banner</div>
|
||||||
|
<div className="flex items-center justify-center border p-2 flex-1 bg-fd-muted">
|
||||||
|
Items
|
||||||
|
</div>
|
||||||
|
<div className="border p-2 bg-fd-muted">Footer</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
value="Docs Layout (Mobile)"
|
||||||
|
className="not-prose text-sm text-center text-fd-muted-foreground overflow-auto"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row gap-4 min-w-[600px]">
|
||||||
|
<div className="flex flex-col gap-2 p-2 border">
|
||||||
|
<div className="border p-2">
|
||||||
|
<p className="text-xs">Nav</p>
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
<div className="bg-fd-muted border p-2">Title</div>
|
||||||
|
<div className="bg-fd-muted border p-2">Search</div>
|
||||||
|
<div className="bg-fd-muted border p-2">Menu</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-fd-muted border p-2">TOC Trigger</div>
|
||||||
|
|
||||||
|
<p className="text-xs">Docs Page</p>
|
||||||
|
<div className="bg-fd-muted border p-2">Article Title</div>
|
||||||
|
<div className="bg-fd-muted border p-2">Description</div>
|
||||||
|
<div className="bg-fd-muted border flex items-center justify-center flex-1 py-2">
|
||||||
|
Body
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="border p-2 bg-fd-muted">Edit on GitHub</div>
|
||||||
|
<div className="border p-2 bg-fd-muted">Last Updated</div>
|
||||||
|
</div>
|
||||||
|
<div className="border p-2">
|
||||||
|
<p className="text-xs mb-2">Footer</p>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="border p-2 bg-fd-muted">Previous</div>
|
||||||
|
<div className="border p-2 bg-fd-muted">Next</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 p-2 border">
|
||||||
|
<div className="border p-2">
|
||||||
|
<p className="text-xs">Nav</p>
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
<div className="bg-fd-muted border p-2">Title</div>
|
||||||
|
<div className="bg-fd-muted border p-2">Search</div>
|
||||||
|
<div className="bg-fd-muted border p-2">Menu</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 p-2 border h-full">
|
||||||
|
<p className="text-xs">Sidebar</p>
|
||||||
|
<div className="border p-2 bg-fd-muted">Sidebar Tabs</div>
|
||||||
|
<div className="border p-2 bg-fd-muted">Search</div>
|
||||||
|
<div className="flex items-center justify-center border p-2 flex-1 bg-fd-muted">
|
||||||
|
Page Tree
|
||||||
|
</div>
|
||||||
|
<div className="border p-2 mt-auto bg-fd-muted">Footer</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,7 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
/* Fumadocs Themes */
|
||||||
|
/* https://fumadocs.vercel.app/docs/ui/theme#themes */
|
||||||
@import 'fumadocs-ui/css/neutral.css';
|
@import 'fumadocs-ui/css/neutral.css';
|
||||||
@import 'fumadocs-ui/css/preset.css';
|
@import 'fumadocs-ui/css/preset.css';
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
|
"@/content/*": ["./content/*"],
|
||||||
|
"@/public/*": ["./public/*"],
|
||||||
"content-collections": ["./.content-collections/generated"]
|
"content-collections": ["./.content-collections/generated"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|