OpenAPI: Use new codeblock tab style

This commit is contained in:
Fuma Nama
2025-07-05 17:27:17 +08:00
parent ae38ed014d
commit a35597e4f4
5 changed files with 108 additions and 69 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-openapi': patch
---
Use new codeblock tab style
+23 -3
View File
@@ -18,6 +18,12 @@ import {
import type { RenderContext } from '@/types';
import { APIPlayground, type APIPlaygroundProps } from '@/playground';
import { CodeExampleSelector } from '@/ui/lazy';
import {
CodeBlockTab,
CodeBlockTabs,
CodeBlockTabsList,
CodeBlockTabsTrigger,
} from 'fumadocs-ui/components/codeblock';
export interface ResponsesProps {
items: string[];
@@ -133,12 +139,26 @@ export function createRenders(): Renderer {
),
Property,
ObjectCollapsible,
Requests: (props) => (
<Tabs groupId="fumadocs_openapi_requests" {...props} />
Requests: ({ items, children }) => (
<CodeBlockTabs
groupId="fumadocs_openapi_requests"
defaultValue={items[0]}
>
<CodeBlockTabsList>
{items.map((item) => (
<CodeBlockTabsTrigger key={item} value={item}>
{item}
</CodeBlockTabsTrigger>
))}
</CodeBlockTabsList>
{children}
</CodeBlockTabs>
),
CodeExampleSelector,
Request: (props) => <Tab value={props.name}>{props.children}</Tab>,
Request: (props) => (
<CodeBlockTab value={props.name}>{props.children}</CodeBlockTab>
),
APIPlayground,
};
}
+6 -4
View File
@@ -22,7 +22,7 @@ export function Schema({
readOnly = false,
writeOnly = false,
as = 'property',
ctx: { renderer },
ctx: renderContext,
}: {
name: string;
required?: boolean;
@@ -33,6 +33,8 @@ export function Schema({
writeOnly?: boolean;
ctx: RenderContext;
}): ReactNode {
const { renderer } = renderContext;
function propertyBody(
schema: Exclude<ResolvedSchema, boolean>,
renderPrimitive: (
@@ -59,7 +61,7 @@ export function Schema({
<TabsList>
{items.map((item) => (
<TabsTrigger key={item.type} value={item.type}>
{schemaToString(item)}
{schemaToString(item, renderContext.schema)}
</TabsTrigger>
))}
</TabsList>
@@ -88,7 +90,7 @@ export function Schema({
<TabsList>
{oneOf.map((item, i) => (
<TabsTrigger key={i} value={i.toString()}>
{schemaToString(item)}
{schemaToString(item, renderContext.schema)}
</TabsTrigger>
))}
</TabsList>
@@ -309,7 +311,7 @@ export function Schema({
return (
<renderer.Property
name={key}
type={schemaToString(schema)}
type={schemaToString(schema, renderContext.schema)}
deprecated={schema.deprecated}
{...props}
>
@@ -5,12 +5,16 @@ export async function getTypescriptSchema(
schema: object,
dereferenceMap: DereferenceMap,
): Promise<string | undefined> {
const cloned = structuredClone({ schema, dereferenceMap });
return compile(cloned.schema, 'Response', {
$refOptions: false,
schemaToId: cloned.dereferenceMap,
bannerComment: '',
additionalProperties: false,
enableConstEnums: false,
});
try {
const cloned = structuredClone({ schema, dereferenceMap });
return await compile(cloned.schema, 'Response', {
$refOptions: false,
schemaToId: cloned.dereferenceMap,
bannerComment: '',
additionalProperties: false,
enableConstEnums: false,
});
} catch (e) {
console.warn('Failed to generate typescript schema:', e);
}
}
+62 -54
View File
@@ -1,63 +1,71 @@
import { type ParsedSchema, type ResolvedSchema } from '@/utils/schema';
import type { ProcessedDocument } from '@/utils/process-document';
export function schemaToString(schema: ResolvedSchema, isRoot = true): string {
if (schema === true) return 'any';
else if (schema === false) return 'never';
export function schemaToString(
value: ResolvedSchema,
ctx?: ProcessedDocument,
): string {
function run(schema: ResolvedSchema, isRoot: boolean): string {
if (schema === true) return 'any';
else if (schema === false) return 'never';
if (isNullable(schema) && isRoot) {
const type = schemaToString(schema, false);
if (isNullable(schema) && isRoot) {
const type = run(schema, false);
// null if schema only contains `nullable`
return type === 'unknown' ? 'null' : `${type} | null`;
// null if schema only contains `nullable`
return type === 'unknown' ? 'null' : `${type} | null`;
}
if (schema.title) return schema.title;
const referenceName = ctx?.dereferenceMap.get(schema);
if (referenceName) return referenceName.split('/').at(-1)!;
if (Array.isArray(schema.type)) {
return schema.type
.map((type, _, originalType) => {
schema.type = type;
const str = run(schema, false);
schema.type = originalType;
return str;
})
.filter((v) => v !== 'unknown' && v !== 'null')
.join(' | ');
}
if (schema.type === 'array')
return `array<${schema.items ? run(schema.items, true) : 'unknown'}>`;
if (schema.oneOf) {
return schema.oneOf
.map((one) => run(one, false))
.filter((v) => v !== 'unknown' && v !== 'null')
.join(' | ');
}
const combinedOf = schema.anyOf ?? schema.allOf;
if (combinedOf) {
return combinedOf
.map((one) => run(one, false))
.filter((v) => v !== 'unknown' && v !== 'null')
.join(' & ');
}
if (schema.not) return `not ${run(schema.not, false)}`;
if (schema.type === 'string' && schema.format === 'binary') return 'file';
if (schema.type && Array.isArray(schema.type)) {
return schema.type.filter((v) => v !== 'null').join(' | ');
}
if (schema.type) {
return schema.type as string;
}
return 'unknown';
}
if (schema.title) return schema.title;
if (Array.isArray(schema.type)) {
return schema.type
.map((type) =>
schemaToString(
{
...schema,
type,
},
false,
),
)
.filter((v) => v !== 'unknown' && v !== 'null')
.join(' | ');
}
if (schema.type === 'array')
return `array<${schema.items ? schemaToString(schema.items) : 'unknown'}>`;
if (schema.oneOf) {
return schema.oneOf
.map((one) => schemaToString(one, false))
.filter((v) => v !== 'unknown' && v !== 'null')
.join(' | ');
}
const combinedOf = schema.anyOf ?? schema.allOf;
if (combinedOf) {
return combinedOf
.map((one) => schemaToString(one, false))
.filter((v) => v !== 'unknown' && v !== 'null')
.join(' & ');
}
if (schema.not) return `not ${schemaToString(schema.not, false)}`;
if (schema.type === 'string' && schema.format === 'binary') return 'file';
if (schema.type && Array.isArray(schema.type)) {
return schema.type.filter((v) => v !== 'null').join(' | ');
}
if (schema.type) {
return schema.type as string;
}
return 'unknown';
return run(value, true);
}
function isNullable(schema: ParsedSchema): boolean {