UI: Simplify internal types

This commit is contained in:
Fuma Nama
2026-03-20 19:54:26 +08:00
parent e2841e961e
commit 652c7251ce
46 changed files with 1058 additions and 1317 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'fumadocs-ui': patch
'@fumadocs/base-ui': patch
---
Simplify internal types
+29 -3
View File
@@ -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" />
-15
View File
@@ -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>
);
}
+165 -10
View File
@@ -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>
);
}
+144 -10
View File
@@ -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';
-15
View File
@@ -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>
);
}
+165 -10
View File
@@ -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>
);
}
+144 -10
View File
@@ -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';