chore: merge upstream fumadocs (cee687f2e)

- Merged latest changes from fuma-nama/fumadocs upstream
- Rebranded all documentation content from Fumadocs to Hanzo Docs
- Updated package references to @hanzo/docs-* namespace
- Renamed what-is-fumadocs.mdx to what-is-hanzo-docs.mdx
- Kept Hanzo package versions and changelogs

Upstream changes include:
- Python: fix bundle size
- Core: Support <auto-files /> syntax
- OpenAPI: Fix Parameter Serialization
- Obsidian: fix slugification
- UI: improve details
- Docs: introduce type-safe slugs option
This commit is contained in:
2026-01-02 05:31:26 -08:00
83 changed files with 1218 additions and 757 deletions
+2 -1
View File
@@ -13,5 +13,6 @@
},
"prettier.enable": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "oxc.oxc-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode",
"oxc.linter.enabled": false
}
+1 -1
View File
@@ -127,7 +127,7 @@ export default async function Page(props: PageProps<'/docs/[[...slug]]'>) {
function DocsCategory({ url }: { url: string }) {
return (
<Cards>
{findSiblings(source.pageTree, url).map((item) => {
{findSiblings(source.getPageTree(), url).map((item) => {
if (item.type === 'separator') return;
if (item.type === 'folder') {
if (!item.index) return;
+1 -1
View File
@@ -11,7 +11,7 @@ export default function Layout({ children }: LayoutProps<'/docs'>) {
return (
<DocsLayout
{...base}
tree={source.pageTree}
tree={source.getPageTree()}
// just icon items
links={linkItems.filter((item) => item.type === 'icon')}
nav={{
+1 -1
View File
@@ -40,7 +40,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
<html lang="en" className={`${geist.variable} ${mono.variable}`} suppressHydrationWarning>
<Body>
<NextProvider>
<TreeContextProvider tree={source.pageTree}>
<TreeContextProvider tree={source.getPageTree()}>
<Provider>{children}</Provider>
</TreeContextProvider>
</NextProvider>
+1 -1
View File
@@ -13,7 +13,7 @@ export async function GET() {
for (const page of pages) {
if (page.data.type === 'openapi') continue;
const items = getBreadcrumbItems(page.url, source.pageTree, {
const items = getBreadcrumbItems(page.url, source.getPageTree(), {
includePage: false,
includeRoot: true,
});
+1 -1
View File
@@ -155,4 +155,4 @@ export const updatedBlog = blog.map(...)
- remove Manifest API - it was designed to export search indexes, but now it's recommended to implement using route handlers.
- Mention more about our [MDX Remote](/docs/headless/custom-source#mdx-remote) package, it will be the primary solution to handle large docs sites with performance needs that bundlers couldn't do, including lazy compilation of MDX files.
- Mention more about our [MDX Remote](/docs/integrations/content/mdx-remote) package, it will be the primary solution to handle large docs sites with performance needs that bundlers couldn't do, including lazy compilation of MDX files.
+9 -12
View File
@@ -152,7 +152,7 @@ export const source = loader({
</Accordion>
<Accordion id='multi-docs' title="How to implement multi-docs?">
We recommend to use [Sidebar Tabs](/docs/navigation/sidebar#sidebar-tabs).
We recommend to use [Sidebar Tabs](/docs/ui/layouts/docs#sidebar-tabs).
</Accordion>
</Accordions>
@@ -176,6 +176,7 @@ export default defineConfig({
'@hanzo/docs-ui',
'@hanzo/docs-openapi',
'@hanzo/docs-base-ui',
'@hanzo/docs-ui',
],
},
});
@@ -189,7 +190,7 @@ export default defineConfig({
<Accordions>
<Accordion id='node-23' title="Getting error on production build?">
Node.js 23.1 might have problems with Next.js, see [#1021](https://github.com/fuma-nama/hanzo-docs/issues/1021). Make sure to change your Node.js version.
Node.js 23.1 might have problems with Next.js, see [#1021](https://github.com/hanzoai/docs/issues/1021). Make sure to change your Node.js version.
</Accordion>
<Accordion id='dynamic-route' title="It uses Dynamic Route, will it be poor in performance?">
@@ -200,7 +201,7 @@ export default defineConfig({
You can [enable Static Exports](/docs/deploying/static) on Next.js to get a static build output.
</Accordion>
<Accordion id='webpack' title='Reading from "hanzo-docs-mdx:collections/server" is not handled by plugins'>
<Accordion id='webpack' title='Reading from "@hanzo/docs-mdx:collections/server" is not handled by plugins'>
Webpack resolves import namespace **before** your import aliases in `tsconfig.json`. When using Hanzo Docs MDX, you have to rename your import alias to `collections/*` instead.
@@ -219,7 +220,7 @@ Make sure to update the references as well.
```ts title="lib/source.ts"
// [!code --]
import { docs } from 'hanzo-docs-mdx:collections/server';
import { docs } from '@hanzo/docs-mdx:collections/server';
// [!code ++]
import { docs } from 'collections/server';
```
@@ -236,7 +237,7 @@ import { docs } from 'collections/server';
<Files>
<File name="(home)/docs/test/page.tsx" />
<Folder name="docs">
<Folder name="docs" defaultOpen>
<File name="layout.tsx" />
<File name="[[...slug]]/page.tsx" />
</Folder>
@@ -260,7 +261,7 @@ import { docs } from 'collections/server';
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/hanzo-docs/discussions)!
If you find anything confusing, please give your feedback on [Github Discussion](https://github.com/hanzoai/docs/discussions)!
### Writing Content
@@ -271,7 +272,7 @@ For authoring docs, make sure to read:
Hanzo Docs has some additional features for authoring content.
</Card>
<Card href="/docs/navigation" title="Navigation">
Learn how to customise navigation links and sidebar items.
Learn how to customise navigation structure.
</Card>
<Card href="/docs/page-conventions" title="Page Slugs & Page Tree">
Learn how to organise content.
@@ -297,9 +298,5 @@ For authoring docs, make sure to read:
description="Learn how to enable i18n"
/>
<Card href="/docs/ui/theme" title="Color Themes" description="Add themes to Hanzo Docs UI" />
<Card
href="/docs/ui/layouts/docs"
title="Layouts"
description="Customise your Hanzo Docs UI layouts"
/>
<Card href="/docs/ui/layouts" title="Layouts" description="Customise your Hanzo Docs UI layouts" />
</Cards>
@@ -1,23 +1,10 @@
---
title: Custom Source
description: Build your own content source
title: Build Your Own
description: Build your own content source for Hanzo Docs.
---
## Introduction
**Hanzo Docs is very flexible.** You can integrate with any content source, even without an official adapter.
### Examples
You can see examples to use Hanzo Docs with a CMS, which allows a nice experience on publishing content, and real-time update without re-building the app.
- [BaseHub](https://github.com/fuma-nama/hanzo-docs-basehub)
- [Sanity](https://github.com/fuma-nama/hanzo-docs-sanity)
- [Community: Payload CMS by `MFarabi619`](https://github.com/MFarabi619/hanzo-docs-payloadcms)
- [Community: Payload CMS by `bapspatil`](https://github.com/bapspatil/hanzo-docs-payload-template)
## Building Content Source
For a custom content source implementation, there's two ways:
### Using Loader API
@@ -38,7 +25,7 @@ See [Definitions of Page Tree](/docs/headless/page-tree).
Pass your page tree to `DocsLayout` (usually in a `layout.tsx`):
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
@@ -70,7 +57,7 @@ For docs page, it's the same logic:
You may need table of contents, which can be generated on your own, or using the [`getTableOfContents`](/docs/headless/utils/get-toc) utility (Markdown/MDX only).
```tsx
import { DocsPage, DocsBody } from '@hanzo/docs/ui/layouts/docs/page';
import { DocsPage, DocsBody } from '@hanzo/docs-ui/layouts/docs/page';
import { getPage } from './my-content-source';
import { notFound } from 'next/navigation';
@@ -123,56 +110,3 @@ Otherwise, you will have to bring your own implementation.
We recommend 3rd party solutions like Orama or Algolia Search. They are more flexible than the built-in Search API, and is easier to integrate with remote sources.
Hanzo Docs offers a variant of [Search Integrations](/docs/headless/search).
## MDX Remote
Hanzo Docs offers the **MDX Remote** package, it is a helper to integrate Markdown-based content sources with Hanzo Docs.
You can think it as a `next-mdx-remote` with built-in plugins for Hanzo Docs.
### Setup
```package-install
@hanzo/docs-mdx-remote
```
The main feature it offers is the MDX Compiler, it can compile MDX content to JSX nodes.
Since it doesn't use a bundler, there's some limitations:
- No imports and exports in MDX files.
It's compatible with Server Components. For example:
```tsx
import { createCompiler } from '@hanzo/docs-mdx-remote';
import { getPage } from './my-content-source';
import { DocsBody, DocsPage } from '@hanzo/docs/ui/layouts/docs/page';
import { getMDXComponents } from '@/mdx-components';
const compiler = createCompiler({
// options
});
export default async function Page({ params }: { params: { slug?: string[] } }) {
const page = getPage(params.slug);
const compiled = await compiler.compile({
source: page.content,
});
const MdxContent = compiled.body;
return (
<DocsPage toc={compiled.toc}>
<DocsBody>
<MdxContent components={getMDXComponents()} />
</DocsBody>
</DocsPage>
);
}
```
### Images
On serverless platforms like Vercel, the original `public` folder (including static assets like images) will be removed after production build.
The MDX compiler might no longer be able to access local images in `public`.
When referencing images, make sure to use a URL.
@@ -0,0 +1,19 @@
---
title: Content Source
description: Integrate different content sources with Hanzo Docs.
---
## Introduction
**Hanzo Docs is very flexible.** You can integrate with any content source, even without an official adapter.
### Examples
You can see examples to use Hanzo Docs with a CMS, which allows a nice experience on publishing content, and real-time update without re-building the app.
- [BaseHub](https://github.com/hanzoai/docs-basehub)
- [Sanity](https://github.com/hanzoai/docs-sanity)
- [Community: Payload CMS by `MFarabi619`](https://github.com/MFarabi619/fumadocs-payloadcms)
- [Community: Payload CMS by `bapspatil`](https://github.com/bapspatil/fumadocs-payload-template)
Not enough? [Build your own content source](/docs/integrations/content/custom).
@@ -0,0 +1,63 @@
---
title: MDX Remote
description: Compiled Markdown/MDX content on demand.
---
## Introduction
Hanzo Docs offers the **MDX Remote** package, it is a helper to integrate Markdown/MDX content sources with Hanzo Docs.
You can think it as a `next-mdx-remote` with built-in plugins for Hanzo Docs.
<Callout type='warn' title="Be Careful">
The Passed MDX content must be trusted as it allows code execution by default.
</Callout>
### Setup
```package-install
@hanzo/docs-mdx-remote
```
The main feature it offers is the MDX Compiler, it can compile MDX content to JSX nodes.
Since it doesn't use a bundler, there's some limitations:
- No imports and exports in MDX files.
It's compatible with Server Components. For example:
```tsx
import { createCompiler } from '@hanzo/docs-mdx-remote';
import { getPage } from './my-content-source';
import { DocsBody, DocsPage } from '@hanzo/docs-ui/layouts/docs/page';
import { getMDXComponents } from '@/mdx-components';
const compiler = createCompiler({
// options
});
export default async function Page({ params }: { params: { slug?: string[] } }) {
const page = getPage(params.slug);
const compiled = await compiler.compile({
source: page.content,
});
const MdxContent = compiled.body;
return (
<DocsPage toc={compiled.toc}>
<DocsBody>
<MdxContent components={getMDXComponents()} />
</DocsBody>
</DocsPage>
);
}
```
### Images
On serverless platforms like Vercel, the original `public` folder (including static assets like images) will be removed after production build.
The MDX compiler might no longer be able to access local images in `public`.
When referencing images, make sure to use a URL.
@@ -171,7 +171,7 @@ export async function loader({ params }: Route.LoaderArgs) {
return {
path: page.path,
// [!code --]
tree: source.pageTree,
tree: source.getPageTree(),
// [!code ++]
tree: source.getPageTree(params.lang),
};
@@ -121,7 +121,7 @@ import { getPageTreePeers } from '@hanzo/docs/core/page-tree';
import { source } from '@/lib/source';
<Cards>
{getPageTreePeers(source.pageTree, '/docs/my-page').map((peer) => (
{getPageTreePeers(source.getPageTree(), '/docs/my-page').map((peer) => (
<Card key={peer.url} title={peer.name} href={peer.url}>
{peer.description}
</Card>
+1 -1
View File
@@ -14,10 +14,10 @@
"page-conventions",
"markdown",
"---Configurations---",
"navigation",
"versioning",
"deploying",
"internationalization",
"navigation",
"search",
"---Integrations---",
"...integrations"
@@ -0,0 +1,28 @@
---
title: Navigation
description: Configure navigation in your Hanzo Docs app.
---
import { LinkIcon, PanelLeftDashed } from 'lucide-react';
**Hanzo Docs UI** provides different layouts to display content, you can specify navigation configurations via layouts.
<Cards>
<Card icon={<LinkIcon />} title="Layout Links" href="/docs/ui/layouts/links">
Display **navigation links** in your layouts.
It is useful for linking to frequently used resources like showcase and pricing pages.
</Card>
<Card icon={<PanelLeftDashed />} title="Sidebar Items" href="/docs/ui/layouts/docs#sidebar-items">
Sidebar renders links to all documentation pages.
Under the hood, **Page Tree** is used to represent the navigation structure.
</Card>
</Cards>
@@ -1,5 +0,0 @@
---
title: Navigation
description: Configure navigation in your Hanzo Docs app.
index: true
---
@@ -1,150 +0,0 @@
---
title: Sidebar Links
description: Customise sidebar navigation links on Docs Layout.
---
## Overview
<div className='flex justify-center items-center *:max-w-[200px] bg-gradient-to-br from-fd-primary/10 rounded-xl border'>
![Sidebar](/docs/sidebar.png)
</div>
Sidebar items are rendered from the page tree you passed to `<DocsLayout />`.
For `source.pageTree`, it generates the tree from your file structure, see [available patterns](/docs/page-conventions).
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
tree={source.pageTree}
// other props
>
{children}
</DocsLayout>
);
}
```
You may hardcode it too:
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
tree={{
name: 'docs',
children: [],
}}
// other props
>
{children}
</DocsLayout>
);
}
```
### Sidebar Tabs (Dropdown) [#sidebar-tabs]
Sidebar Tabs are folders with tab-like behaviours, only the content of opened tab will be visible.
<div className='flex justify-center items-center *:max-w-[360px] bg-linear-to-br from-fd-primary/10 rounded-xl border'>
![Sidebar Tabs](/docs/sidebar-tabs.png)
</div>
By default, the tab trigger will be displayed as a **Dropdown** component (hidden unless one of its items is active).
You can add items by marking folders as [Root Folders](/docs/page-conventions#root-folder), create a `meta.json` file in the folder:
```json title="content/docs/my-folder/meta.json"
{
"title": "Name of Folder",
"description": "The description of root folder (optional)",
"root": true
}
```
Or specify them explicitly:
```tsx title="/app/docs/layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
<DocsLayout
sidebar={{
tabs: [
{
title: 'Components',
description: 'Hello World!',
// active for `/docs/components` and sub routes like `/docs/components/button`
url: '/docs/components',
// optionally, you can specify a set of urls which activates the item
// urls: new Set(['/docs/test', '/docs/components']),
},
],
}}
/>;
```
Set it to `false` to disable:
```tsx
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
<DocsLayout sidebar={{ tabs: false }} />;
```
<Callout title="Want further customisations?">
You can specify a `banner` to the [Docs Layout](/docs/ui/layouts/docs) component.
```tsx
import { DocsLayout, type DocsLayoutProps } from '@hanzo/docs/ui/layouts/docs';
import type { ReactNode } from 'react';
import { baseOptions } from '@/lib/layout.shared';
import { source } from '@/lib/source';
const docsOptions: DocsLayoutProps = {
...baseOptions(),
tree: source.pageTree,
sidebar: {
banner: <div>Hello World</div>,
},
};
export default function Layout({ children }: { children: ReactNode }) {
return <DocsLayout {...docsOptions}>{children}</DocsLayout>;
}
```
</Callout>
#### Decoration
Change the icon/styles of tabs.
```tsx
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
<DocsLayout
sidebar={{
tabs: {
transform: (option, node) => ({
...option,
icon: <MyIcon />,
}),
},
}}
/>;
```
@@ -27,8 +27,8 @@ For example:
</Files>
<Callout title="Good to Know">
You may want to group them with tabs rather than folders [using Sidebar
Tabs](/docs/navigation/sidebar#sidebar-tabs).
When grouping with folders, you can display them as tabs [using Sidebar
Tabs](/docs/ui/layouts/docs#sidebar-tabs).
</Callout>
## Full Versioning
@@ -4,10 +4,12 @@ description: Introducing Hanzo Docs, a docs framework that you can break.
icon: CircleQuestionMark
---
Hanzo Docs was created because the creator Fuma wanted a more customisable experience for building docs, to be a docs framework that is not opinionated, **a "framework" that you can break**.
The **Hanzo Docs** framework refers to a combination of UI + Core + Content Source, Hanzo Docs is more of a mental framework and each layer can be a library on its own.
## Philosophy
Hanzo Docs was created to bring a more customisable experience for building docs, to be a docs framework that is not opinionated, **a "framework" that you can break**.
### Less Abstraction
While most frameworks are configured with a configuration file, they usually lack flexibility when you hope to tune its details.
@@ -19,6 +19,8 @@ You can define folders and pages similar to file-system based routing.
</Folder>
</Files>
To customise its output further, you may use [Loader Plugins](/docs/headless/source-api/plugins).
## File
For [MDX](https://mdxjs.com) & Markdown file, you can customise page information from frontmatter.
@@ -98,7 +100,7 @@ For example, when you are opening root folder `framework`, the other folders (e.
<Callout title='Hanzo Docs UI'>
Hanzo Docs UI renders root folders as [Sidebar Tabs](/docs/navigation/sidebar#sidebar-tabs), which allows user to switch between them.
Hanzo Docs UI renders root folders as [Sidebar Tabs](/docs/ui/layouts/docs#sidebar-tabs), which allows user to switch between them.
</Callout>
@@ -171,7 +173,7 @@ You can add an [`icon` handler](/docs/headless/source-api#icons) to `loader()`.
You can define different style for i18n routing.
```ts title="lib/i18n.ts"
import type { I18nConfig } from '@hanzo/docs/core/i18n';
import type { I18nConfig } from '@hanzo/docs-core/i18n';
export const i18n: I18nConfig = {
// default
@@ -20,10 +20,10 @@ description: Turn content sources into a unified interface
## Usage
You can use it with built-in content sources like Hanzo Docs MDX.
You can use it with content sources like Hanzo Docs MDX, see [`source`](/docs/headless/source-api/source) for details.
```ts
import { loader } from '@hanzo/docs/core/source';
import { loader } from '@hanzo/docs-core/source';
import { docs } from '@hanzo/docs-mdx:collections/server';
export const source = loader({
@@ -37,7 +37,7 @@ export const source = loader({
You can override the base URL, or specify a function to generate URL for each page.
```ts
import { loader } from '@hanzo/docs/core/source';
import { loader } from '@hanzo/docs-core/source';
loader({
baseUrl: '/docs',
@@ -49,12 +49,28 @@ loader({
});
```
### Icons
### Slugs
Load the [icon](/docs/headless/page-conventions#icons) property specified by pages and meta files.
Define a custom function to generate slugs from page files.
```ts
import { loader } from '@hanzo/docs/core/source';
import { loader } from '@hanzo/docs-core/source';
loader({
slugs(file) {
console.log(file.path, file.data);
// or return `undefined` to fallback to default value
return ['my', 'slug'];
},
});
```
### Icons
Load the [icon](/docs/headless/page-conventions#icons) property specified by page and meta files.
```ts
import { loader } from '@hanzo/docs-core/source';
import { icons } from 'lucide-react';
import { createElement } from 'react';
@@ -76,7 +92,7 @@ Pass the `i18n` config to loader.
```ts title="lib/source.ts"
import { i18n } from '@/lib/i18n';
import { loader } from '@hanzo/docs/core/source';
import { loader } from '@hanzo/docs-core/source';
export const source = loader({
i18n, // [!code highlight]
@@ -124,7 +140,7 @@ source.getPages('locale');
import { source } from '@/lib/source';
// without i18n
source.pageTree;
source.getPageTree();
// with i18n
source.getPageTree('locale');
@@ -186,7 +202,7 @@ async function loader() {
```
```tsx tab="Client"
import { useHanzo DocsLoader } from '@hanzo/docs/core/source/client';
import { useHanzo DocsLoader } from '@hanzo/docs-core/source/client';
function MyComponent() {
// receives loader data
@@ -5,6 +5,8 @@ description: Common utilities to interact with page tree.
## Usage
See [Definitions](/docs/headless/page-tree) to learn more about Page Tree.
```ts
import {
findNeighbour,
+1 -1
View File
@@ -56,7 +56,7 @@ Create your custom content source with remoting content loading.
You can ensure your custom source doesn't need to pre-compile content files, it can process them on-demand with SSG which can **improve your build speed.**
However, you cannot leverage bulid time optimizations (e.g. bundling).
See [Custom Source](/docs/headless/custom-source) for implementing custom sources.
See [Custom Source](/docs/integrations/content/custom) for implementing custom sources.
### Lazy Loading
+22 -10
View File
@@ -11,7 +11,7 @@ preview: 'files'
Wrap file components in `Files`, you can use it in your MDX content, or as a normal React.js component.
```mdx title="content.mdx"
import { File, Folder, Files } from '@hanzo/docs/ui/components/files';
import { File, Folder, Files } from '@hanzo/docs-ui/components/files';
<Files>
<Folder name="app" defaultOpen>
@@ -28,13 +28,21 @@ import { File, Folder, Files } from '@hanzo/docs/ui/components/files';
</Files>
```
### As CodeBlock
### File
You can enable [`remark-mdx-files`](/docs/headless/mdx/remark-mdx-files) to define file structure with codeblocks.
<auto-type-table cwd path="content/docs/ui/props.ts" name="FileProps" />
### Folder
<auto-type-table cwd path="content/docs/ui/props.ts" name="FolderProps" />
## Remark Plugin
You can enable [`remark-mdx-files`](/docs/headless/mdx/remark-mdx-files) for additional feature & syntax.
```tsx title="source.config.ts (Hanzo Docs MDX)"
import { remarkMdxFiles } from '@hanzo/docs/core/mdx-plugins';
import { defineConfig } from '@hanzo/docs/mdx/config';
import { remarkMdxFiles } from '@hanzo/docs-core/mdx-plugins/remark-mdx-files';
import { defineConfig } from '@hanzo/docs-mdx/config';
export default defineConfig({
mdxOptions: {
@@ -44,7 +52,9 @@ export default defineConfig({
});
```
it will convert `files` codeblocks into MDX usage, like:
### CodeBlock Syntax
It will convert `files` codeblocks into `<Files />` component, like:
````md title="content.md"
```files
@@ -57,10 +67,12 @@ project
```
````
### File
### `<auto-files>`
<auto-type-table cwd path="content/docs/ui/props.ts" name="FileProps" />
Generate `<Files />` component from glob.
### Folder
```mdx title="content.mdx"
<auto-files dir="./my-dir" pattern="**/*.{ts,tsx}" />
<auto-type-table cwd path="content/docs/ui/props.ts" name="FolderProps" />
<auto-files dir="./my-dir" pattern="**/*.{ts,tsx}" defaultOpenAll />
```
@@ -34,7 +34,7 @@ import { GithubInfo } from '@hanzo/docs/ui/components/github-info';
function docsOptions(): DocsLayoutProps {
return {
...baseOptions(),
tree: source.pageTree,
tree: source.getPageTree(),
links: [
{
type: 'custom',
+2 -2
View File
@@ -4,9 +4,9 @@ description: The default theme of Hanzo Docs
icon: Album
---
Hanzo Docs UI offers a beautifully designed theme for documentation sites.
**Hanzo Docs UI** offers a beautifully designed theme for documentation sites.
It is bundled with many interactive components & layouts for docs sites, allowing low code maintenance cost while receiving regular UI improvements.
It is bundled with many interactive components & layouts for docs sites, allowing low maintenance cost of code while receiving regular UI improvements.
You can also use [Hanzo Docs CLI](/docs/cli) to install the components locally and have full control.
+155 -4
View File
@@ -27,7 +27,7 @@ export default function Layout({ children }: { children: ReactNode }) {
}
```
See [`baseOptions`](/docs/ui/layouts#shared-options) for available options to configure **navbar** and **layout links**.
See detailed docs for [`links`](/docs/ui/layouts/links) and [`nav`](/docs/ui/layouts/nav) options.
### References
@@ -38,8 +38,6 @@ See [`baseOptions`](/docs/ui/layouts#shared-options) for available options to co
## Sidebar
It display navigation links from your page tree, see [Sidebar Links](/docs/navigation/sidebar) to configure sidebar items.
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
@@ -50,7 +48,156 @@ import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
/>;
```
<auto-type-table cwd path="content/docs/ui/props.ts" name="SidebarProps" />
### Sidebar Items
Customise sidebar navigation links.
<div className='flex justify-center items-center *:max-w-[200px] bg-gradient-to-br from-fd-primary/10 rounded-xl border'>
![Sidebar](/docs/sidebar.png)
</div>
Sidebar items are rendered from the page tree you passed to `<DocsLayout />`.
For page tree from [`loader()`](/docs/headless/source-api), it generates the tree from your file structure, see [available patterns](/docs/page-conventions).
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
tree={source.getPageTree()}
// other props
>
{children}
</DocsLayout>
);
}
```
You may hardcode it too:
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
tree={{
name: 'docs',
children: [],
}}
// other props
>
{children}
</DocsLayout>
);
}
```
### Sidebar Tabs (Dropdown) [#sidebar-tabs]
Sidebar Tabs are folders with tab-like behaviours, only the content of opened tab will be visible.
<div className='flex justify-center items-center *:max-w-[360px] bg-linear-to-br from-fd-primary/10 rounded-xl border'>
![Sidebar Tabs](/docs/sidebar-tabs.png)
</div>
By default, the tab trigger will be displayed as a **Dropdown** component (hidden unless one of its items is active).
You can add items by marking folders as [Root Folders](/docs/page-conventions#root-folder), create a `meta.json` file in the folder:
```json title="content/docs/my-folder/meta.json"
{
"title": "Name of Folder",
"description": "The description of root folder (optional)",
"root": true
}
```
Or specify them explicitly:
```tsx title="/app/docs/layout.tsx"
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
<DocsLayout
sidebar={{
tabs: [
{
title: 'Components',
description: 'Hello World!',
// active for `/docs/components` and sub routes like `/docs/components/button`
url: '/docs/components',
// optionally, you can specify a set of urls which activates the item
// urls: new Set(['/docs/test', '/docs/components']),
},
],
}}
/>;
```
Set it to `false` to disable:
```tsx
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
<DocsLayout sidebar={{ tabs: false }} />;
```
<Callout title="Want further customisations?">
You can specify a `banner` to the [Docs Layout](/docs/ui/layouts/docs) component.
```tsx
import { DocsLayout, type DocsLayoutProps } from '@hanzo/docs-ui/layouts/docs';
import type { ReactNode } from 'react';
import { baseOptions } from '@/lib/layout.shared';
import { source } from '@/lib/source';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
{...baseOptions()}
tree={source.getPageTree()}
sidebar={{
// [!code ++]
banner: <div>Hello World</div>,
}}
>
{children}
</DocsLayout>
);
}
```
</Callout>
#### Decoration
Change the icon/styles of tabs.
```tsx
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
<DocsLayout
sidebar={{
tabs: {
transform: (option, node) => ({
...option,
icon: <MyIcon />,
}),
},
}}
/>;
```
### Sidebar Components
@@ -92,6 +239,10 @@ export function SidebarSeparator({ className, style, children, ...props }: Compo
}
```
### References
<auto-type-table cwd path="content/docs/ui/props.ts" name="SidebarProps" />
## Advanced
### Disable Prefetching
@@ -23,4 +23,4 @@ export default function Layout({ children }: { children: ReactNode }) {
}
```
We recommend to customise it from [`baseOptions`](/docs/ui/layouts#shared-options).
See detailed docs for [`links`](/docs/ui/layouts/links) and [`nav`](/docs/ui/layouts/nav) options.
+15 -133
View File
@@ -9,14 +9,14 @@ Hanzo Docs UI provides essential layouts to display content.
<DocsCategory />
## Shared Options
### Configurations
Each layout supports a shared set of options.
It is recommended to store these options into a file, and pass them to the layouts.
```tsx title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs-base-ui/layouts/shared';
```tsx tab="Shared Options" title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
@@ -27,80 +27,18 @@ export function baseOptions(): BaseLayoutProps {
}
```
### Links
<div className="w-full mx-auto max-w-[600px]">
![Nav](/docs/nav-layout-home.png)
</div>
You can display navigation links with the `links` option, see [Layout Links](/docs/navigation/links) for details.
### Nav
Options for navbar (header).
<auto-type-table
cwd path="content/docs/ui/props.ts"
type="Omit<NavbarProps, 'children'>"
/>
#### Transparent Mode
To make the navbar background transparent, you can configure transparent mode.
```tsx tab="All" title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs-base-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: 'My App',
// [!code ++]
transparentMode: 'top',
},
};
}
```
```tsx tab="Home Layout"
```tsx tab="Docs Layout" title="app/docs/layout.tsx"
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
import { baseOptions } from '@/lib/layout.shared';
import { HomeLayout } from '@hanzo/docs-base-ui/layouts/home';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
const base = baseOptions();
return (
<HomeLayout
{...base}
nav={{
...base.nav,
// [!code ++]
transparentMode: 'top',
}}
>
{children}
</HomeLayout>
);
}
```
```tsx tab="Docs Layout"
import { baseOptions } from '@/lib/layout.shared';
import { DocsLayout } from '@hanzo/docs-base-ui/layouts/docs';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
const base = baseOptions();
return (
<DocsLayout
{...base}
nav={{
...base.nav,
// [!code ++]
transparentMode: 'top',
}}
// [!code highlight]
{...baseOptions()}
tree={source.getPageTree()}
>
{children}
</DocsLayout>
@@ -108,44 +46,16 @@ export default function Layout({ children }: { children: ReactNode }) {
}
```
| 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 different layouts, set `nav.component` to your own component.
```tsx tab="All" title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs-base-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
nav: {
// [!code ++]
component: <CustomNavbar />,
},
};
}
```
```tsx tab="Home Layout"
```tsx tab="Home Layout" title="app/(home)/layout.tsx"
import { HomeLayout } from '@hanzo/docs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';
import { HomeLayout } from '@hanzo/docs-base-ui/layouts/home';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
const base = baseOptions();
return (
<HomeLayout
{...base}
nav={{
...base.nav,
// [!code ++]
component: <CustomNavbar />,
}}
// [!code highlight]
{...baseOptions()}
>
{children}
</HomeLayout>
@@ -153,34 +63,6 @@ export default function Layout({ children }: { children: ReactNode }) {
}
```
```tsx tab="Docs Layout"
import { baseOptions } from '@/lib/layout.shared';
import { DocsLayout } from '@hanzo/docs-base-ui/layouts/docs';
import type { ReactNode } from 'react';
See detailed docs for [`links`](/docs/ui/layouts/links) and [`nav`](/docs/ui/layouts/nav) options.
export default function Layout({ children }: { children: ReactNode }) {
const base = baseOptions();
return (
<DocsLayout
{...base}
nav={{
...base.nav,
// [!code ++]
component: <CustomNavbar />,
}}
>
{children}
</DocsLayout>
);
}
```
Hanzo Docs 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;
}
```
<auto-type-table cwd path="content/docs/ui/props.ts" name="BaseLayoutProps" />
@@ -1,11 +1,11 @@
---
title: Layout Links
description: Customise the shared navigation links on all layouts.
description: Navigation configurations on all layouts.
---
## Overview
Hanzo Docs allows adding additional links to your layouts with a `links` prop, like linking to your "showcase" page.
Hanzo Docs allows adding navigation links to your layouts with a `links` prop, like linking to your "showcase" page.
<div className="not-prose grid gap-2 *:border max-sm:*:last:hidden sm:grid-cols-[2fr_1fr]">
@@ -15,19 +15,18 @@ Hanzo Docs allows adding additional links to your layouts with a `links` prop, l
</div>
```tsx tab="Shared Options" title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs/ui/layouts/shared';
```tsx tab="All" title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
links: [], // [!code highlight]
// other options}
links: [], // [!code ++]
};
}
```
```tsx tab="Docs Layout" title="app/docs/layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
import { baseOptions } from '@/lib/layout.shared';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
@@ -36,7 +35,7 @@ export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
{...baseOptions()}
tree={source.pageTree}
tree={source.getPageTree()}
links={[]} // [!code highlight]
>
{children}
@@ -46,7 +45,7 @@ export default function Layout({ children }: { children: ReactNode }) {
```
```tsx tab="Home Layout" title="app/(home)/layout.tsx"
import { HomeLayout } from '@hanzo/docs/ui/layouts/home';
import { HomeLayout } from '@hanzo/docs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';
import type { ReactNode } from 'react';
@@ -70,7 +69,7 @@ A link to navigate to a URL/href, can be external.
```tsx title="lib/layout.shared.tsx"
import { BookIcon } from 'lucide-react';
import type { BaseLayoutProps } from '@hanzo/docs/ui/layouts/shared';
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
@@ -98,7 +97,7 @@ The conditions to be marked as active.
| `none` | Never be active |
```tsx title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs/ui/layouts/shared';
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
@@ -120,7 +119,7 @@ Icon items are secondary by default.
```tsx title="lib/layout.shared.tsx"
import { BookIcon } from 'lucide-react';
import type { BaseLayoutProps } from '@hanzo/docs/ui/layouts/shared';
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
@@ -142,7 +141,7 @@ export function baseOptions(): BaseLayoutProps {
Display a custom component.
```tsx title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs/ui/layouts/shared';
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
@@ -162,7 +161,7 @@ export function baseOptions(): BaseLayoutProps {
There's also a shortcut for adding GitHub repository link item.
```tsx twoslash title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs/ui/layouts/shared';
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
@@ -176,7 +175,7 @@ export function baseOptions(): BaseLayoutProps {
A menu containing multiple link items.
```tsx title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs/ui/layouts/shared';
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
@@ -206,13 +205,13 @@ In Home Layout, you can add navigation menu (fully animated) to the navbar.
```tsx title="app/(home)/layout.tsx"
import { baseOptions } from '@/lib/layout.shared';
import type { ReactNode } from 'react';
import { HomeLayout } from '@hanzo/docs/ui/layouts/home';
import { HomeLayout } from '@hanzo/docs-ui/layouts/home';
import {
NavbarMenu,
NavbarMenuContent,
NavbarMenuLink,
NavbarMenuTrigger,
} from '@hanzo/docs/ui/layouts/home/navbar';
} from '@hanzo/docs-ui/layouts/home/navbar';
export default function Layout({ children }: { children: ReactNode }) {
return (
@@ -0,0 +1,4 @@
{
"$schema": "../../../../.source/json-schema/docs.meta.json",
"pages": ["---Configurations---", "links", "nav", "---Components---", "..."]
}
+152
View File
@@ -0,0 +1,152 @@
---
title: Navbar
description: Navbar/header configurations.
---
## Configurations
Options for navbar (header).
<auto-type-table
cwd path="content/docs/ui/props.ts"
type="Omit<NavbarProps, 'children'>"
/>
### Transparent Mode
To make the navbar background transparent, you can configure transparent mode.
```tsx tab="All" title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: 'My App',
// [!code ++]
transparentMode: 'top',
},
};
}
```
```tsx tab="Home Layout"
import { baseOptions } from '@/lib/layout.shared';
import { HomeLayout } from '@hanzo/docs-ui/layouts/home';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
const base = baseOptions();
return (
<HomeLayout
{...base}
nav={{
...base.nav,
// [!code ++]
transparentMode: 'top',
}}
>
{children}
</HomeLayout>
);
}
```
```tsx tab="Docs Layout"
import { baseOptions } from '@/lib/layout.shared';
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
const base = baseOptions();
return (
<DocsLayout
{...base}
nav={{
...base.nav,
// [!code ++]
transparentMode: 'top',
}}
>
{children}
</DocsLayout>
);
}
```
| 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 different layouts, set `nav.component` to your own component.
```tsx tab="All" title="lib/layout.shared.tsx"
import type { BaseLayoutProps } from '@hanzo/docs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
nav: {
// [!code ++]
component: <CustomNavbar />,
},
};
}
```
```tsx tab="Home Layout"
import { baseOptions } from '@/lib/layout.shared';
import { HomeLayout } from '@hanzo/docs-ui/layouts/home';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
const base = baseOptions();
return (
<HomeLayout
{...base}
nav={{
...base.nav,
// [!code ++]
component: <CustomNavbar />,
}}
>
{children}
</HomeLayout>
);
}
```
```tsx tab="Docs Layout"
import { baseOptions } from '@/lib/layout.shared';
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
const base = baseOptions();
return (
<DocsLayout
{...base}
nav={{
...base.nav,
// [!code ++]
component: <CustomNavbar />,
}}
>
{children}
</DocsLayout>
);
}
```
Hanzo Docs 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;
}
```
+10 -8
View File
@@ -1,25 +1,27 @@
---
title: Notebook Layout
description: A more compact version of Docs Layout
description: A compact version of Docs Layout
---
A compact version of [`<DocsLayout />`](/docs/ui/layouts/docs).
![Notebook](/docs/notebook.png)
<Customisation />
## Usage
Enable the notebook layout with `@hanzo/docs-base-ui/layouts/notebook`, it's a more compact version of [`<DocsLayout />`](/docs/ui/layouts/docs).
Enable the notebook layout with `@hanzo/docs-ui/layouts/notebook`.
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/notebook'; // [!code highlight]
import { DocsLayout } from '@hanzo/docs-ui/layouts/notebook'; // [!code highlight]
import { baseOptions } from '@/lib/layout.shared';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout {...baseOptions()} tree={source.pageTree}>
<DocsLayout {...baseOptions()} tree={source.getPageTree()}>
{children}
</DocsLayout>
);
@@ -40,7 +42,7 @@ Configure the style of sidebar tabs.
![Notebook](/docs/notebook-tab-mode.png)
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/notebook';
import { DocsLayout } from '@hanzo/docs-ui/layouts/notebook';
import { baseOptions } from '@/lib/layout.shared';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
@@ -50,7 +52,7 @@ export default function Layout({ children }: { children: ReactNode }) {
<DocsLayout
{...baseOptions()}
tabMode="navbar" // [!code ++]
tree={source.pageTree}
tree={source.getPageTree()}
>
{children}
</DocsLayout>
@@ -65,7 +67,7 @@ Configure the style of navbar.
![Notebook](/docs/notebook-nav-mode.png)
```tsx title="layout.tsx"
import { DocsLayout } from '@hanzo/docs/ui/layouts/notebook';
import { DocsLayout } from '@hanzo/docs-ui/layouts/notebook';
import { baseOptions } from '@/lib/layout.shared';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
@@ -77,7 +79,7 @@ export default function Layout({ children }: { children: ReactNode }) {
<DocsLayout
{...base}
nav={{ ...nav, mode: 'top' }} // [!code ++]
tree={source.pageTree}
tree={source.getPageTree()}
>
{children}
</DocsLayout>
+2
View File
@@ -73,3 +73,5 @@ export type FooterProps = Omit<NonNullable<DocsPageProps['footer']>, keyof Compo
export type AutoTypeTableProps = ComponentPropsWithoutRef<typeof AutoTypeTable>;
export type SearchProps = Required<RootProviderProps>['search'];
export type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
+11 -11
View File
@@ -1,19 +1,19 @@
---
import { render, type CollectionEntry } from 'astro:content';
import { Docs } from '@/components/docs';
import defaultMdxComponents from '@hanzo/docs/ui/mdx';
import '@/styles/global.css';
import { source } from '@/lib/source';
import Layout from '@/components/layout.astro';
import type { TOCItemType } from '@hanzo/docs/core/toc';
import { render, type CollectionEntry } from "astro:content";
import { Docs } from "@/components/docs";
import defaultMdxComponents from "fumadocs-ui/mdx";
import "@/styles/global.css";
import { source } from "@/lib/source";
import Layout from "@/components/layout.astro";
import type { TOCItemType } from "fumadocs-core/toc";
interface Props {
page: CollectionEntry<'docs'>;
page: CollectionEntry<"docs">;
}
export async function getStaticPaths() {
return source.getPages().map((page) => ({
params: { slug: page.slugs.length > 0 ? page.slugs.join('/') : undefined },
params: { slug: page.slugs.length > 0 ? page.slugs.join("/") : undefined },
props: { page: page.data._raw },
}));
}
@@ -24,7 +24,7 @@ const { Content, headings } = await render(page);
const toc: TOCItemType[] = headings.map((heading) => ({
depth: heading.depth,
title: heading.text,
url: '#' + heading.slug,
url: "#" + heading.slug,
}));
---
@@ -33,7 +33,7 @@ const toc: TOCItemType[] = headings.map((heading) => ({
<meta name="title" content={page.data.title} />
<meta name="description" content={page.data.description} />
<Docs
tree={source.pageTree}
tree={source.getPageTree()}
pathname={Astro.url.pathname}
params={Astro.params as Record<string, string | string[]>}
page={{ toc }}
+3 -1
View File
@@ -13,7 +13,9 @@ const server = createFromSource(source, {
description: page.data.description,
structuredData,
url: page.url,
breadcrumbs: getBreadcrumbItems(page.url, source.pageTree).map((item) => String(item.name)),
breadcrumbs: getBreadcrumbItems(page.url, source.getPageTree()).map((item) =>
String(item.name),
),
};
},
});
@@ -5,7 +5,7 @@ import { baseOptions } from '@/app/layout.config';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions}>
<DocsLayout tree={source.getPageTree()} {...baseOptions}>
{children}
</DocsLayout>
);
+1 -1
View File
@@ -4,7 +4,7 @@ import { source } from '@/lib/source';
export default function Layout({ children }: LayoutProps<'/docs'>) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions()}>
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
{children}
</DocsLayout>
);
+1 -1
View File
@@ -4,7 +4,7 @@ import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: LayoutProps<'/docs'>) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions()}>
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
{children}
</DocsLayout>
);
+1 -1
View File
@@ -4,7 +4,7 @@ import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: LayoutProps<'/docs'>) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions()}>
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
{children}
</DocsLayout>
);
+1 -1
View File
@@ -4,7 +4,7 @@ import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: LayoutProps<'/docs'>) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions()}>
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
{children}
</DocsLayout>
);
+1 -1
View File
@@ -5,7 +5,7 @@ import { baseOptions } from '@/lib/layout.shared';
export default function RootDocsLayout({ children }: { children: ReactNode }) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions()}>
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
{children}
</DocsLayout>
);
+1 -1
View File
@@ -5,7 +5,7 @@ import { source } from '@/lib/source';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions()}>
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
{children}
</DocsLayout>
);
+1 -1
View File
@@ -13,7 +13,7 @@ export function ServerComponent({ params }: Route.ComponentProps) {
const { body: MDX } = page.data;
return (
<DocsLayout {...baseOptions()} tree={source.pageTree}>
<DocsLayout {...baseOptions()} tree={source.getPageTree()}>
<DocsPage toc={page.data.toc}>
<title>{page.data.title}</title>
<meta name="description" content={page.data.description} />
+1 -1
View File
@@ -14,7 +14,7 @@ export async function loader({ params }: Route.LoaderArgs) {
return {
path: page.path,
pageTree: await source.serializePageTree(source.pageTree),
pageTree: await source.serializePageTree(source.getPageTree()),
};
}
@@ -30,7 +30,7 @@ const loader = createServerFn({
return {
path: page.path,
pageTree: await source.serializePageTree(source.pageTree),
pageTree: await source.serializePageTree(source.getPageTree()),
};
});
+1 -1
View File
@@ -5,7 +5,7 @@ import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout {...baseOptions()} tree={source.pageTree}>
<DocsLayout {...baseOptions()} tree={source.getPageTree()}>
{children}
</DocsLayout>
);
@@ -162,7 +162,6 @@ export function DocsLayout(props: DocsLayoutProps) {
)}
</div>
)}
{nav.children}
{tabs.length > 0 && (
<SidebarTabsDropdown
options={tabs}
@@ -315,6 +314,7 @@ function DocsNavbar({
navMode === 'auto' && 'md:hidden',
),
})}
{nav.children}
</div>
{searchToggle.enabled !== false &&
(searchToggle.components?.lg ? (
@@ -343,7 +343,6 @@ function DocsNavbar({
<NavbarLinkItem key={i} item={item} />
))}
</div>
{nav.children}
{links
.filter((item) => item.type === 'icon')
.map((item, i) => (
+11
View File
@@ -1,5 +1,16 @@
# @fumadocs/content-collections
## 1.2.5
### Patch Changes
- b16a32f: Switch to tsdown for bundling
- Updated dependencies [590d36a]
- Updated dependencies [98d38ff]
- Updated dependencies [446631d]
- Updated dependencies [b16a32f]
- fumadocs-core@16.4.2
## 1.2.4
### Patch Changes
+1 -1
View File
@@ -10,5 +10,5 @@ export * from './remark-code-tab';
export * from './remark-steps';
export * from './remark-npm';
export * from './codeblock-utils';
export * from './remark-mdx-files';
export { remarkMdxFiles, type RemarkMdxFilesOptions } from './remark-mdx-files';
export * from './remark-mdx-mermaid';
+144 -18
View File
@@ -2,20 +2,26 @@ import type { Root } from 'mdast';
import { visit } from 'unist-util-visit';
import type { Transformer } from 'unified';
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
import type { VFile } from 'vfile';
import path from 'node:path';
interface FileNode {
export interface FileNode {
depth: number;
type: 'file';
name: string;
}
interface FolderNode {
export interface FolderNode {
depth: number;
type: 'folder';
name: string;
children: Node[];
}
export interface ToMdxOptions {
defaultOpenAll: boolean;
}
type Node = FileNode | FolderNode;
export interface RemarkMdxFilesOptions {
@@ -23,7 +29,12 @@ export interface RemarkMdxFilesOptions {
* @defaultValue files
*/
lang?: string;
toMdx?: (node: Node) => MdxJsxFlowElement;
toMdx?: (node: Node, options: ToMdxOptions) => MdxJsxFlowElement;
}
interface AutoFilesProps extends Partial<ToMdxOptions> {
dir?: string;
patterns?: string[];
}
function parseFileTree(code: string) {
@@ -65,13 +76,13 @@ function parseFileTree(code: string) {
return stack.get(0);
}
function defaultToMDX(node: Node, depth = 0): MdxJsxFlowElement {
function defaultToMDX(node: Node, options: ToMdxOptions, depth = 0): MdxJsxFlowElement {
if (depth === 0) {
return {
type: 'mdxJsxFlowElement',
name: 'Files',
attributes: [],
children: [defaultToMDX(node, depth + 1)],
children: [defaultToMDX(node, options, depth + 1)],
};
}
@@ -88,21 +99,26 @@ function defaultToMDX(node: Node, depth = 0): MdxJsxFlowElement {
};
}
attributes.push({
type: 'mdxJsxAttribute',
name: 'defaultOpen',
value: null,
});
if (options.defaultOpenAll) {
attributes.push({
type: 'mdxJsxAttribute',
name: 'defaultOpen',
value: null,
});
}
return {
type: 'mdxJsxFlowElement',
attributes,
name: 'Folder',
children: node.children.map((item) => defaultToMDX(item, depth + 1)),
children: node.children.map((item) => defaultToMDX(item, options, depth + 1)),
};
}
/**
*
* **Files CodeBlock:**
*
* Convert codeblocks with `files` as lang, like:
*
* ```files
@@ -114,19 +130,129 @@ function defaultToMDX(node: Node, depth = 0): MdxJsxFlowElement {
* ├── package.json
* ```
*
* into MDX `<Files />` component
* into MDX `<Files />` component.
*
* **Auto Files:**
*
* Generates MDX `<Files />` component from file system.
*
* ```mdx
* <auto-files dir="scripts" pattern="my-dir/*" defaultOpenAll />
* ```
*/
export function remarkMdxFiles(options: RemarkMdxFilesOptions = {}): Transformer<Root, Root> {
const { lang = 'files', toMdx = defaultToMDX } = options;
return (tree) => {
visit(tree, 'code', (node) => {
if (node.lang !== lang || !node.value) return;
async function autoFiles(
file: VFile,
node: MdxJsxFlowElement,
{ patterns, dir, defaultOpenAll = false }: AutoFilesProps,
) {
const { glob } = await import('tinyglobby');
if (!patterns) {
file.fail('Missing `pattern` prop in <auto-files>', {
place: node.position,
});
}
const fileTree = parseFileTree(node.value);
if (!fileTree) return;
const baseDir = file.dirname ?? file.cwd;
const cwd = dir ? path.join(baseDir, dir) : baseDir;
const files = await glob(patterns, { cwd });
Object.assign(node, toMdx(buildFileTreeFromGlob(cwd, files), { defaultOpenAll }));
}
Object.assign(node, toMdx(fileTree));
return async (tree, file) => {
const queue: Promise<void>[] = [];
visit(tree, ['code', 'mdxJsxFlowElement'] as const, (node) => {
if (node.type === 'code') {
if (node.lang !== lang || !node.value) return;
const fileTree = parseFileTree(node.value);
if (!fileTree) return;
Object.assign(
node,
toMdx(fileTree, {
defaultOpenAll: true,
}),
);
return 'skip';
}
if (node.type === 'mdxJsxFlowElement') {
if (node.name !== 'auto-files') return;
const parsed: AutoFilesProps = {};
for (const attr of node.attributes) {
if (attr.type !== 'mdxJsxAttribute') continue;
const { name, value } = attr;
switch (name) {
case 'dir':
if (typeof value === 'string') parsed.dir = value;
break;
case 'pattern':
if (typeof value === 'string') {
parsed.patterns ??= [];
parsed.patterns.push(value);
}
break;
case 'defaultOpenAll':
parsed.defaultOpenAll = true;
break;
}
}
queue.push(autoFiles(file, node, parsed));
return 'skip';
}
});
await Promise.all(queue);
};
}
function buildFileTreeFromGlob(dir: string, files: string[]): Node {
const nodeMap = new Map<string, FolderNode>();
const root: FolderNode = {
depth: 0,
type: 'folder',
name: path.basename(dir),
children: [],
};
nodeMap.set('', root);
for (const file of files) {
const parts = path
.normalize(file)
.split(path.sep)
.filter((part) => part.length > 0);
let currentPath = '';
let current = root;
for (let i = 0; i < parts.length; i++) {
const name = parts[i];
const nextPath = path.join(currentPath, name);
if (i === parts.length - 1) {
// Add file node.
const fileNode: FileNode = { depth: i + 1, type: 'file', name };
current.children.push(fileNode);
} else {
// Add or retrieve folder node using the map.
let folder = nodeMap.get(nextPath);
if (!folder) {
folder = { depth: i + 1, type: 'folder', name, children: [] };
nodeMap.set(nextPath, folder);
current.children.push(folder);
}
current = folder;
currentPath = nextPath;
}
}
}
return root;
}
+10
View File
@@ -0,0 +1,10 @@
```files
project
├── src
│ ├── index.js
│ └── utils
│ └── helper.js
├── package.json
```
<auto-files dir="page-trees" pattern="**/*.json" />
+16
View File
@@ -11,3 +11,19 @@
<File name="package.json" />
</Folder>
</Files>
<Files>
<Folder name="page-trees">
<File name="basic.tree.json" />
<File name="i18n-dir.tree.json" />
<File name="i18n-no-prefix.tree.json" />
<File name="i18n.entries.json" />
<File name="i18n.tree.json" />
<File name="nested.tree.json" />
</Folder>
</Files>
+22 -19
View File
@@ -1,5 +1,5 @@
import { expect, test } from 'vitest';
import { readFileSync } from 'node:fs';
import fs from 'node:fs/promises';
import path from 'node:path';
import { remark } from 'remark';
import {
@@ -16,7 +16,6 @@ import { fileURLToPath } from 'node:url';
import remarkMdx from 'remark-mdx';
import remarkGfm from 'remark-gfm';
import { createProcessor } from '@mdx-js/mdx';
import * as fs from 'node:fs/promises';
import { remarkSteps } from '@/mdx-plugins/remark-steps';
import remarkDirective from 'remark-directive';
@@ -24,7 +23,7 @@ const cwd = path.dirname(fileURLToPath(import.meta.url));
test('Remark Heading', async () => {
const file = path.resolve(cwd, './fixtures/remark-heading.md');
const content = readFileSync(file);
const content = await fs.readFile(file);
const result = await remark().use(remarkHeading).process(content);
@@ -33,8 +32,21 @@ test('Remark Heading', async () => {
);
});
test('Remark Mdx Files', async () => {
const file = path.resolve(cwd, './fixtures/remark-mdx-files.mdx');
const content = await fs.readFile(file);
const result = await remark().use(remarkMdx).use(remarkMdxFiles).process({
path: file,
value: content,
});
await expect(String(result.value)).toMatchFileSnapshot(
path.resolve(cwd, './fixtures/remark-mdx-files.output.mdx'),
);
});
test('Remark Structure', async () => {
const content = readFileSync(path.resolve(cwd, './fixtures/remark-structure.md'));
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-structure.md'));
const result = await remark().use(remarkGfm).use(remarkStructure).process(content);
await expect(result.data.structuredData).toMatchFileSnapshot(
@@ -43,7 +55,7 @@ test('Remark Structure', async () => {
});
test('Remark Admonition', async () => {
const content = readFileSync(path.resolve(cwd, './fixtures/remark-admonition.md'));
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-admonition.md'));
const processor = remark().use(remarkMdx).use(remarkDirective).use(remarkDirectiveAdmonition);
let tree = processor.parse(content);
tree = await processor.run(tree);
@@ -65,7 +77,7 @@ test('Remark Steps', async () => {
test('Remark Image: With Path', async () => {
const file = path.resolve(cwd, './fixtures/remark-image.md');
const content = readFileSync(file);
const content = await fs.readFile(file);
const processor = remark()
.use(remarkImage, { publicDir: path.resolve(cwd, './fixtures') })
.use(remarkMdx);
@@ -80,7 +92,7 @@ test('Remark Image: With Path', async () => {
});
test('Remark Image: Without Import', async () => {
const content = readFileSync(path.resolve(cwd, './fixtures/remark-image.md'));
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image.md'));
const result = await remark()
.use(remarkImage, {
publicDir: path.resolve(cwd, './fixtures'),
@@ -95,7 +107,7 @@ test('Remark Image: Without Import', async () => {
});
test('Remark Image: `publicDir` with URL', async () => {
const content = readFileSync(path.resolve(cwd, './fixtures/remark-image-public-dir.md'));
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image-public-dir.md'));
const result = await remark()
.use(remarkImage, {
publicDir: 'https://hanzo-docs.dev',
@@ -109,17 +121,8 @@ test('Remark Image: `publicDir` with URL', async () => {
);
});
test('Remark MDX Files', async () => {
const content = readFileSync(path.resolve(cwd, './fixtures/remark-mdx-files.md'));
const result = await remark().use(remarkMdxFiles).use(remarkMdx).process(content);
await expect(result.value).toMatchFileSnapshot(
path.resolve(cwd, './fixtures/remark-mdx-files.output.mdx'),
);
});
test('converts mermaid codeblock to MDX Mermaid component', async () => {
const content = readFileSync(path.resolve(cwd, './fixtures/remark-mdx-mermaid.md'));
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-mdx-mermaid.md'));
const result = await remark().use(remarkMdxMermaid).use(remarkMdx).process(content);
await expect(result.value).toMatchFileSnapshot(
@@ -128,7 +131,7 @@ test('converts mermaid codeblock to MDX Mermaid component', async () => {
});
test('Rehype Toc', async () => {
const content = readFileSync(path.resolve(cwd, './fixtures/rehype-toc.md'));
const content = await fs.readFile(path.resolve(cwd, './fixtures/rehype-toc.md'));
const processor = createProcessor({
remarkPlugins: [remarkHeading],
@@ -4,7 +4,7 @@ import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: LayoutProps<'/docs'>) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions()}>
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
{children}
</DocsLayout>
);
@@ -14,7 +14,7 @@ export async function loader({ params }: Route.LoaderArgs) {
return {
path: page.path,
pageTree: await source.serializePageTree(source.pageTree),
pageTree: await source.serializePageTree(source.getPageTree()),
};
}
@@ -30,7 +30,7 @@ const loader = createServerFn({
return {
path: page.path,
pageTree: await source.serializePageTree(source.pageTree),
pageTree: await source.serializePageTree(source.getPageTree()),
};
});
@@ -5,7 +5,7 @@ import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout {...baseOptions()} tree={source.pageTree}>
<DocsLayout {...baseOptions()} tree={source.getPageTree()}>
{children}
</DocsLayout>
);
+12 -1
View File
@@ -1,5 +1,16 @@
# fumadocs-docgen
## 3.0.5
### Patch Changes
- b16a32f: Switch to tsdown for bundling
- Updated dependencies [590d36a]
- Updated dependencies [98d38ff]
- Updated dependencies [446631d]
- Updated dependencies [b16a32f]
- fumadocs-core@16.4.2
## 3.0.4
### Patch Changes
@@ -125,7 +136,7 @@
Import it like:
```ts
import { remarkTypeScriptToJavaScript } from 'fumadocs-docgen/remark-ts2js';
import { remarkTypeScriptToJavaScript } from "fumadocs-docgen/remark-ts2js";
```
instead of importing from `fumadocs-docgen`.
+11
View File
@@ -1,5 +1,16 @@
# @fumadocs/mdx-remote
## 1.4.4
### Patch Changes
- b16a32f: Switch to tsdown for bundling
- Updated dependencies [590d36a]
- Updated dependencies [98d38ff]
- Updated dependencies [446631d]
- Updated dependencies [b16a32f]
- fumadocs-core@16.4.2
## 1.4.3
### Patch Changes
+12
View File
@@ -1,5 +1,17 @@
# fumadocs-obsidian
## 0.0.7
### Patch Changes
- b16a32f: Switch to tsdown for bundling
- dd7498f: fix slugification
- Updated dependencies [590d36a]
- Updated dependencies [98d38ff]
- Updated dependencies [446631d]
- Updated dependencies [b16a32f]
- fumadocs-core@16.4.2
## 0.0.6
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
import type { ParsedFile, VaultStorage } from '@/build-storage';
import path from 'node:path';
import { stash } from '@/utils/stash';
import { slash } from '@/utils/slash';
export interface VaultResolver {
/**
@@ -35,7 +35,7 @@ export function buildResolver(storage: VaultStorage): VaultResolver {
const parsed = path.parse(file.path);
nameToFile.set(parsed.name, file);
pathToFile.set(stash(path.join(parsed.dir, parsed.name)), file);
pathToFile.set(slash(path.join(parsed.dir, parsed.name)), file);
nameToFile.set(parsed.base, file);
pathToFile.set(file.path, file);
@@ -59,7 +59,7 @@ export function buildResolver(storage: VaultStorage): VaultResolver {
const dir = path.dirname(fromPath);
if (name.startsWith('./') || name.startsWith('../')) {
return this.resolvePath(stash(path.join(dir, name)));
return this.resolvePath(slash(path.join(dir, name)));
}
// absolute path or name
+25 -14
View File
@@ -1,8 +1,9 @@
import path from 'node:path';
import matter from 'gray-matter';
import { type Frontmatter, frontmatterSchema } from '@/utils/schema';
import { stash } from '@/utils/stash';
import { slash } from '@/utils/slash';
import type { VaultFile } from '@/read-vaults';
import { slug } from 'github-slugger';
type RenameOutputFn = (originalOutputPath: string, file: VaultFile) => string;
type RenameOutputPreset = 'ignore' | 'simple';
@@ -86,23 +87,19 @@ export function buildStorage(
const storage = new Map<string, ParsedFile>();
for (const rawFile of rawFiles) {
parseFile(rawFile);
}
function parseFile(file: VaultFile) {
const normalizedPath = normalize(file.path);
let outPath = getOutputPath(normalizedPath, file);
const normalizedPath = normalize(rawFile.path);
let outPath = getOutputPath(normalizedPath, rawFile);
let parsed: ParsedFile;
if (['.md', '.mdx'].includes(path.extname(normalizedPath))) {
const { data, content } = matter(String(file.content));
const { data, content } = matter(String(rawFile.content));
if (enforceMdx) {
outPath = outPath.slice(0, -path.extname(outPath).length) + '.mdx';
}
parsed = {
format: 'content',
_raw: file._raw,
_raw: rawFile._raw,
path: normalizedPath,
outPath,
frontmatter: frontmatterSchema.parse(data),
@@ -112,10 +109,10 @@ export function buildStorage(
parsed = {
format: 'media',
path: normalizedPath,
_raw: file._raw,
_raw: rawFile._raw,
outPath,
content: file.content,
url: url(outPath, file),
content: rawFile.content,
url: url(outPath, rawFile),
};
}
@@ -128,11 +125,25 @@ export function buildStorage(
function createRenameOutput(preset: RenameOutputPreset): RenameOutputFn {
if (preset === 'ignore') return (file) => file;
return (file) => file.toLowerCase().replaceAll(' ', '-');
const occurrences = new Map<string, number>();
return (file) => {
const ext = path.extname(file);
const segs = file.slice(0, -ext.length).split('/');
for (let i = 0; i < segs.length; i++) {
// preserve separators
segs[i] = slug(segs[i]);
}
// we only count occurrences by the full path
let out = segs.join('/');
const o = occurrences.get(out) ?? 0;
occurrences.set(out, o + 1);
if (o > 0) out += `-${o}`;
return out + ext;
};
}
function normalize(filePath: string): string {
filePath = stash(filePath);
filePath = slash(filePath);
if (filePath.startsWith('../')) throw new Error(`${filePath} points outside of vault folder`);
return filePath.startsWith('./') ? filePath.slice(2) : filePath;
+2 -2
View File
@@ -1,13 +1,13 @@
import type { ParsedFile } from '@/build-storage';
import path from 'node:path';
import { stash } from '@/utils/stash';
import { slash } from '@/utils/slash';
import { slug } from 'github-slugger';
export function getFileHref(ref: ParsedFile, sourceFile: ParsedFile, heading?: string) {
if (ref.format === 'media' && ref.url) return ref.url;
const dir = path.dirname(sourceFile.outPath);
let url = stash(path.relative(dir, ref.outPath));
let url = slash(path.relative(dir, ref.outPath));
if (!url.startsWith('../')) url = `./${url}`;
if (heading) url += `#${getHeadingHash(heading)}`;
+9
View File
@@ -0,0 +1,9 @@
export function slash(path: string): string {
const isExtendedLengthPath = path.startsWith('\\\\?\\');
if (isExtendedLengthPath) {
return path;
}
return path.replaceAll('\\', '/');
}
-8
View File
@@ -1,8 +0,0 @@
import path from 'node:path';
export function stash(v: string): string {
const sep = path.sep;
if (sep !== '/') return v.replaceAll(path.sep, '/');
return v;
}
+88 -75
View File
@@ -1,5 +1,18 @@
# @fuma-docs/openapi
## 10.2.3
### Patch Changes
- 7e58c8e: Fix Parameter Serialization
- b16a32f: Switch to tsdown for bundling
- Updated dependencies [590d36a]
- Updated dependencies [98d38ff]
- Updated dependencies [446631d]
- Updated dependencies [b16a32f]
- fumadocs-core@16.4.2
- fumadocs-ui@16.4.2
## 10.2.2
### Patch Changes
@@ -188,8 +201,8 @@
```ts
// components/api-page.tsx
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
import { openapi } from "@/lib/openapi";
import { createAPIPage } from "fumadocs-openapi/ui";
export const APIPage = createAPIPage(openapi, {
// e.g. customise render functions
@@ -219,8 +232,8 @@
```tsx
// components/api-page.tsx
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
import { openapi } from "@/lib/openapi";
import { createAPIPage } from "fumadocs-openapi/ui";
export const APIPage = createAPIPage(openapi, {
content: {
@@ -250,23 +263,23 @@
Before:
```ts
import { openapi } from '@/lib/openapi';
import { openapi } from "@/lib/openapi";
void generateFiles({
input: ['./products.yaml'],
output: './content/docs',
input: ["./products.yaml"],
output: "./content/docs",
});
```
After:
```ts
import { generateFiles } from 'fumadocs-openapi';
import { openapi } from '@/lib/openapi';
import { generateFiles } from "fumadocs-openapi";
import { openapi } from "@/lib/openapi";
void generateFiles({
input: openapi,
output: './content/docs',
output: "./content/docs",
});
```
@@ -298,12 +311,12 @@
```ts
// lib/openapi.ts
import { createOpenAPI } from 'fumadocs-openapi/server';
import path from 'node:path';
import { createOpenAPI } from "fumadocs-openapi/server";
import path from "node:path";
export const openapi = createOpenAPI({
input: [path.resolve('./scalar.yaml')],
proxyUrl: '/api/proxy',
input: [path.resolve("./scalar.yaml")],
proxyUrl: "/api/proxy",
});
```
@@ -327,8 +340,8 @@
```ts
// components/api-page.tsx
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
import { openapi } from "@/lib/openapi";
import { createAPIPage } from "fumadocs-openapi/ui";
export const APIPage = createAPIPage(openapi, {
playground: {
@@ -341,9 +354,9 @@
```tsx
// components/api-page.tsx
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
import client from './api-page.client';
import { openapi } from "@/lib/openapi";
import { createAPIPage } from "fumadocs-openapi/ui";
import client from "./api-page.client";
export const APIPage = createAPIPage(openapi, {
client,
@@ -352,17 +365,17 @@
```tsx
// components/api-page.client.tsx
'use client';
import { defineClientConfig } from 'fumadocs-openapi/ui/client';
"use client";
import { defineClientConfig } from "fumadocs-openapi/ui/client";
export default defineClientConfig({
playground: {
transformAuthInputs: (inputs) => [
...inputs,
{
fieldName: 'auth.tests',
fieldName: "auth.tests",
children: <div>Tests</div>,
defaultValue: '',
defaultValue: "",
},
],
},
@@ -375,10 +388,10 @@
```tsx
// components/api-page.tsx
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
import { adapters } from './my-media-adapters';
import client from './api-page.client';
import { openapi } from "@/lib/openapi";
import { createAPIPage } from "fumadocs-openapi/ui";
import { adapters } from "./my-media-adapters";
import client from "./api-page.client";
export const APIPage = createAPIPage(openapi, {
client,
@@ -388,9 +401,9 @@
```tsx
// components/api-page.client.tsx
'use client';
import { defineClientConfig } from 'fumadocs-openapi/ui/client';
import { adapters } from './my-media-adapters';
"use client";
import { defineClientConfig } from "fumadocs-openapi/ui/client";
import { adapters } from "./my-media-adapters";
export default defineClientConfig({
mediaAdapters: adapters,
@@ -408,11 +421,11 @@
```tsx
// components/api-page.client.tsx
'use client';
import { defineClientConfig } from 'fumadocs-openapi/ui/client';
"use client";
import { defineClientConfig } from "fumadocs-openapi/ui/client";
export default defineClientConfig({
storageKeyPrefix: 'fumadocs-openapi-custom-',
storageKeyPrefix: "fumadocs-openapi-custom-",
});
```
@@ -758,22 +771,22 @@
Migration: Move the server object from `lib/source` to `lib/openapi`
```ts
import { createOpenAPI } from 'fumadocs-openapi/server';
import { createOpenAPI } from "fumadocs-openapi/server";
export const openapi = createOpenAPI({
input: ['./my-schema.json'],
input: ["./my-schema.json"],
});
```
Use the server object for `generateFiles()`:
```ts
import { generateFiles } from 'fumadocs-openapi';
import { openapi } from '@/lib/openapi';
import { generateFiles } from "fumadocs-openapi";
import { openapi } from "@/lib/openapi";
void generateFiles({
input: openapi,
output: './content/docs',
output: "./content/docs",
// we recommend to enable it
// make sure your endpoint description doesn't break MDX syntax.
includeDescription: true,
@@ -1091,15 +1104,15 @@
```ts
generateFiles({
input: ['./content/docs/openapi/museum.yaml'],
output: './content/docs/openapi/(generated)',
per: 'operation',
input: ["./content/docs/openapi/museum.yaml"],
output: "./content/docs/openapi/(generated)",
per: "operation",
name: (output, document) => {
// page info
output.item;
// parsed OpenAPI schema
document;
return 'dir/my-file';
return "dir/my-file";
},
});
```
@@ -1108,11 +1121,11 @@
```ts
generateFiles({
input: ['./content/docs/openapi/museum.yaml'],
output: './content/docs/openapi/(generated)',
per: 'operation',
input: ["./content/docs/openapi/museum.yaml"],
output: "./content/docs/openapi/(generated)",
per: "operation",
name: {
algorithm: 'v1',
algorithm: "v1",
},
});
```
@@ -1123,9 +1136,9 @@
```ts
generateFiles({
input: ['./content/docs/openapi/museum.yaml'],
output: './content/docs/openapi/(generated)',
per: 'operation',
input: ["./content/docs/openapi/museum.yaml"],
output: "./content/docs/openapi/(generated)",
per: "operation",
});
```
@@ -1287,11 +1300,11 @@
We highly recommend to use the following instead:
```css
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import "tailwindcss";
@import "fumadocs-ui/css/neutral.css";
@import "fumadocs-ui/css/preset.css";
/* do this */
@import 'fumadocs-openapi/css/preset.css';
@import "fumadocs-openapi/css/preset.css";
```
- Updated dependencies [3a5595a]
@@ -1335,10 +1348,10 @@
in your `mdx-components.tsx` (or where you pass MDX components):
```tsx
import defaultComponents from 'fumadocs-ui/mdx';
import { APIPage } from 'fumadocs-openapi/ui';
import { openapi } from '@/lib/source';
import type { MDXComponents } from 'mdx/types';
import defaultComponents from "fumadocs-ui/mdx";
import { APIPage } from "fumadocs-openapi/ui";
import { openapi } from "@/lib/source";
import type { MDXComponents } from "mdx/types";
export function getMDXComponents(components?: MDXComponents): MDXComponents {
return {
@@ -1690,8 +1703,8 @@
From:
```tsx
import { createOpenAPI } from 'fumadocs-openapi/server';
import { APIPlayground } from 'fumadocs-openapi/scalar';
import { createOpenAPI } from "fumadocs-openapi/server";
import { APIPlayground } from "fumadocs-openapi/scalar";
export const openapi = createOpenAPI({
useScalar: true,
@@ -1701,8 +1714,8 @@
To:
```tsx
import { createOpenAPI } from 'fumadocs-openapi/server';
import { APIPlayground } from 'fumadocs-openapi/scalar';
import { createOpenAPI } from "fumadocs-openapi/server";
import { APIPlayground } from "fumadocs-openapi/scalar";
export const openapi = createOpenAPI({
renderer: {
@@ -2414,7 +2427,7 @@
---
<APIPage
operations={[{ path: '/v1/apis.deleteApi', method: 'post' }]}
operations={[{ path: "/v1/apis.deleteApi", method: "post" }]}
hasHead={false}
/>
```
@@ -2620,13 +2633,13 @@
Add the package to `content` under your Tailwind CSS configuration.
```js
import { createPreset, presets } from 'fumadocs-ui/tailwind-plugin';
import { createPreset, presets } from "fumadocs-ui/tailwind-plugin";
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./node_modules/fumadocs-ui/dist/**/*.js',
'./node_modules/fumadocs-openapi/dist/**/*.js',
"./node_modules/fumadocs-ui/dist/**/*.js",
"./node_modules/fumadocs-openapi/dist/**/*.js",
],
presets: [createPreset()],
};
@@ -2824,11 +2837,11 @@
migrate: Create a script named `scripts/generate-docs.mjs`:
```js
import { generateFiles } from 'fumadocs-openapi';
import { generateFiles } from "fumadocs-openapi";
void generateFiles({
input: ['./petstore.yaml'],
output: './content/docs',
input: ["./petstore.yaml"],
output: "./content/docs",
});
```
@@ -2875,18 +2888,18 @@
* @type {import("@fuma-docs/openapi").Config}
*/
module.exports = {
input: ['./petstore.yaml'],
output: './content/docs',
per: 'tag',
input: ["./petstore.yaml"],
output: "./content/docs",
per: "tag",
render: (title, description) => {
return {
frontmatter: [
'---',
"---",
`title: ${title}`,
`description: ${description}`,
'toc: false',
'---',
].join('\n'),
"toc: false",
"---",
].join("\n"),
};
},
};
+1
View File
@@ -12,6 +12,7 @@ export default [
'no-console': 'off',
// some arrays will not be changed
'react/no-array-index-key': 'off',
'no-fallthrough': 'off',
},
},
];
+1 -6
View File
@@ -33,12 +33,7 @@ export function createBrowserFetcher(
for (const key in options.header) {
const param = options.header[key];
if (!Array.isArray(param.value)) {
headers.append(key, param.value);
} else {
headers.append(key, param.value.join(','));
}
headers.append(key, param.value);
}
const proxyUrl = options.proxyUrl ? new URL(options.proxyUrl, document.baseURI) : null;
@@ -32,7 +32,7 @@ export const generator: SampleGenerator = (url, data, { mediaAdapters }) => {
s.push('var client = new HttpClient();');
const headerLines: string[] = [];
function addHeader(key: string, value: unknown) {
function addHeader(key: string, value: string) {
headerLines.push(`client.DefaultRequestHeaders.Add("${key}", ${JSON.stringify(value)});`);
}
for (const k in headers) {
@@ -14,7 +14,7 @@ export const generator: SampleGenerator = (url, data, { mediaAdapters }) => {
}
for (const [k, v] of Object.entries(data.header)) {
headers[k] = v.value as string;
headers[k] = v.value;
}
const cookies = Object.entries(data.cookie);
@@ -25,7 +25,7 @@ export const generator: SampleGenerator = (url, data, { mediaAdapters }) => {
}
for (const [k, v] of Object.entries(data.header)) {
headers[k] = v.value as string;
headers[k] = v.value;
}
if (Object.keys(headers).length > 0) {
@@ -37,7 +37,7 @@ export const generator: SampleGenerator = (url, data, { mediaAdapters }) => {
const cookies: Record<string, string> = {};
for (const [k, v] of inputCookies) {
cookies[k] = v.value as string;
cookies[k] = v.value;
}
params.push(`cookies = ${generatePythonObject(cookies, imports)}`);
+196 -94
View File
@@ -5,117 +5,67 @@ import type { ParameterObject } from '@/types';
import type { RawRequestData, RequestData } from '@/requests/types';
export interface EncodedParameter {
readonly value: string | string[];
readonly value: string;
}
const FormDelimiter = {
spaceDelimited: ' ',
pipeDelimited: '|',
};
const PathPrefix = {
label: '.',
matrix: ';',
};
export interface EncodedParameterMultiple {
readonly values: string[];
}
/**
* serialize parameters, see https://swagger.io/docs/specification/v3_0/serialization.
*/
export function encodeRequestData(
from: RawRequestData,
adapters: Record<string, MediaAdapter>,
parameters: NoReference<ParameterObject>[],
): RequestData {
const result: Partial<RequestData> = {
const result: RequestData = {
method: from.method,
body: from.body,
bodyMediaType: from.bodyMediaType,
cookie: {},
header: {},
path: {},
query: {},
};
for (const type of ['cookie', 'query', 'header', 'path'] as const) {
const output: Record<string, EncodedParameter> = {};
for (const key in from[type]) {
const value = from[type][key];
if (value == null) continue;
const field = parameters.find((p) => p.name === key && p.in === type);
if (!field) {
output[key] = { value: String(value) };
continue;
}
// skip unknown parameters
if (!field) continue;
const encoder = getMediaEncoder(field, adapters);
if (encoder) {
output[key] = { value: encoder(value) };
result[type][key] = { value: encoder(value) };
continue;
}
const explode = field.explode ?? true;
let prefix = '';
let sep = ',';
if (field.in === 'path') {
const style = field.style ?? 'simple';
if (style in PathPrefix) {
prefix = PathPrefix[style as keyof typeof PathPrefix];
if (explode) sep = prefix;
}
}
if (Array.isArray(value)) {
// header & cookie doesn't support explode for array values
if (explode && field.in !== 'header' && field.in !== 'cookie') {
output[key] = {
value: prefix + value.map(String),
switch (type) {
case 'path':
serializePathParameter(field, value, result.path);
break;
case 'query':
serializeQueryParameter(field, value, result.query);
break;
case 'header': {
result.header[key] = {
value: serializeSimple(value, field.explode ?? false),
};
continue;
break;
}
if (field.in === 'query') {
const style = field.style ?? 'form';
if (style in FormDelimiter) sep = FormDelimiter[style as keyof typeof FormDelimiter];
}
output[key] = {
value: prefix + value.map(String).join(sep),
};
continue;
case 'cookie':
serializeCookieParameter(field, value, result.cookie);
break;
}
if (typeof value === 'object' && value) {
// header & path creates key-value pairs
if (explode && (field.in === 'header' || field.in === 'path')) {
output[key] = {
value:
prefix +
Object.entries(value)
.map(([k, v]) => `${k}=${v}`)
.join(sep),
};
continue;
}
if (explode || field.style === 'deepObject') {
writeObject(key, value, field.style === 'deepObject', output);
continue;
}
output[key] = {
value: prefix + Object.entries(value).flat().join(sep),
};
continue;
}
output[key] = {
value: prefix + String(value),
};
}
result[type] = output;
}
return result as RequestData;
return result;
}
function getMediaEncoder(
@@ -132,25 +82,177 @@ function getMediaEncoder(
}
}
function writeObject(
parentKey: string,
value: object,
deep: boolean,
function serializeSimple(value: NonNullable<unknown>, explode: boolean): string {
if (Array.isArray(value)) {
return value.join(',');
}
if (typeof value === 'object') {
return explode
? Object.entries(value)
.map(([k, v]) => `${k}=${v}`)
.join(',')
: Object.entries(value).flat().join(',');
}
return String(value);
}
function serializePathParameter(
field: NoReference<ParameterObject>,
value: NonNullable<unknown>,
// write output
output: Record<string, EncodedParameter>,
) {
for (const k in value) {
const prop: unknown = value[k as keyof object];
if (prop == null) continue;
): void {
const { explode = false, name } = field;
const key = deep ? `${parentKey}[${k}]` : k;
if (!deep || typeof prop !== 'object') {
output[key] = {
value: String(prop),
switch (field.style) {
case 'label':
if (Array.isArray(value)) {
output[field.name] = {
value: '.' + value.join(explode ? '.' : ','),
};
break;
}
if (typeof value === 'object') {
output[field.name] = {
value:
'.' +
(explode
? Object.entries(value)
.map(([k, v]) => `${k}=${v}`)
.join('.')
: Object.entries(value).flat().join(',')),
};
break;
}
output[field.name] = {
value: `.${value}`,
};
break;
case 'matrix': {
const specifier = `;${name}=`;
continue;
if (Array.isArray(value)) {
output[field.name] = {
value: explode
? `${specifier}${value.join(',')}`
: `${specifier}${value.join(specifier)}`,
};
break;
}
if (typeof value === 'object') {
output[field.name] = {
value: explode
? Object.entries(value)
.map(([k, v]) => `;${k}=${v}`)
.join('')
: specifier + Object.entries(value).flat().join(','),
};
break;
}
output[field.name] = {
value: `${specifier}${value}`,
};
break;
}
// simple
default:
output[field.name] = {
value: serializeSimple(value, explode),
};
}
}
writeObject(key, value, deep, output);
function serializeQueryParameter(
field: NoReference<ParameterObject>,
value: NonNullable<unknown>,
// write output
output: Record<string, EncodedParameterMultiple>,
): void {
const { explode = true } = field;
switch (field.style) {
case 'spaceDelimited':
if (!explode && Array.isArray(value)) {
output[field.name] = {
values: [value.join(' ')],
};
break;
}
case 'pipeDelimited':
if (!explode && Array.isArray(value)) {
output[field.name] = {
values: [value.join('|')],
};
break;
}
case 'deepObject':
if (!Array.isArray(value) && typeof value === 'object') {
for (const [k, v] of Object.entries(value)) {
output[`${field.name}[${k}]`] = {
// note: the behaviour of nested array is undefined, we do this to avoid edge cases
values: Array.isArray(v) ? v : [String(v)],
};
}
break;
}
// form
default:
if (Array.isArray(value)) {
output[field.name] = {
values: explode ? value : [value.join(',')],
};
break;
}
if (typeof value === 'object' && explode) {
for (const [k, v] of Object.entries(value)) {
output[k] = {
values: [String(v)],
};
}
break;
}
if (typeof value === 'object') {
output[field.name] = {
values: [Object.entries(value).flat().join(',')],
};
break;
}
output[field.name] = {
values: [String(value)],
};
}
}
function serializeCookieParameter(
field: NoReference<ParameterObject>,
value: NonNullable<unknown>,
// write output
output: Record<string, EncodedParameter>,
) {
const { explode = true } = field;
// form
if (Array.isArray(value)) {
output[field.name] = {
value: explode ? value.map((v) => `${field.name}=${v}`).join('&') : value.join(','),
};
} else if (typeof value === 'object' && explode) {
for (const [k, v] of Object.entries(value)) {
output[k] = {
value: String(v),
};
}
} else if (typeof value === 'object') {
output[field.name] = {
value: Object.entries(value).flat().join(','),
};
} else {
output[field.name] = {
value: String(value),
};
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import type { MediaAdapter } from '@/requests/media/adapter';
import type { EncodedParameter } from '@/requests/media/encode';
import type { EncodedParameter, EncodedParameterMultiple } from '@/requests/media/encode';
export type SampleGenerator<ServerContext = unknown> = (
url: string,
@@ -26,7 +26,7 @@ export interface RequestData {
method: string;
path: Record<string, EncodedParameter>;
query: Record<string, EncodedParameter>;
query: Record<string, EncodedParameterMultiple>;
header: Record<string, EncodedParameter>;
cookie: Record<string, EncodedParameter>;
+6 -14
View File
@@ -37,11 +37,7 @@ export function resolveRequestData(pathname: string, { path, query }: RequestDat
for (const key in path) {
const param = path[key];
if (Array.isArray(param.value)) {
pathname = pathname.replace(`{${key}}`, param.value.join('/'));
} else {
pathname = pathname.replace(`{${key}}`, param.value);
}
pathname = pathname.replace(`{${key}}`, param.value);
}
// Check if pathname already contains query parameters (legacy API support)
@@ -53,16 +49,12 @@ export function resolveRequestData(pathname: string, { path, query }: RequestDat
// Add new query parameters from the RequestData
for (const key in query) {
const param = query[key];
if (param.values.length === 0) continue;
if (Array.isArray(param.value)) {
// Remove existing parameter first to avoid duplicates
searchParams.delete(key);
for (const item of param.value) {
searchParams.append(key, item);
}
} else {
// Set (replace if exists) the parameter value
searchParams.set(key, param.value);
// Remove existing parameter first to avoid duplicates
searchParams.delete(key);
for (const item of param.values) {
searchParams.append(key, item);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ describe('Code Sample Generators', () => {
authorization: { value: 'Bearer' },
},
query: {
search: { value: 'ai' },
search: { values: ['ai'] },
},
};
const url = resolveRequestData('http://localhost:8080/{test}', data);
+15 -15
View File
@@ -315,7 +315,7 @@ describe('URL utilities', () => {
const requestData: RequestData = {
method: 'GET',
path: {
segments: { value: ['api', 'v1', 'users'] },
segments: { value: 'api/v1/users' },
},
query: {},
header: {},
@@ -330,8 +330,8 @@ describe('URL utilities', () => {
method: 'GET',
path: {},
query: {
limit: { value: '10' },
offset: { value: '20' },
limit: { values: ['10'] },
offset: { values: ['20'] },
},
header: {},
cookie: {},
@@ -345,7 +345,7 @@ describe('URL utilities', () => {
method: 'GET',
path: {},
query: {
tags: { value: ['javascript', 'typescript'] },
tags: { values: ['javascript', 'typescript'] },
},
header: {},
cookie: {},
@@ -362,7 +362,7 @@ describe('URL utilities', () => {
method: 'GET',
path: {},
query: {
limit: { value: '5' },
limit: { values: ['5'] },
},
header: {},
cookie: {},
@@ -378,8 +378,8 @@ describe('URL utilities', () => {
method: 'GET',
path: {},
query: {
limit: { value: '10' },
sort: { value: 'date' },
limit: { values: ['10'] },
sort: { values: ['date'] },
},
header: {},
cookie: {},
@@ -397,7 +397,7 @@ describe('URL utilities', () => {
userId: { value: '123' },
},
query: {
include: { value: 'profile' },
include: { values: ['profile'] },
},
header: {},
cookie: {},
@@ -413,7 +413,7 @@ describe('URL utilities', () => {
method: 'GET',
path: {},
query: {
verbose: { value: 'false' },
verbose: { values: ['false'] },
},
header: {},
cookie: {},
@@ -429,7 +429,7 @@ describe('URL utilities', () => {
method: 'GET',
path: {},
query: {
newParam: { value: 'value' },
newParam: { values: ['value'] },
},
header: {},
cookie: {},
@@ -445,7 +445,7 @@ describe('URL utilities', () => {
method: 'GET',
path: {},
query: {
categories: { value: ['tech', 'science'] },
categories: { values: ['tech', 'science'] },
},
header: {},
cookie: {},
@@ -461,7 +461,7 @@ describe('URL utilities', () => {
method: 'GET',
path: {},
query: {
tags: { value: ['new', 'updated'] },
tags: { values: ['new', 'updated'] },
},
header: {},
cookie: {},
@@ -494,9 +494,9 @@ describe('URL utilities', () => {
projectId: { value: 'web-app' },
},
query: {
include: { value: ['members', 'settings'] },
format: { value: 'json' },
debug: { value: 'true' },
include: { values: ['members', 'settings'] },
format: { values: ['json'] },
debug: { values: ['true'] },
},
header: {},
cookie: {},
+1 -1
View File
@@ -37,7 +37,7 @@ async function Layout({ page, children }: { page: Page; children: ReactNode }) {
const { toc } = await page.data.load();
return (
<DocsLayout {...baseOptions()} tree={source.pageTree}>
<DocsLayout {...baseOptions()} tree={source.getPageTree()}>
<DocsPage toc={toc}>{children}</DocsPage>
</DocsLayout>
);
+12
View File
@@ -1,5 +1,17 @@
# fumadocs-python
## 0.0.7
### Patch Changes
- b16a32f: Switch to tsdown for bundling
- Updated dependencies [590d36a]
- Updated dependencies [98d38ff]
- Updated dependencies [446631d]
- Updated dependencies [b16a32f]
- fumadocs-core@16.4.2
- fumadocs-ui@16.4.2
## 0.0.6
### Patch Changes
@@ -162,7 +162,6 @@ export function DocsLayout(props: DocsLayoutProps) {
)}
</div>
)}
{nav.children}
{tabs.length > 0 && (
<SidebarTabsDropdown
options={tabs}
@@ -315,6 +314,7 @@ function DocsNavbar({
navMode === 'auto' && 'md:hidden',
),
})}
{nav.children}
</div>
{searchToggle.enabled !== false &&
(searchToggle.components?.lg ? (
@@ -343,7 +343,6 @@ function DocsNavbar({
<NavbarLinkItem key={i} item={item} />
))}
</div>
{nav.children}
{links
.filter((item) => item.type === 'icon')
.map((item, i) => (
+12 -4
View File
@@ -1,5 +1,13 @@
# fumadocs-twoslash
## 3.1.11
### Patch Changes
- b16a32f: Switch to tsdown for bundling
- Updated dependencies [b16a32f]
- fumadocs-ui@16.4.2
## 3.1.10
### Patch Changes
@@ -166,17 +174,17 @@
Before:
```ts
import 'fumadocs-ui/twoslash.css';
import "fumadocs-ui/twoslash.css";
import { Popup } from 'fumadocs-ui/twoslash/popup';
import { Popup } from "fumadocs-ui/twoslash/popup";
```
After:
```ts
import 'fumadocs-twoslash/twoslash.css';
import "fumadocs-twoslash/twoslash.css";
import { Popup } from 'fumadocs-twoslash/ui';
import { Popup } from "fumadocs-twoslash/ui";
```
**Tailwind CSS is now required for Twoslash integration.**
+18 -5
View File
@@ -1,5 +1,18 @@
# fumadocs-typescript
## 5.0.1
### Patch Changes
- 689d31e: Improve error message
- b16a32f: Switch to tsdown for bundling
- Updated dependencies [590d36a]
- Updated dependencies [98d38ff]
- Updated dependencies [446631d]
- Updated dependencies [b16a32f]
- fumadocs-core@16.4.2
- fumadocs-ui@16.4.2
## 5.0.0
### Major Changes
@@ -24,11 +37,11 @@
import {
createGenerator,
createFileSystemGeneratorCache,
} from 'fumadocs-typescript';
} from "fumadocs-typescript";
const generator = createGenerator({
// add this!
cache: createFileSystemGeneratorCache('.next/fumadocs-typescript'),
cache: createFileSystemGeneratorCache(".next/fumadocs-typescript"),
});
```
@@ -179,7 +192,7 @@
Create a generator instance:
```ts
import { createGenerator } from 'fumadocs-typescript';
import { createGenerator } from "fumadocs-typescript";
const generator = createGenerator(tsconfig);
```
@@ -299,7 +312,7 @@
Add the `remarkDocGen` plugin to your remark plugins.
```ts
import { remarkDocGen, fileGenerator } from 'fumadocs-docgen';
import { remarkDocGen, fileGenerator } from "fumadocs-docgen";
remark().use(remarkDocGen, { generators: [fileGenerator()] });
```
@@ -329,7 +342,7 @@
For `remarkInstall`, it remains the same:
```ts
import { remarkInstall } from 'fumadocs-docgen';
import { remarkInstall } from "fumadocs-docgen";
```
## 1.0.2
+1 -1
View File
@@ -85,7 +85,7 @@ export function TOCItems({ ref, className, ...props }: ComponentProps<'div'>) {
>
<TocThumb
containerRef={containerRef}
className="absolute w-full top-(--fd-top) h-(--fd-height) bg-fd-primary transition-[top,height] duration-250"
className="absolute w-full top-(--fd-top) h-(--fd-height) bg-fd-primary transition-[top,height] ease-linear duration-250"
/>
</div>
)}
+1 -1
View File
@@ -22,7 +22,7 @@ export function TOCItems({ ref, className, ...props }: ComponentProps<'div'>) {
<>
<TocThumb
containerRef={containerRef}
className="absolute top-(--fd-top) h-(--fd-height) w-0.5 rounded-e-sm bg-fd-primary transition-[top,height] duration-250"
className="absolute top-(--fd-top) h-(--fd-height) w-0.5 rounded-e-sm bg-fd-primary transition-[top,height] ease-linear duration-250"
/>
<div
ref={mergeRefs(ref, containerRef)}