Skip to content

Commit a35597e

Browse files
committed
OpenAPI: Use new codeblock tab style
1 parent ae38ed0 commit a35597e

File tree

5 files changed

+101
-62
lines changed

5 files changed

+101
-62
lines changed

.changeset/late-feet-press.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'fumadocs-openapi': patch
3+
---
4+
5+
Use new codeblock tab style

packages/openapi/src/render/renderer.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ import {
1818
import type { RenderContext } from '@/types';
1919
import { APIPlayground, type APIPlaygroundProps } from '@/playground';
2020
import { CodeExampleSelector } from '@/ui/lazy';
21+
import {
22+
CodeBlockTab,
23+
CodeBlockTabs,
24+
CodeBlockTabsList,
25+
CodeBlockTabsTrigger,
26+
} from 'fumadocs-ui/components/codeblock';
2127

2228
export interface ResponsesProps {
2329
items: string[];
@@ -133,12 +139,26 @@ export function createRenders(): Renderer {
133139
),
134140
Property,
135141
ObjectCollapsible,
136-
Requests: (props) => (
137-
<Tabs groupId="fumadocs_openapi_requests" {...props} />
142+
Requests: ({ items, children }) => (
143+
<CodeBlockTabs
144+
groupId="fumadocs_openapi_requests"
145+
defaultValue={items[0]}
146+
>
147+
<CodeBlockTabsList>
148+
{items.map((item) => (
149+
<CodeBlockTabsTrigger key={item} value={item}>
150+
{item}
151+
</CodeBlockTabsTrigger>
152+
))}
153+
</CodeBlockTabsList>
154+
{children}
155+
</CodeBlockTabs>
138156
),
139157

140158
CodeExampleSelector,
141-
Request: (props) => <Tab value={props.name}>{props.children}</Tab>,
159+
Request: (props) => (
160+
<CodeBlockTab value={props.name}>{props.children}</CodeBlockTab>
161+
),
142162
APIPlayground,
143163
};
144164
}

packages/openapi/src/render/schema.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function Schema({
2222
readOnly = false,
2323
writeOnly = false,
2424
as = 'property',
25-
ctx: { renderer },
25+
ctx: renderContext,
2626
}: {
2727
name: string;
2828
required?: boolean;
@@ -33,6 +33,8 @@ export function Schema({
3333
writeOnly?: boolean;
3434
ctx: RenderContext;
3535
}): ReactNode {
36+
const { renderer } = renderContext;
37+
3638
function propertyBody(
3739
schema: Exclude<ResolvedSchema, boolean>,
3840
renderPrimitive: (
@@ -59,7 +61,7 @@ export function Schema({
5961
<TabsList>
6062
{items.map((item) => (
6163
<TabsTrigger key={item.type} value={item.type}>
62-
{schemaToString(item)}
64+
{schemaToString(item, renderContext.schema)}
6365
</TabsTrigger>
6466
))}
6567
</TabsList>
@@ -88,7 +90,7 @@ export function Schema({
8890
<TabsList>
8991
{oneOf.map((item, i) => (
9092
<TabsTrigger key={i} value={i.toString()}>
91-
{schemaToString(item)}
93+
{schemaToString(item, renderContext.schema)}
9294
</TabsTrigger>
9395
))}
9496
</TabsList>
@@ -309,7 +311,7 @@ export function Schema({
309311
return (
310312
<renderer.Property
311313
name={key}
312-
type={schemaToString(schema)}
314+
type={schemaToString(schema, renderContext.schema)}
313315
deprecated={schema.deprecated}
314316
{...props}
315317
>

packages/openapi/src/utils/get-typescript-schema.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ export async function getTypescriptSchema(
55
schema: object,
66
dereferenceMap: DereferenceMap,
77
): Promise<string | undefined> {
8-
const cloned = structuredClone({ schema, dereferenceMap });
9-
return compile(cloned.schema, 'Response', {
10-
$refOptions: false,
11-
schemaToId: cloned.dereferenceMap,
12-
bannerComment: '',
13-
additionalProperties: false,
14-
enableConstEnums: false,
15-
});
8+
try {
9+
const cloned = structuredClone({ schema, dereferenceMap });
10+
return await compile(cloned.schema, 'Response', {
11+
$refOptions: false,
12+
schemaToId: cloned.dereferenceMap,
13+
bannerComment: '',
14+
additionalProperties: false,
15+
enableConstEnums: false,
16+
});
17+
} catch (e) {
18+
console.warn('Failed to generate typescript schema:', e);
19+
}
1620
}

packages/openapi/src/utils/schema-to-string.ts

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,71 @@
11
import { type ParsedSchema, type ResolvedSchema } from '@/utils/schema';
2+
import type { ProcessedDocument } from '@/utils/process-document';
23

3-
export function schemaToString(schema: ResolvedSchema, isRoot = true): string {
4-
if (schema === true) return 'any';
5-
else if (schema === false) return 'never';
4+
export function schemaToString(
5+
value: ResolvedSchema,
6+
ctx?: ProcessedDocument,
7+
): string {
8+
function run(schema: ResolvedSchema, isRoot: boolean): string {
9+
if (schema === true) return 'any';
10+
else if (schema === false) return 'never';
611

7-
if (isNullable(schema) && isRoot) {
8-
const type = schemaToString(schema, false);
12+
if (isNullable(schema) && isRoot) {
13+
const type = run(schema, false);
914

10-
// null if schema only contains `nullable`
11-
return type === 'unknown' ? 'null' : `${type} | null`;
12-
}
15+
// null if schema only contains `nullable`
16+
return type === 'unknown' ? 'null' : `${type} | null`;
17+
}
1318

14-
if (schema.title) return schema.title;
15-
16-
if (Array.isArray(schema.type)) {
17-
return schema.type
18-
.map((type) =>
19-
schemaToString(
20-
{
21-
...schema,
22-
type,
23-
},
24-
false,
25-
),
26-
)
27-
.filter((v) => v !== 'unknown' && v !== 'null')
28-
.join(' | ');
29-
}
19+
if (schema.title) return schema.title;
20+
const referenceName = ctx?.dereferenceMap.get(schema);
21+
if (referenceName) return referenceName.split('/').at(-1)!;
3022

31-
if (schema.type === 'array')
32-
return `array<${schema.items ? schemaToString(schema.items) : 'unknown'}>`;
23+
if (Array.isArray(schema.type)) {
24+
return schema.type
25+
.map((type, _, originalType) => {
26+
schema.type = type;
27+
const str = run(schema, false);
28+
schema.type = originalType;
3329

34-
if (schema.oneOf) {
35-
return schema.oneOf
36-
.map((one) => schemaToString(one, false))
37-
.filter((v) => v !== 'unknown' && v !== 'null')
38-
.join(' | ');
39-
}
30+
return str;
31+
})
32+
.filter((v) => v !== 'unknown' && v !== 'null')
33+
.join(' | ');
34+
}
4035

41-
const combinedOf = schema.anyOf ?? schema.allOf;
42-
if (combinedOf) {
43-
return combinedOf
44-
.map((one) => schemaToString(one, false))
45-
.filter((v) => v !== 'unknown' && v !== 'null')
46-
.join(' & ');
47-
}
36+
if (schema.type === 'array')
37+
return `array<${schema.items ? run(schema.items, true) : 'unknown'}>`;
4838

49-
if (schema.not) return `not ${schemaToString(schema.not, false)}`;
50-
if (schema.type === 'string' && schema.format === 'binary') return 'file';
39+
if (schema.oneOf) {
40+
return schema.oneOf
41+
.map((one) => run(one, false))
42+
.filter((v) => v !== 'unknown' && v !== 'null')
43+
.join(' | ');
44+
}
5145

52-
if (schema.type && Array.isArray(schema.type)) {
53-
return schema.type.filter((v) => v !== 'null').join(' | ');
54-
}
46+
const combinedOf = schema.anyOf ?? schema.allOf;
47+
if (combinedOf) {
48+
return combinedOf
49+
.map((one) => run(one, false))
50+
.filter((v) => v !== 'unknown' && v !== 'null')
51+
.join(' & ');
52+
}
53+
54+
if (schema.not) return `not ${run(schema.not, false)}`;
55+
if (schema.type === 'string' && schema.format === 'binary') return 'file';
56+
57+
if (schema.type && Array.isArray(schema.type)) {
58+
return schema.type.filter((v) => v !== 'null').join(' | ');
59+
}
60+
61+
if (schema.type) {
62+
return schema.type as string;
63+
}
5564

56-
if (schema.type) {
57-
return schema.type as string;
65+
return 'unknown';
5866
}
5967

60-
return 'unknown';
68+
return run(value, true);
6169
}
6270

6371
function isNullable(schema: ParsedSchema): boolean {

0 commit comments

Comments
 (0)