mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
OpenAPI: improve description rendering
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
improve description rendering
|
||||
@@ -55,9 +55,9 @@ export default async function Page(props: PageProps<'/docs/[...slug]'>) {
|
||||
if (page.data.type === 'openapi') {
|
||||
const { APIPage } = await import('@/components/api-page');
|
||||
return (
|
||||
<DocsPage>
|
||||
<DocsPage full>
|
||||
<h1 className="text-[1.75em] font-semibold">{page.data.title}</h1>
|
||||
<p className="text-fd-muted-foreground mb-6">{page.data.description}</p>
|
||||
|
||||
<DocsBody>
|
||||
<APIPage {...page.data.getAPIPageProps()} />
|
||||
</DocsBody>
|
||||
|
||||
@@ -382,7 +382,7 @@ export function AISearchPanel() {
|
||||
<Presence present={open}>
|
||||
<div
|
||||
data-state={open ? 'open' : 'closed'}
|
||||
className="fixed inset-0 z-20 backdrop-blur-xs bg-fd-overlay data-[state=open]:animate-fd-fade-in data-[state=closed]:animate-fd-fade-out lg:hidden"
|
||||
className="fixed inset-0 z-30 backdrop-blur-xs bg-fd-overlay data-[state=open]:animate-fd-fade-in data-[state=closed]:animate-fd-fade-out lg:hidden"
|
||||
onClick={() => setOpen(false)}
|
||||
/>
|
||||
</Presence>
|
||||
|
||||
@@ -93,7 +93,11 @@ export async function openapiSource(
|
||||
path: `${baseDir}/${entry.path}`,
|
||||
data: {
|
||||
...entry.info,
|
||||
getAPIPageProps: () => toBody(entry),
|
||||
getAPIPageProps: () => {
|
||||
const props = toBody(entry);
|
||||
props.showDescription ??= true;
|
||||
return props;
|
||||
},
|
||||
_openapi: {
|
||||
method:
|
||||
entry.type === 'operation' || entry.type === 'webhook'
|
||||
|
||||
@@ -123,6 +123,7 @@ export interface CreateAPIPageOptions {
|
||||
renderOperationLayout?: (
|
||||
slots: {
|
||||
header: ReactNode;
|
||||
description: ReactNode;
|
||||
apiExample: ReactNode;
|
||||
apiPlayground: ReactNode;
|
||||
|
||||
@@ -138,6 +139,7 @@ export interface CreateAPIPageOptions {
|
||||
|
||||
renderWebhookLayout?: (slots: {
|
||||
header: ReactNode;
|
||||
description: ReactNode;
|
||||
authSchemes: ReactNode;
|
||||
paremeters: ReactNode;
|
||||
body: ReactNode;
|
||||
@@ -188,7 +190,8 @@ export interface CreateAPIPageOptions {
|
||||
|
||||
export interface ApiPageProps {
|
||||
document: Promise<ProcessedDocument> | string | ProcessedDocument;
|
||||
hasHead?: boolean;
|
||||
showTitle?: boolean;
|
||||
showDescription?: boolean;
|
||||
|
||||
/**
|
||||
* An array of operations
|
||||
@@ -307,7 +310,8 @@ export function createAPIPage(
|
||||
}
|
||||
|
||||
async function APIPage({
|
||||
hasHead = false,
|
||||
showTitle: hasHead = false,
|
||||
showDescription,
|
||||
operations,
|
||||
webhooks,
|
||||
ctx,
|
||||
@@ -348,7 +352,8 @@ async function APIPage({
|
||||
method={method}
|
||||
path={item.path}
|
||||
ctx={ctx}
|
||||
hasHead={hasHead}
|
||||
showTitle={hasHead}
|
||||
showDescription={showDescription}
|
||||
/>
|
||||
),
|
||||
};
|
||||
@@ -375,7 +380,8 @@ async function APIPage({
|
||||
method={createMethod(item.method, webhook, hook)}
|
||||
ctx={ctx}
|
||||
path={`/${item.name}`}
|
||||
hasHead={hasHead}
|
||||
showTitle={hasHead}
|
||||
showDescription={showDescription}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -141,7 +141,7 @@ export async function APIExample({
|
||||
|
||||
renderAPIExampleLayout ??= (slots) => {
|
||||
return (
|
||||
<div className="prose-no-margin md:sticky md:top-(--fd-api-info-top) xl:w-[400px]">
|
||||
<div className="prose-no-margin md:sticky md:py-4 md:top-(--fd-docs-toc-top) xl:w-[400px]">
|
||||
{slots.selector}
|
||||
{slots.usageTabs}
|
||||
{slots.responseTabs}
|
||||
|
||||
@@ -46,7 +46,8 @@ export async function Operation({
|
||||
path,
|
||||
method,
|
||||
ctx,
|
||||
hasHead,
|
||||
showTitle,
|
||||
showDescription,
|
||||
headingLevel = 2,
|
||||
}: {
|
||||
type?: 'webhook' | 'operation';
|
||||
@@ -54,7 +55,8 @@ export async function Operation({
|
||||
method: MethodInformation;
|
||||
ctx: RenderContext;
|
||||
|
||||
hasHead?: boolean;
|
||||
showTitle?: boolean;
|
||||
showDescription?: boolean;
|
||||
headingLevel?: number;
|
||||
}) {
|
||||
const {
|
||||
@@ -62,22 +64,21 @@ export async function Operation({
|
||||
} = ctx;
|
||||
const body = method.requestBody;
|
||||
let headNode: ReactNode = null;
|
||||
const descriptionNode =
|
||||
showDescription &&
|
||||
method.description &&
|
||||
ctx.renderMarkdown(method.description);
|
||||
let bodyNode: ReactNode = null;
|
||||
let authNode: ReactNode = null;
|
||||
let responseNode: ReactNode = null;
|
||||
let callbacksNode: ReactNode = null;
|
||||
|
||||
if (hasHead) {
|
||||
if (showTitle) {
|
||||
const title =
|
||||
method.summary ??
|
||||
(method.operationId ? idToTitle(method.operationId) : path);
|
||||
|
||||
headNode = (
|
||||
<>
|
||||
{ctx.renderHeading(headingLevel, title)}
|
||||
{method.description && ctx.renderMarkdown(method.description)}
|
||||
</>
|
||||
);
|
||||
headNode = ctx.renderHeading(headingLevel, title);
|
||||
headingLevel++;
|
||||
}
|
||||
|
||||
@@ -262,18 +263,11 @@ export async function Operation({
|
||||
if (type === 'operation') {
|
||||
renderOperationLayout ??= (slots) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col gap-x-6 gap-y-4 xl:flex-row xl:items-start"
|
||||
style={
|
||||
{
|
||||
'--fd-api-info-top':
|
||||
'calc(12px + var(--fd-nav-height) + var(--fd-banner-height) + var(--fd-tocnav-height, 0px))',
|
||||
} as object
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-x-6 gap-y-4 xl:flex-row xl:items-start">
|
||||
<div className="min-w-0 flex-1">
|
||||
{slots.header}
|
||||
{slots.apiPlayground}
|
||||
{slots.description}
|
||||
{slots.authSchemes}
|
||||
{slots.paremeters}
|
||||
{slots.body}
|
||||
@@ -289,6 +283,7 @@ export async function Operation({
|
||||
const content = await renderOperationLayout(
|
||||
{
|
||||
header: headNode,
|
||||
description: descriptionNode,
|
||||
authSchemes: authNode,
|
||||
body: bodyNode,
|
||||
callbacks: callbacksNode,
|
||||
@@ -325,6 +320,7 @@ export async function Operation({
|
||||
renderWebhookLayout ??= (slots) => (
|
||||
<div>
|
||||
{slots.header}
|
||||
{slots.description}
|
||||
{slots.authSchemes}
|
||||
{slots.paremeters}
|
||||
{slots.body}
|
||||
@@ -334,6 +330,7 @@ export async function Operation({
|
||||
);
|
||||
return renderWebhookLayout({
|
||||
header: headNode,
|
||||
description: descriptionNode,
|
||||
authSchemes: authNode,
|
||||
body: bodyNode,
|
||||
callbacks: callbacksNode,
|
||||
|
||||
@@ -176,9 +176,20 @@ function SchemaUIContent({ $type }: { $type: string }) {
|
||||
child = (
|
||||
<>
|
||||
{child}
|
||||
<ObjectCollapsible name="Array item">
|
||||
<SchemaUIContent $type={schema.item.$type} />
|
||||
</ObjectCollapsible>
|
||||
<Collapsible className="my-2">
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
buttonVariants({ color: 'secondary', size: 'sm' }),
|
||||
'group px-3 py-2 data-[state=open]:rounded-b-none',
|
||||
)}
|
||||
>
|
||||
Array Item
|
||||
<ChevronDown className="size-4 text-fd-muted-foreground group-data-[state=open]:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="-mt-px bg-fd-card px-3 rounded-lg rounded-tl-none border shadow-sm">
|
||||
<SchemaUIContent $type={schema.item.$type} />
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -387,25 +398,6 @@ function LinkRef({
|
||||
);
|
||||
}
|
||||
|
||||
function ObjectCollapsible(props: { name: string; children: ReactNode }) {
|
||||
return (
|
||||
<Collapsible className="my-2" {...props}>
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
buttonVariants({ color: 'secondary', size: 'sm' }),
|
||||
'group px-3 py-2 data-[state=open]:rounded-b-none',
|
||||
)}
|
||||
>
|
||||
{props.name}
|
||||
<ChevronDown className="size-4 text-fd-muted-foreground group-data-[state=open]:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="-mt-px *:bg-fd-card">
|
||||
{props.children}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
interface PropertyProps {
|
||||
name: ReactNode;
|
||||
type: ReactNode;
|
||||
|
||||
@@ -4,19 +4,18 @@ import type { OutputEntry } from '@/utils/pages/builder';
|
||||
export function toBody(entry: OutputEntry): ApiPageProps {
|
||||
if (entry.type === 'operation')
|
||||
return {
|
||||
hasHead: false,
|
||||
document: entry.schemaId,
|
||||
operations: [entry.item],
|
||||
};
|
||||
if (entry.type === 'webhook')
|
||||
return {
|
||||
hasHead: false,
|
||||
document: entry.schemaId,
|
||||
webhooks: [entry.item],
|
||||
};
|
||||
|
||||
return {
|
||||
hasHead: true,
|
||||
showTitle: true,
|
||||
showDescription: true,
|
||||
document: entry.schemaId,
|
||||
operations: entry.operations,
|
||||
webhooks: entry.webhooks,
|
||||
|
||||
@@ -62,7 +62,6 @@ export function toText(
|
||||
processed,
|
||||
{
|
||||
operations: [entry.item],
|
||||
hasHead: false,
|
||||
},
|
||||
{
|
||||
...options,
|
||||
@@ -79,7 +78,7 @@ export function toText(
|
||||
{
|
||||
operations: entry.operations,
|
||||
webhooks: entry.webhooks,
|
||||
hasHead: true,
|
||||
showTitle: true,
|
||||
},
|
||||
{
|
||||
...options,
|
||||
@@ -96,7 +95,7 @@ export function toText(
|
||||
{
|
||||
operations: entry.operations,
|
||||
webhooks: entry.webhooks,
|
||||
hasHead: true,
|
||||
showTitle: true,
|
||||
},
|
||||
{
|
||||
...options,
|
||||
@@ -113,7 +112,6 @@ export function toText(
|
||||
processed,
|
||||
{
|
||||
webhooks: [entry.item],
|
||||
hasHead: false,
|
||||
},
|
||||
{
|
||||
...options,
|
||||
@@ -243,7 +241,7 @@ function generateStaticData(
|
||||
const operation = dereferenced.paths?.[item.path]?.[item.method];
|
||||
if (!operation) continue;
|
||||
|
||||
if (props.hasHead && operation.operationId) {
|
||||
if (props.showTitle && operation.operationId) {
|
||||
const title =
|
||||
operation.summary ??
|
||||
(operation.operationId ? idToTitle(operation.operationId) : item.path);
|
||||
@@ -281,5 +279,5 @@ function pageContent(props: ApiPageProps): string {
|
||||
method: item.method,
|
||||
}));
|
||||
|
||||
return `<APIPage document={${JSON.stringify(props.document)}} operations={${JSON.stringify(operations)}} webhooks={${JSON.stringify(webhooks)}} hasHead={${JSON.stringify(props.hasHead)}} />`;
|
||||
return `<APIPage document={${JSON.stringify(props.document)}} operations={${JSON.stringify(operations)}} webhooks={${JSON.stringify(webhooks)}} hasHead={${JSON.stringify(props.showTitle)}} />`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user