mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
UI: Simplify internal types
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'fumadocs-ui': patch
|
||||
'@fumadocs/base-ui': patch
|
||||
---
|
||||
|
||||
Simplify internal types
|
||||
@@ -155,18 +155,44 @@ return (
|
||||
```
|
||||
|
||||
```tsx tab="Replace Component" title="page.tsx"
|
||||
import { TOC } from './my-toc';
|
||||
import { TOCProvider, TOC, TOCPopover } from './my-toc';
|
||||
|
||||
return <DocsPage slots={{ toc: TOC }}>...</DocsPage>;
|
||||
return (
|
||||
<DocsPage
|
||||
slots={{
|
||||
toc: {
|
||||
provider: TOCProvider,
|
||||
main: TOC,
|
||||
popover: TOCPopover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
...
|
||||
</DocsPage>
|
||||
);
|
||||
```
|
||||
|
||||
```tsx tab="Replace Component" title="my-toc.tsx"
|
||||
'use client';
|
||||
import type { TOCProps } from 'fumadocs-ui/layouts/<layout>/page/slots/toc';
|
||||
import type {
|
||||
TOCProps,
|
||||
TOCPopoverProps,
|
||||
TOCProviderProps,
|
||||
} from 'fumadocs-ui/layouts/<layout>/page/slots/toc';
|
||||
|
||||
export function TOCProvider({ toc, children }: TOCProviderProps) {
|
||||
// receive TOC items, you can pass it down via React contexts
|
||||
console.log(toc);
|
||||
return children;
|
||||
}
|
||||
|
||||
export function TOC(props: TOCProps) {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
|
||||
export function TOCPopover(props: TOCPopoverProps) {
|
||||
return <div>Popover</div>;
|
||||
}
|
||||
```
|
||||
|
||||
<auto-type-table type="import('fumadocs-ui/layouts/docs/page').TOCMainProps" />
|
||||
|
||||
@@ -75,11 +75,6 @@ export const registry: Registry = {
|
||||
path: 'layouts/docs/page/index.tsx',
|
||||
target: '<dir>/docs/page/index.tsx',
|
||||
},
|
||||
{
|
||||
type: 'layout',
|
||||
path: 'layouts/docs/page/client.tsx',
|
||||
target: '<dir>/docs/page/client.tsx',
|
||||
},
|
||||
],
|
||||
unlisted: true,
|
||||
},
|
||||
@@ -96,11 +91,6 @@ export const registry: Registry = {
|
||||
path: 'layouts/flux/page/index.tsx',
|
||||
target: '<dir>/flux/page/index.tsx',
|
||||
},
|
||||
{
|
||||
type: 'layout',
|
||||
path: 'layouts/flux/page/client.tsx',
|
||||
target: '<dir>/flux/page/client.tsx',
|
||||
},
|
||||
],
|
||||
unlisted: true,
|
||||
},
|
||||
@@ -122,11 +112,6 @@ export const registry: Registry = {
|
||||
path: 'layouts/notebook/page/index.tsx',
|
||||
target: '<dir>/notebook/page/index.tsx',
|
||||
},
|
||||
{
|
||||
type: 'layout',
|
||||
path: 'layouts/notebook/page/client.tsx',
|
||||
target: '<dir>/notebook/page/client.tsx',
|
||||
},
|
||||
],
|
||||
unlisted: true,
|
||||
},
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import {
|
||||
TOC,
|
||||
TOCPopover,
|
||||
TOCProvider,
|
||||
type TOCProviderProps,
|
||||
type TOCPopoverProps,
|
||||
type TOCProps,
|
||||
} from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
slots?: DocsPageSlots;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
tableOfContentPopover?: TableOfContentPopoverOptions;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentPopoverOptions extends TOCPopoverProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.tocPopover` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
export interface DocsPageSlots {
|
||||
toc?: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main?: FC<TOCProps>;
|
||||
popover?: FC<TOCPopoverProps>;
|
||||
};
|
||||
container?: FC<ComponentProps<'article'>>;
|
||||
footer?: FC<FooterProps>;
|
||||
breadcrumb?: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
interface PageSlotsProps extends Pick<DocsPageProps, 'full' | 'breadcrumb' | 'footer'> {
|
||||
tableOfContent: TOCProps & { component?: ReactNode };
|
||||
tableOfContentPopover: TOCPopoverProps & { component?: ReactNode };
|
||||
}
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/docs/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
tableOfContentPopover: { enabled: tocPopoverEnabled, ...tocPopoverProps } = {},
|
||||
footer = {},
|
||||
breadcrumb = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(!full && (toc.length > 0 || tocProps.footer || tocProps.header));
|
||||
tocPopoverEnabled ??= Boolean(toc.length > 0 || tocPopoverProps.header || tocPopoverProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb:
|
||||
breadcrumb.enabled !== false ? (defaultSlots.breadcrumb ?? InlineBreadcrumb) : undefined,
|
||||
footer: footer.enabled !== false ? (defaultSlots.footer ?? InlineFooter) : undefined,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: tocEnabled ? InlineTOC : undefined,
|
||||
popover: tocPopoverEnabled ? InlineTOCPopover : undefined,
|
||||
},
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
let content = (
|
||||
<>
|
||||
{slots.toc?.popover && <slots.toc.popover />}
|
||||
{slots.container && (
|
||||
<slots.container {...containerProps}>
|
||||
{slots.breadcrumb && <slots.breadcrumb />}
|
||||
{children}
|
||||
{slots.footer && <slots.footer />}
|
||||
</slots.container>
|
||||
)}
|
||||
{slots.toc?.main && <slots.toc.main />}
|
||||
</>
|
||||
);
|
||||
|
||||
if (slots.toc)
|
||||
content = (
|
||||
<slots.toc.provider single={single} toc={tocEnabled || tocPopoverEnabled ? toc : []}>
|
||||
{content}
|
||||
</slots.toc.provider>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: {
|
||||
full,
|
||||
tableOfContent: tocProps,
|
||||
tableOfContentPopover: tocPopoverProps,
|
||||
footer,
|
||||
breadcrumb,
|
||||
},
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
function InlineBreadcrumb(props: BreadcrumbProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.breadcrumb ?? {};
|
||||
if (component) return component;
|
||||
return <Breadcrumb {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineFooter(props: FooterProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.footer ?? {};
|
||||
if (component) return component;
|
||||
return <Footer {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineTOCPopover(props: TOCPopoverProps) {
|
||||
const { tableOfContentPopover } = useDocsPage().props;
|
||||
if (tableOfContentPopover.component) return tableOfContentPopover.component;
|
||||
return <TOCPopover {...props} {...tableOfContentPopover} />;
|
||||
}
|
||||
|
||||
function InlineTOC(props: TOCProps) {
|
||||
const { tableOfContent } = useDocsPage().props;
|
||||
if (tableOfContent.component) return tableOfContent.component;
|
||||
return <TOC {...props} {...tableOfContent} />;
|
||||
}
|
||||
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,151 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
'use client';
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { I18nLabel, useI18n } from '@/contexts/i18n';
|
||||
import {
|
||||
TOC,
|
||||
TOCPopover,
|
||||
TOCProvider,
|
||||
type TOCProviderProps,
|
||||
type TOCPopoverProps,
|
||||
type TOCProps,
|
||||
} from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { Edit } from 'lucide-react';
|
||||
import { I18nLabel } from '@/contexts/i18n';
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
slots?: Partial<DocsPageSlots>;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
tableOfContentPopover?: TableOfContentPopoverOptions;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentPopoverOptions extends TOCPopoverProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.tocPopover` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface DocsPageSlots {
|
||||
toc: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main: FC<TOCProps>;
|
||||
popover: FC<TOCPopoverProps>;
|
||||
};
|
||||
container: FC<ComponentProps<'article'>>;
|
||||
footer: FC<FooterProps>;
|
||||
breadcrumb: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
type PageSlotsProps = Pick<DocsPageProps, 'full'>;
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/docs/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
tableOfContentPopover: { enabled: tocPopoverEnabled, ...tocPopoverProps } = {},
|
||||
breadcrumb: { enabled: breadcrumbEnabled = true, ...breadcrumb } = {},
|
||||
footer: { enabled: footerEnabled = true, ...footer } = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(!full && (toc.length > 0 || tocProps.footer || tocProps.header));
|
||||
tocPopoverEnabled ??= Boolean(toc.length > 0 || tocPopoverProps.header || tocPopoverProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb: defaultSlots.breadcrumb ?? Breadcrumb,
|
||||
footer: defaultSlots.footer ?? Footer,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: TOC,
|
||||
popover: TOCPopover,
|
||||
},
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: { full },
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
<slots.toc.provider single={single} toc={tocEnabled || tocPopoverEnabled ? toc : []}>
|
||||
{tocPopoverEnabled &&
|
||||
(tocPopoverProps.component ?? <slots.toc.popover {...tocPopoverProps} />)}
|
||||
<slots.container {...containerProps}>
|
||||
{breadcrumbEnabled && (breadcrumb.component ?? <slots.breadcrumb {...breadcrumb} />)}
|
||||
{children}
|
||||
{footerEnabled && (footer.component ?? <slots.footer {...footer} />)}
|
||||
</slots.container>
|
||||
{tocEnabled && (tocProps.component ?? <slots.toc.main {...tocProps} />)}
|
||||
</slots.toc.provider>
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
return (
|
||||
@@ -14,8 +157,8 @@ export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
buttonVariants({
|
||||
color: 'secondary',
|
||||
size: 'sm',
|
||||
className: 'gap-1.5 not-prose',
|
||||
}),
|
||||
'gap-1.5 not-prose',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@@ -59,13 +202,25 @@ export function DocsTitle({ children, className, ...props }: ComponentProps<'h1'
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
DocsPage,
|
||||
type DocsPageSlots,
|
||||
type DocsPageProps,
|
||||
PageLastUpdate,
|
||||
useDocsPage,
|
||||
} from './client';
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export { type BreadcrumbProps, Breadcrumb as PageBreadcrumb } from './slots/breadcrumb';
|
||||
export { type FooterProps, Footer as PageFooter } from './slots/footer';
|
||||
export { MarkdownCopyButton, ViewOptionsPopover } from '@/layouts/shared/page-actions';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useTreePath, useTreeContext } from '@/contexts/tree';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { type BreadcrumbOptions, getBreadcrumbItemsFromPath } from 'fumadocs-core/breadcrumb';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsPage } from '../client';
|
||||
import { useDocsPage } from '..';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
export function Container(props: ComponentProps<'article'>) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { isActive } from '@/utils/urls';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'use client';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsLayout } from '..';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsLayout } from '../client';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { TOC, type TOCProps } from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { TOCProvider, type TOCProviderProps } from '@/components/toc';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
|
||||
export interface DocsPageSlots {
|
||||
toc?: FC<TOCProps>;
|
||||
container?: FC<ComponentProps<'article'>>;
|
||||
tocProvider?: FC<TOCProviderProps>;
|
||||
footer?: FC<FooterProps>;
|
||||
breadcrumb?: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
children?: ReactNode;
|
||||
slots?: DocsPageSlots;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface PageSlotsProps extends Pick<DocsPageProps, 'full' | 'footer' | 'breadcrumb'> {
|
||||
tableOfContent: TOCProps & { component?: ReactNode };
|
||||
}
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/flux/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single = false, ...tocProps } = {},
|
||||
footer = {},
|
||||
breadcrumb = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(toc.length > 0 || tocProps.header || tocProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb:
|
||||
breadcrumb.enabled !== false ? (defaultSlots.breadcrumb ?? InlineBreadcrumb) : undefined,
|
||||
footer: footer.enabled !== false ? (defaultSlots.footer ?? InlineFooter) : undefined,
|
||||
toc: tocEnabled ? (defaultSlots.toc ?? InlineTOC) : undefined,
|
||||
tocProvider: defaultSlots.tocProvider ?? TOCProvider,
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
let content = (
|
||||
<>
|
||||
{slots.toc && <slots.toc />}
|
||||
{slots.container && (
|
||||
<slots.container {...containerProps}>
|
||||
{slots.breadcrumb && <slots.breadcrumb />}
|
||||
{children}
|
||||
{slots.footer && <slots.footer />}
|
||||
</slots.container>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
if (slots.tocProvider)
|
||||
content = (
|
||||
<slots.tocProvider single={single} toc={tocEnabled ? toc : []}>
|
||||
{content}
|
||||
</slots.tocProvider>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: {
|
||||
full,
|
||||
tableOfContent: tocProps,
|
||||
footer,
|
||||
breadcrumb,
|
||||
},
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
function InlineBreadcrumb(props: BreadcrumbProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.breadcrumb ?? {};
|
||||
if (component) return component;
|
||||
return <Breadcrumb {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineFooter(props: FooterProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.footer ?? {};
|
||||
if (component) return component;
|
||||
return <Footer {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineTOC(props: TOCProps) {
|
||||
const { tableOfContent } = useDocsPage().props;
|
||||
if (tableOfContent.component) return tableOfContent.component;
|
||||
return <TOC {...props} {...tableOfContent} />;
|
||||
}
|
||||
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,130 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
'use client';
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { I18nLabel, useI18n } from '@/contexts/i18n';
|
||||
import { TOC, TOCProvider, type TOCProviderProps, type TOCProps } from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { Edit } from 'lucide-react';
|
||||
import { I18nLabel } from '@/contexts/i18n';
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
children?: ReactNode;
|
||||
slots?: Partial<DocsPageSlots>;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface DocsPageSlots {
|
||||
toc: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main: FC<TOCProps>;
|
||||
};
|
||||
container: FC<ComponentProps<'article'>>;
|
||||
footer: FC<FooterProps>;
|
||||
breadcrumb: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
type PageSlotsProps = Pick<DocsPageProps, 'full'>;
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/flux/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
breadcrumb: { enabled: breadcrumbEnabled = true, ...breadcrumb } = {},
|
||||
footer: { enabled: footerEnabled = true, ...footer } = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(toc.length > 0 || tocProps.header || tocProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb: defaultSlots.breadcrumb ?? Breadcrumb,
|
||||
footer: defaultSlots.footer ?? Footer,
|
||||
container: defaultSlots.container ?? Container,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: TOC,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: { full },
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
<slots.toc.provider single={single} toc={tocEnabled ? toc : []}>
|
||||
{tocEnabled && (tocProps.component ?? <slots.toc.main {...tocProps} />)}
|
||||
<slots.container {...containerProps}>
|
||||
{breadcrumbEnabled && (breadcrumb.component ?? <slots.breadcrumb {...breadcrumb} />)}
|
||||
{children}
|
||||
{footerEnabled && (footer.component ?? <slots.footer {...footer} />)}
|
||||
</slots.container>
|
||||
</slots.toc.provider>
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
return (
|
||||
@@ -14,8 +136,8 @@ export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
buttonVariants({
|
||||
color: 'secondary',
|
||||
size: 'sm',
|
||||
className: 'gap-1.5 not-prose',
|
||||
}),
|
||||
'gap-1.5 not-prose',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@@ -59,13 +181,25 @@ export function DocsTitle({ children, className, ...props }: ComponentProps<'h1'
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
DocsPage,
|
||||
type DocsPageSlots,
|
||||
type DocsPageProps,
|
||||
PageLastUpdate,
|
||||
useDocsPage,
|
||||
} from './client';
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export { type FooterProps, Footer as PageFooter } from './slots/footer';
|
||||
export { type BreadcrumbProps, Breadcrumb as PageBreadcrumb } from './slots/breadcrumb';
|
||||
export { MarkdownCopyButton, ViewOptionsPopover } from '@/layouts/shared/page-actions';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useTreeContext, useTreePath } from '@/contexts/tree';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { type BreadcrumbOptions, getBreadcrumbItemsFromPath } from 'fumadocs-core/breadcrumb';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useDocsPage } from '../client';
|
||||
import { useDocsPage } from '..';
|
||||
|
||||
export function Container(props: ComponentProps<'article'>) {
|
||||
const {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { isActive } from '@/utils/urls';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { TOCScrollArea, useTOCItems } from '@/components/toc';
|
||||
import * as Base from '@/components/toc';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { useTreePath } from '@/contexts/tree';
|
||||
import { cn } from '@/utils/cn';
|
||||
@@ -28,6 +28,10 @@ const TocPopoverContext = createContext<{
|
||||
setOpen: (open: boolean) => void;
|
||||
} | null>(null);
|
||||
|
||||
export type TOCProviderProps = Base.TOCProviderProps;
|
||||
|
||||
export const { TOCProvider } = Base;
|
||||
|
||||
export interface TOCProps {
|
||||
container?: ComponentProps<'div'>;
|
||||
trigger?: ComponentProps<'button'>;
|
||||
@@ -54,9 +58,9 @@ export function TOC({ container, trigger, content, header, footer, style }: TOCP
|
||||
<PageTOCPopover {...container}>
|
||||
<PageTOCPopoverContent {...content}>
|
||||
{header}
|
||||
<TOCScrollArea>
|
||||
<Base.TOCScrollArea>
|
||||
{style === 'clerk' ? <TocClerk.TOCItems /> : <TocDefault.TOCItems />}
|
||||
</TOCScrollArea>
|
||||
</Base.TOCScrollArea>
|
||||
{footer}
|
||||
</PageTOCPopoverContent>
|
||||
<PageTOCPopoverTrigger {...trigger} />
|
||||
@@ -129,7 +133,7 @@ function PageTOCPopoverPhysical({ className, children, ...rest }: ComponentProps
|
||||
function PageTOCPopoverTrigger({ className, ...props }: ComponentProps<'button'>) {
|
||||
const { text } = useI18n();
|
||||
const { open } = use(TocPopoverContext)!;
|
||||
const items = useTOCItems();
|
||||
const items = Base.useTOCItems();
|
||||
const active = useActiveAnchor();
|
||||
const selected = useMemo(
|
||||
() => items.findIndex((item) => active === item.url.slice(1)),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import {
|
||||
TOC,
|
||||
TOCPopover,
|
||||
TOCProvider,
|
||||
type TOCProviderProps,
|
||||
type TOCPopoverProps,
|
||||
type TOCProps,
|
||||
} from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
|
||||
export interface DocsPageSlots {
|
||||
toc?: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main?: FC<TOCProps>;
|
||||
popover?: FC<TOCPopoverProps>;
|
||||
};
|
||||
container?: FC<ComponentProps<'article'>>;
|
||||
footer?: FC<FooterProps>;
|
||||
breadcrumb?: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
slots?: DocsPageSlots;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
tableOfContentPopover?: TableOfContentPopoverOptions;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentPopoverOptions extends TOCPopoverProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.tocPopover` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface PageSlotsProps extends Pick<DocsPageProps, 'full' | 'breadcrumb' | 'footer'> {
|
||||
tableOfContent: TOCProps & { component?: ReactNode };
|
||||
tableOfContentPopover: TOCPopoverProps & { component?: ReactNode };
|
||||
}
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/notebook/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
tableOfContentPopover: { enabled: tocPopoverEnabled, ...tocPopoverProps } = {},
|
||||
footer = {},
|
||||
breadcrumb = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(!full && (toc.length > 0 || tocProps.footer || tocProps.header));
|
||||
tocPopoverEnabled ??= Boolean(toc.length > 0 || tocPopoverProps.header || tocPopoverProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb:
|
||||
breadcrumb.enabled !== false ? (defaultSlots.breadcrumb ?? InlineBreadcrumb) : undefined,
|
||||
footer: footer.enabled !== false ? (defaultSlots.footer ?? InlineFooter) : undefined,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: tocEnabled ? InlineTOC : undefined,
|
||||
popover: tocPopoverEnabled ? InlineTOCPopover : undefined,
|
||||
},
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
let content = (
|
||||
<>
|
||||
{slots.toc?.popover && <slots.toc.popover />}
|
||||
{slots.container && (
|
||||
<slots.container {...containerProps}>
|
||||
{slots.breadcrumb && <slots.breadcrumb />}
|
||||
{children}
|
||||
{slots.footer && <slots.footer />}
|
||||
</slots.container>
|
||||
)}
|
||||
{slots.toc?.main && <slots.toc.main />}
|
||||
</>
|
||||
);
|
||||
|
||||
if (slots.toc)
|
||||
content = (
|
||||
<slots.toc.provider single={single} toc={tocEnabled || tocPopoverEnabled ? toc : []}>
|
||||
{content}
|
||||
</slots.toc.provider>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: {
|
||||
full,
|
||||
tableOfContent: tocProps,
|
||||
tableOfContentPopover: tocPopoverProps,
|
||||
footer,
|
||||
breadcrumb,
|
||||
},
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
function InlineBreadcrumb(props: BreadcrumbProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.breadcrumb ?? {};
|
||||
if (component) return component;
|
||||
return <Breadcrumb {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineFooter(props: FooterProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.footer ?? {};
|
||||
if (component) return component;
|
||||
return <Footer {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineTOCPopover(props: TOCPopoverProps) {
|
||||
const { tableOfContentPopover } = useDocsPage().props;
|
||||
if (tableOfContentPopover.component) return tableOfContentPopover.component;
|
||||
return <TOCPopover {...props} {...tableOfContentPopover} />;
|
||||
}
|
||||
|
||||
function InlineTOC(props: TOCProps) {
|
||||
const { tableOfContent } = useDocsPage().props;
|
||||
if (tableOfContent.component) return tableOfContent.component;
|
||||
return <TOC {...props} {...tableOfContent} />;
|
||||
}
|
||||
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,150 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
'use client';
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { I18nLabel, useI18n } from '@/contexts/i18n';
|
||||
import {
|
||||
TOC,
|
||||
TOCPopover,
|
||||
TOCProvider,
|
||||
type TOCProviderProps,
|
||||
type TOCPopoverProps,
|
||||
type TOCProps,
|
||||
} from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { Edit } from 'lucide-react';
|
||||
import { I18nLabel } from '@/contexts/i18n';
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
slots?: Partial<DocsPageSlots>;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
tableOfContentPopover?: TableOfContentPopoverOptions;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentPopoverOptions extends TOCPopoverProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.tocPopover` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface DocsPageSlots {
|
||||
toc: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main: FC<TOCProps>;
|
||||
popover: FC<TOCPopoverProps>;
|
||||
};
|
||||
container: FC<ComponentProps<'article'>>;
|
||||
footer: FC<FooterProps>;
|
||||
breadcrumb: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
type PageSlotsProps = Pick<DocsPageProps, 'full'>;
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/notebook/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
tableOfContentPopover: { enabled: tocPopoverEnabled, ...tocPopoverProps } = {},
|
||||
breadcrumb: { enabled: breadcrumbEnabled = true, ...breadcrumb } = {},
|
||||
footer: { enabled: footerEnabled = true, ...footer } = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(!full && (toc.length > 0 || tocProps.footer || tocProps.header));
|
||||
tocPopoverEnabled ??= Boolean(toc.length > 0 || tocPopoverProps.header || tocPopoverProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb: defaultSlots.breadcrumb ?? Breadcrumb,
|
||||
footer: defaultSlots.footer ?? Footer,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: TOC,
|
||||
popover: TOCPopover,
|
||||
},
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: { full },
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
<slots.toc.provider single={single} toc={tocEnabled || tocPopoverEnabled ? toc : []}>
|
||||
{tocPopoverEnabled &&
|
||||
(tocPopoverProps.component ?? <slots.toc.popover {...tocPopoverProps} />)}
|
||||
<slots.container {...containerProps}>
|
||||
{breadcrumbEnabled && (breadcrumb.component ?? <slots.breadcrumb {...breadcrumb} />)}
|
||||
{children}
|
||||
{footerEnabled && (footer.component ?? <slots.footer {...footer} />)}
|
||||
</slots.container>
|
||||
{tocEnabled && (tocProps.component ?? <slots.toc.main {...tocProps} />)}
|
||||
</slots.toc.provider>
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
return (
|
||||
@@ -14,8 +156,8 @@ export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
buttonVariants({
|
||||
color: 'secondary',
|
||||
size: 'sm',
|
||||
className: 'gap-1.5 not-prose',
|
||||
}),
|
||||
'gap-1.5 not-prose',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@@ -59,13 +201,25 @@ export function DocsTitle({ children, className, ...props }: ComponentProps<'h1'
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
DocsPage,
|
||||
type DocsPageProps,
|
||||
type DocsPageSlots,
|
||||
PageLastUpdate,
|
||||
useDocsPage,
|
||||
} from './client';
|
||||
export { type BreadcrumbProps, Breadcrumb as PageBreadcrumb } from './slots/breadcrumb';
|
||||
export { type FooterProps, Footer as PageFooter } from './slots/footer';
|
||||
export { MarkdownCopyButton, ViewOptionsPopover } from '@/layouts/shared/page-actions';
|
||||
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useTreeContext, useTreePath } from '@/contexts/tree';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { type BreadcrumbOptions, getBreadcrumbItemsFromPath } from 'fumadocs-core/breadcrumb';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsPage } from '../client';
|
||||
import { useDocsPage } from '..';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
export function Container(props: ComponentProps<'article'>) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { isActive } from '@/utils/urls';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useNotebookLayout } from '../client';
|
||||
|
||||
@@ -75,11 +75,6 @@ export const registry: Registry = {
|
||||
path: 'layouts/docs/page/index.tsx',
|
||||
target: '<dir>/docs/page/index.tsx',
|
||||
},
|
||||
{
|
||||
type: 'layout',
|
||||
path: 'layouts/docs/page/client.tsx',
|
||||
target: '<dir>/docs/page/client.tsx',
|
||||
},
|
||||
],
|
||||
unlisted: true,
|
||||
},
|
||||
@@ -96,11 +91,6 @@ export const registry: Registry = {
|
||||
path: 'layouts/flux/page/index.tsx',
|
||||
target: '<dir>/flux/page/index.tsx',
|
||||
},
|
||||
{
|
||||
type: 'layout',
|
||||
path: 'layouts/flux/page/client.tsx',
|
||||
target: '<dir>/flux/page/client.tsx',
|
||||
},
|
||||
],
|
||||
unlisted: true,
|
||||
},
|
||||
@@ -122,11 +112,6 @@ export const registry: Registry = {
|
||||
path: 'layouts/notebook/page/index.tsx',
|
||||
target: '<dir>/notebook/page/index.tsx',
|
||||
},
|
||||
{
|
||||
type: 'layout',
|
||||
path: 'layouts/notebook/page/client.tsx',
|
||||
target: '<dir>/notebook/page/client.tsx',
|
||||
},
|
||||
],
|
||||
unlisted: true,
|
||||
},
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import {
|
||||
TOC,
|
||||
TOCPopover,
|
||||
TOCProvider,
|
||||
type TOCProviderProps,
|
||||
type TOCPopoverProps,
|
||||
type TOCProps,
|
||||
} from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
slots?: DocsPageSlots;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
tableOfContentPopover?: TableOfContentPopoverOptions;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentPopoverOptions extends TOCPopoverProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.tocPopover` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
export interface DocsPageSlots {
|
||||
toc?: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main?: FC<TOCProps>;
|
||||
popover?: FC<TOCPopoverProps>;
|
||||
};
|
||||
container?: FC<ComponentProps<'article'>>;
|
||||
footer?: FC<FooterProps>;
|
||||
breadcrumb?: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
interface PageSlotsProps extends Pick<DocsPageProps, 'full' | 'breadcrumb' | 'footer'> {
|
||||
tableOfContent: TOCProps & { component?: ReactNode };
|
||||
tableOfContentPopover: TOCPopoverProps & { component?: ReactNode };
|
||||
}
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/docs/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
tableOfContentPopover: { enabled: tocPopoverEnabled, ...tocPopoverProps } = {},
|
||||
footer = {},
|
||||
breadcrumb = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(!full && (toc.length > 0 || tocProps.footer || tocProps.header));
|
||||
tocPopoverEnabled ??= Boolean(toc.length > 0 || tocPopoverProps.header || tocPopoverProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb:
|
||||
breadcrumb.enabled !== false ? (defaultSlots.breadcrumb ?? InlineBreadcrumb) : undefined,
|
||||
footer: footer.enabled !== false ? (defaultSlots.footer ?? InlineFooter) : undefined,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: tocEnabled ? InlineTOC : undefined,
|
||||
popover: tocPopoverEnabled ? InlineTOCPopover : undefined,
|
||||
},
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
let content = (
|
||||
<>
|
||||
{slots.toc?.popover && <slots.toc.popover />}
|
||||
{slots.container && (
|
||||
<slots.container {...containerProps}>
|
||||
{slots.breadcrumb && <slots.breadcrumb />}
|
||||
{children}
|
||||
{slots.footer && <slots.footer />}
|
||||
</slots.container>
|
||||
)}
|
||||
{slots.toc?.main && <slots.toc.main />}
|
||||
</>
|
||||
);
|
||||
|
||||
if (slots.toc)
|
||||
content = (
|
||||
<slots.toc.provider single={single} toc={tocEnabled || tocPopoverEnabled ? toc : []}>
|
||||
{content}
|
||||
</slots.toc.provider>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: {
|
||||
full,
|
||||
tableOfContent: tocProps,
|
||||
tableOfContentPopover: tocPopoverProps,
|
||||
footer,
|
||||
breadcrumb,
|
||||
},
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
function InlineBreadcrumb(props: BreadcrumbProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.breadcrumb ?? {};
|
||||
if (component) return component;
|
||||
return <Breadcrumb {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineFooter(props: FooterProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.footer ?? {};
|
||||
if (component) return component;
|
||||
return <Footer {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineTOCPopover(props: TOCPopoverProps) {
|
||||
const { tableOfContentPopover } = useDocsPage().props;
|
||||
if (tableOfContentPopover.component) return tableOfContentPopover.component;
|
||||
return <TOCPopover {...props} {...tableOfContentPopover} />;
|
||||
}
|
||||
|
||||
function InlineTOC(props: TOCProps) {
|
||||
const { tableOfContent } = useDocsPage().props;
|
||||
if (tableOfContent.component) return tableOfContent.component;
|
||||
return <TOC {...props} {...tableOfContent} />;
|
||||
}
|
||||
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,151 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
'use client';
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { I18nLabel, useI18n } from '@/contexts/i18n';
|
||||
import {
|
||||
TOC,
|
||||
TOCPopover,
|
||||
TOCProvider,
|
||||
type TOCProviderProps,
|
||||
type TOCPopoverProps,
|
||||
type TOCProps,
|
||||
} from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { Edit } from 'lucide-react';
|
||||
import { I18nLabel } from '@/contexts/i18n';
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
slots?: Partial<DocsPageSlots>;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
tableOfContentPopover?: TableOfContentPopoverOptions;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentPopoverOptions extends TOCPopoverProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.tocPopover` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface DocsPageSlots {
|
||||
toc: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main: FC<TOCProps>;
|
||||
popover: FC<TOCPopoverProps>;
|
||||
};
|
||||
container: FC<ComponentProps<'article'>>;
|
||||
footer: FC<FooterProps>;
|
||||
breadcrumb: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
type PageSlotsProps = Pick<DocsPageProps, 'full'>;
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/docs/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
tableOfContentPopover: { enabled: tocPopoverEnabled, ...tocPopoverProps } = {},
|
||||
breadcrumb: { enabled: breadcrumbEnabled = true, ...breadcrumb } = {},
|
||||
footer: { enabled: footerEnabled = true, ...footer } = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(!full && (toc.length > 0 || tocProps.footer || tocProps.header));
|
||||
tocPopoverEnabled ??= Boolean(toc.length > 0 || tocPopoverProps.header || tocPopoverProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb: defaultSlots.breadcrumb ?? Breadcrumb,
|
||||
footer: defaultSlots.footer ?? Footer,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: TOC,
|
||||
popover: TOCPopover,
|
||||
},
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: { full },
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
<slots.toc.provider single={single} toc={tocEnabled || tocPopoverEnabled ? toc : []}>
|
||||
{tocPopoverEnabled &&
|
||||
(tocPopoverProps.component ?? <slots.toc.popover {...tocPopoverProps} />)}
|
||||
<slots.container {...containerProps}>
|
||||
{breadcrumbEnabled && (breadcrumb.component ?? <slots.breadcrumb {...breadcrumb} />)}
|
||||
{children}
|
||||
{footerEnabled && (footer.component ?? <slots.footer {...footer} />)}
|
||||
</slots.container>
|
||||
{tocEnabled && (tocProps.component ?? <slots.toc.main {...tocProps} />)}
|
||||
</slots.toc.provider>
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
return (
|
||||
@@ -14,8 +157,8 @@ export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
buttonVariants({
|
||||
color: 'secondary',
|
||||
size: 'sm',
|
||||
className: 'gap-1.5 not-prose',
|
||||
}),
|
||||
'gap-1.5 not-prose',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@@ -59,13 +202,25 @@ export function DocsTitle({ children, className, ...props }: ComponentProps<'h1'
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
DocsPage,
|
||||
type DocsPageSlots,
|
||||
type DocsPageProps,
|
||||
PageLastUpdate,
|
||||
useDocsPage,
|
||||
} from './client';
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export { type BreadcrumbProps, Breadcrumb as PageBreadcrumb } from './slots/breadcrumb';
|
||||
export { type FooterProps, Footer as PageFooter } from './slots/footer';
|
||||
export { MarkdownCopyButton, ViewOptionsPopover } from '@/layouts/shared/page-actions';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useTreePath, useTreeContext } from '@/contexts/tree';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { type BreadcrumbOptions, getBreadcrumbItemsFromPath } from 'fumadocs-core/breadcrumb';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsPage } from '../client';
|
||||
import { useDocsPage } from '..';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
export function Container(props: ComponentProps<'article'>) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { isActive } from '@/utils/urls';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsLayout } from '..';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsLayout } from '../client';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { TOC, type TOCProps } from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { TOCProvider, type TOCProviderProps } from '@/components/toc';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
|
||||
export interface DocsPageSlots {
|
||||
toc?: FC<TOCProps>;
|
||||
container?: FC<ComponentProps<'article'>>;
|
||||
tocProvider?: FC<TOCProviderProps>;
|
||||
footer?: FC<FooterProps>;
|
||||
breadcrumb?: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
children?: ReactNode;
|
||||
slots?: DocsPageSlots;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface PageSlotsProps extends Pick<DocsPageProps, 'full' | 'footer' | 'breadcrumb'> {
|
||||
tableOfContent: TOCProps & { component?: ReactNode };
|
||||
}
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/flux/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
footer = {},
|
||||
breadcrumb = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(toc.length > 0 || tocProps.header || tocProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb:
|
||||
breadcrumb.enabled !== false ? (defaultSlots.breadcrumb ?? InlineBreadcrumb) : undefined,
|
||||
footer: footer.enabled !== false ? (defaultSlots.footer ?? InlineFooter) : undefined,
|
||||
toc: tocEnabled ? (defaultSlots.toc ?? InlineTOC) : undefined,
|
||||
tocProvider: defaultSlots.tocProvider ?? TOCProvider,
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
let content = (
|
||||
<>
|
||||
{slots.toc && <slots.toc />}
|
||||
{slots.container && (
|
||||
<slots.container {...containerProps}>
|
||||
{slots.breadcrumb && <slots.breadcrumb />}
|
||||
{children}
|
||||
{slots.footer && <slots.footer />}
|
||||
</slots.container>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
if (slots.tocProvider)
|
||||
content = (
|
||||
<slots.tocProvider single={single} toc={tocEnabled ? toc : []}>
|
||||
{content}
|
||||
</slots.tocProvider>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: {
|
||||
full,
|
||||
tableOfContent: tocProps,
|
||||
footer,
|
||||
breadcrumb,
|
||||
},
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
function InlineBreadcrumb(props: BreadcrumbProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.breadcrumb ?? {};
|
||||
if (component) return component;
|
||||
return <Breadcrumb {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineFooter(props: FooterProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.footer ?? {};
|
||||
if (component) return component;
|
||||
return <Footer {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineTOC(props: TOCProps) {
|
||||
const { tableOfContent } = useDocsPage().props;
|
||||
if (tableOfContent.component) return tableOfContent.component;
|
||||
return <TOC {...props} {...tableOfContent} />;
|
||||
}
|
||||
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,130 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
'use client';
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { I18nLabel, useI18n } from '@/contexts/i18n';
|
||||
import { TOC, TOCProvider, type TOCProviderProps, type TOCProps } from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { Edit } from 'lucide-react';
|
||||
import { I18nLabel } from '@/contexts/i18n';
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
children?: ReactNode;
|
||||
slots?: Partial<DocsPageSlots>;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface DocsPageSlots {
|
||||
toc: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main: FC<TOCProps>;
|
||||
};
|
||||
container: FC<ComponentProps<'article'>>;
|
||||
footer: FC<FooterProps>;
|
||||
breadcrumb: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
type PageSlotsProps = Pick<DocsPageProps, 'full'>;
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/flux/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
breadcrumb: { enabled: breadcrumbEnabled = true, ...breadcrumb } = {},
|
||||
footer: { enabled: footerEnabled = true, ...footer } = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(toc.length > 0 || tocProps.header || tocProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb: defaultSlots.breadcrumb ?? Breadcrumb,
|
||||
footer: defaultSlots.footer ?? Footer,
|
||||
container: defaultSlots.container ?? Container,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: TOC,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: { full },
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
<slots.toc.provider single={single} toc={tocEnabled ? toc : []}>
|
||||
{tocEnabled && (tocProps.component ?? <slots.toc.main {...tocProps} />)}
|
||||
<slots.container {...containerProps}>
|
||||
{breadcrumbEnabled && (breadcrumb.component ?? <slots.breadcrumb {...breadcrumb} />)}
|
||||
{children}
|
||||
{footerEnabled && (footer.component ?? <slots.footer {...footer} />)}
|
||||
</slots.container>
|
||||
</slots.toc.provider>
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
return (
|
||||
@@ -14,8 +136,8 @@ export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
buttonVariants({
|
||||
color: 'secondary',
|
||||
size: 'sm',
|
||||
className: 'gap-1.5 not-prose',
|
||||
}),
|
||||
'gap-1.5 not-prose',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@@ -59,13 +181,25 @@ export function DocsTitle({ children, className, ...props }: ComponentProps<'h1'
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
DocsPage,
|
||||
type DocsPageSlots,
|
||||
type DocsPageProps,
|
||||
PageLastUpdate,
|
||||
useDocsPage,
|
||||
} from './client';
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export { type FooterProps, Footer as PageFooter } from './slots/footer';
|
||||
export { type BreadcrumbProps, Breadcrumb as PageBreadcrumb } from './slots/breadcrumb';
|
||||
export { MarkdownCopyButton, ViewOptionsPopover } from '@/layouts/shared/page-actions';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useTreeContext, useTreePath } from '@/contexts/tree';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { type BreadcrumbOptions, getBreadcrumbItemsFromPath } from 'fumadocs-core/breadcrumb';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useDocsPage } from '../client';
|
||||
import { useDocsPage } from '..';
|
||||
|
||||
export function Container(props: ComponentProps<'article'>) {
|
||||
const {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { isActive } from '@/utils/urls';
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { TOCScrollArea, useTOCItems } from '@/components/toc';
|
||||
import * as Base from '@/components/toc';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { useTreePath } from '@/contexts/tree';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import { useActiveAnchor } from 'fumadocs-core/toc';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import {
|
||||
type ComponentProps,
|
||||
@@ -28,6 +27,10 @@ const TocPopoverContext = createContext<{
|
||||
setOpen: (open: boolean) => void;
|
||||
} | null>(null);
|
||||
|
||||
export type TOCProviderProps = Base.TOCProviderProps;
|
||||
|
||||
export const { TOCProvider } = Base;
|
||||
|
||||
export interface TOCProps {
|
||||
container?: ComponentProps<'div'>;
|
||||
trigger?: ComponentProps<'button'>;
|
||||
@@ -54,9 +57,9 @@ export function TOC({ container, trigger, content, header, footer, style }: TOCP
|
||||
<PageTOCPopover {...container}>
|
||||
<PageTOCPopoverContent {...content}>
|
||||
{header}
|
||||
<TOCScrollArea>
|
||||
<Base.TOCScrollArea>
|
||||
{style === 'clerk' ? <TocClerk.TOCItems /> : <TocDefault.TOCItems />}
|
||||
</TOCScrollArea>
|
||||
</Base.TOCScrollArea>
|
||||
{footer}
|
||||
</PageTOCPopoverContent>
|
||||
<PageTOCPopoverTrigger {...trigger} />
|
||||
@@ -129,8 +132,8 @@ function PageTOCPopoverPhysical({ className, children, ...rest }: ComponentProps
|
||||
function PageTOCPopoverTrigger({ className, ...props }: ComponentProps<'button'>) {
|
||||
const { text } = useI18n();
|
||||
const { open } = use(TocPopoverContext)!;
|
||||
const items = useTOCItems();
|
||||
const active = useActiveAnchor();
|
||||
const items = Base.useTOCItems();
|
||||
const active = Base.useActiveAnchor();
|
||||
const selected = useMemo(
|
||||
() => items.findIndex((item) => active === item.url.slice(1)),
|
||||
[items, active],
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import {
|
||||
TOC,
|
||||
TOCPopover,
|
||||
TOCProvider,
|
||||
type TOCProviderProps,
|
||||
type TOCPopoverProps,
|
||||
type TOCProps,
|
||||
} from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
|
||||
export interface DocsPageSlots {
|
||||
toc?: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main?: FC<TOCProps>;
|
||||
popover?: FC<TOCPopoverProps>;
|
||||
};
|
||||
container?: FC<ComponentProps<'article'>>;
|
||||
footer?: FC<FooterProps>;
|
||||
breadcrumb?: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
slots?: DocsPageSlots;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
tableOfContentPopover?: TableOfContentPopoverOptions;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentPopoverOptions extends TOCPopoverProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.tocPopover` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface PageSlotsProps extends Pick<DocsPageProps, 'full' | 'breadcrumb' | 'footer'> {
|
||||
tableOfContent: TOCProps & { component?: ReactNode };
|
||||
tableOfContentPopover: TOCPopoverProps & { component?: ReactNode };
|
||||
}
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/notebook/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
tableOfContentPopover: { enabled: tocPopoverEnabled, ...tocPopoverProps } = {},
|
||||
footer = {},
|
||||
breadcrumb = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(!full && (toc.length > 0 || tocProps.footer || tocProps.header));
|
||||
tocPopoverEnabled ??= Boolean(toc.length > 0 || tocPopoverProps.header || tocPopoverProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb:
|
||||
breadcrumb.enabled !== false ? (defaultSlots.breadcrumb ?? InlineBreadcrumb) : undefined,
|
||||
footer: footer.enabled !== false ? (defaultSlots.footer ?? InlineFooter) : undefined,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: tocEnabled ? InlineTOC : undefined,
|
||||
popover: tocPopoverEnabled ? InlineTOCPopover : undefined,
|
||||
},
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
let content = (
|
||||
<>
|
||||
{slots.toc?.popover && <slots.toc.popover />}
|
||||
{slots.container && (
|
||||
<slots.container {...containerProps}>
|
||||
{slots.breadcrumb && <slots.breadcrumb />}
|
||||
{children}
|
||||
{slots.footer && <slots.footer />}
|
||||
</slots.container>
|
||||
)}
|
||||
{slots.toc?.main && <slots.toc.main />}
|
||||
</>
|
||||
);
|
||||
|
||||
if (slots.toc)
|
||||
content = (
|
||||
<slots.toc.provider single={single} toc={tocEnabled || tocPopoverEnabled ? toc : []}>
|
||||
{content}
|
||||
</slots.toc.provider>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: {
|
||||
full,
|
||||
tableOfContent: tocProps,
|
||||
tableOfContentPopover: tocPopoverProps,
|
||||
footer,
|
||||
breadcrumb,
|
||||
},
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
function InlineBreadcrumb(props: BreadcrumbProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.breadcrumb ?? {};
|
||||
if (component) return component;
|
||||
return <Breadcrumb {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineFooter(props: FooterProps) {
|
||||
const { component, enabled: _, ...rest } = useDocsPage().props.footer ?? {};
|
||||
if (component) return component;
|
||||
return <Footer {...props} {...rest} />;
|
||||
}
|
||||
|
||||
function InlineTOCPopover(props: TOCPopoverProps) {
|
||||
const { tableOfContentPopover } = useDocsPage().props;
|
||||
if (tableOfContentPopover.component) return tableOfContentPopover.component;
|
||||
return <TOCPopover {...props} {...tableOfContentPopover} />;
|
||||
}
|
||||
|
||||
function InlineTOC(props: TOCProps) {
|
||||
const { tableOfContent } = useDocsPage().props;
|
||||
if (tableOfContent.component) return tableOfContent.component;
|
||||
return <TOC {...props} {...tableOfContent} />;
|
||||
}
|
||||
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,150 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
'use client';
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
use,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { I18nLabel, useI18n } from '@/contexts/i18n';
|
||||
import {
|
||||
TOC,
|
||||
TOCPopover,
|
||||
TOCProvider,
|
||||
type TOCProviderProps,
|
||||
type TOCPopoverProps,
|
||||
type TOCProps,
|
||||
} from './slots/toc';
|
||||
import { Footer, type FooterProps } from './slots/footer';
|
||||
import { Breadcrumb, type BreadcrumbProps } from './slots/breadcrumb';
|
||||
import { Container } from './slots/container';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { Edit } from 'lucide-react';
|
||||
import { I18nLabel } from '@/contexts/i18n';
|
||||
|
||||
export interface DocsPageProps extends ComponentProps<'article'> {
|
||||
toc?: TOCItemType[];
|
||||
/**
|
||||
* Extend the page to fill all available space
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
full?: boolean;
|
||||
slots?: Partial<DocsPageSlots>;
|
||||
|
||||
footer?: FooterOptions;
|
||||
breadcrumb?: BreadcrumbOptions;
|
||||
tableOfContent?: TableOfContentOptions;
|
||||
tableOfContentPopover?: TableOfContentPopoverOptions;
|
||||
}
|
||||
|
||||
interface BreadcrumbOptions extends BreadcrumbProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.breadcrumb` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface FooterOptions extends FooterProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.footer` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentOptions extends Pick<TOCProviderProps, 'single'>, TOCProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.toc` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface TableOfContentPopoverOptions extends TOCPopoverProps {
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @deprecated use `slots.tocPopover` instead.
|
||||
*/
|
||||
component?: ReactNode;
|
||||
}
|
||||
|
||||
interface DocsPageSlots {
|
||||
toc: {
|
||||
provider: FC<TOCProviderProps>;
|
||||
main: FC<TOCProps>;
|
||||
popover: FC<TOCPopoverProps>;
|
||||
};
|
||||
container: FC<ComponentProps<'article'>>;
|
||||
footer: FC<FooterProps>;
|
||||
breadcrumb: FC<BreadcrumbProps>;
|
||||
}
|
||||
|
||||
type PageSlotsProps = Pick<DocsPageProps, 'full'>;
|
||||
|
||||
const PageContext = createContext<{
|
||||
props: PageSlotsProps;
|
||||
slots: DocsPageSlots;
|
||||
} | null>(null);
|
||||
|
||||
export function useDocsPage() {
|
||||
const context = use(PageContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Please use page components under <DocsPage /> (`fumadocs-ui/layouts/notebook/page`).',
|
||||
);
|
||||
return context;
|
||||
}
|
||||
|
||||
export function DocsPage({
|
||||
tableOfContent: { enabled: tocEnabled, single, ...tocProps } = {},
|
||||
tableOfContentPopover: { enabled: tocPopoverEnabled, ...tocPopoverProps } = {},
|
||||
breadcrumb: { enabled: breadcrumbEnabled = true, ...breadcrumb } = {},
|
||||
footer: { enabled: footerEnabled = true, ...footer } = {},
|
||||
full = false,
|
||||
toc = [],
|
||||
slots: defaultSlots = {},
|
||||
children,
|
||||
...containerProps
|
||||
}: DocsPageProps) {
|
||||
tocEnabled ??= Boolean(!full && (toc.length > 0 || tocProps.footer || tocProps.header));
|
||||
tocPopoverEnabled ??= Boolean(toc.length > 0 || tocPopoverProps.header || tocPopoverProps.footer);
|
||||
|
||||
const slots: DocsPageSlots = {
|
||||
breadcrumb: defaultSlots.breadcrumb ?? Breadcrumb,
|
||||
footer: defaultSlots.footer ?? Footer,
|
||||
toc: defaultSlots.toc ?? {
|
||||
provider: TOCProvider,
|
||||
main: TOC,
|
||||
popover: TOCPopover,
|
||||
},
|
||||
container: defaultSlots.container ?? Container,
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContext
|
||||
value={{
|
||||
props: { full },
|
||||
slots,
|
||||
}}
|
||||
>
|
||||
<slots.toc.provider single={single} toc={tocEnabled || tocPopoverEnabled ? toc : []}>
|
||||
{tocPopoverEnabled &&
|
||||
(tocPopoverProps.component ?? <slots.toc.popover {...tocPopoverProps} />)}
|
||||
<slots.container {...containerProps}>
|
||||
{breadcrumbEnabled && (breadcrumb.component ?? <slots.breadcrumb {...breadcrumb} />)}
|
||||
{children}
|
||||
{footerEnabled && (footer.component ?? <slots.footer {...footer} />)}
|
||||
</slots.container>
|
||||
{tocEnabled && (tocProps.component ?? <slots.toc.main {...tocProps} />)}
|
||||
</slots.toc.provider>
|
||||
</PageContext>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
return (
|
||||
@@ -14,8 +156,8 @@ export function EditOnGitHub(props: ComponentProps<'a'>) {
|
||||
buttonVariants({
|
||||
color: 'secondary',
|
||||
size: 'sm',
|
||||
className: 'gap-1.5 not-prose',
|
||||
}),
|
||||
'gap-1.5 not-prose',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@@ -59,13 +201,25 @@ export function DocsTitle({ children, className, ...props }: ComponentProps<'h1'
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
DocsPage,
|
||||
type DocsPageProps,
|
||||
type DocsPageSlots,
|
||||
PageLastUpdate,
|
||||
useDocsPage,
|
||||
} from './client';
|
||||
export function PageLastUpdate({
|
||||
date: value,
|
||||
...props
|
||||
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
||||
const { text } = useI18n();
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// to the timezone of client
|
||||
setDate(value.toLocaleDateString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<p {...props} className={cn('text-sm text-fd-muted-foreground', props.className)}>
|
||||
{text.lastUpdate} {date}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export { type BreadcrumbProps, Breadcrumb as PageBreadcrumb } from './slots/breadcrumb';
|
||||
export { type FooterProps, Footer as PageFooter } from './slots/footer';
|
||||
export { MarkdownCopyButton, ViewOptionsPopover } from '@/layouts/shared/page-actions';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useTreeContext, useTreePath } from '@/contexts/tree';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { type BreadcrumbOptions, getBreadcrumbItemsFromPath } from 'fumadocs-core/breadcrumb';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsPage } from '../client';
|
||||
import { useDocsPage } from '..';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
export function Container(props: ComponentProps<'article'>) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { isActive } from '@/utils/urls';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useNotebookLayout } from '../client';
|
||||
|
||||
Reference in New Issue
Block a user