Skip to content

Commit 87cdffa

Browse files
committed
OpenAPI: Drop renderer API
1 parent 2970885 commit 87cdffa

File tree

15 files changed

+582
-661
lines changed

15 files changed

+582
-661
lines changed

.changeset/cute-masks-carry.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'fumadocs-openapi': major
3+
---
4+
5+
**Drop `renderer` API**
6+
7+
Fumadocs OpenAPI now expects per-feature customizations, dropping the old centralized `renderer` API.

packages/openapi/src/playground/client.tsx

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,7 @@ import {
6161
SchemaProvider,
6262
useResolvedSchema,
6363
} from '@/playground/schema';
64-
import {
65-
useRequestDataUpdater,
66-
useRequestInitialData,
67-
} from '@/ui/contexts/code-example';
64+
import { useOperationContext } from '@/ui/contexts/operation';
6865
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
6966
import {
7067
Select,
@@ -169,8 +166,11 @@ export default function Client({
169166
...rest
170167
}: ClientProps) {
171168
const { server } = useServerSelectContext();
172-
const { key: requestDataKey, data: requestData } = useRequestInitialData();
173-
const updater = useRequestDataUpdater();
169+
const {
170+
example: exampleId,
171+
examples,
172+
setExampleData,
173+
} = useOperationContext();
174174
const fieldInfoMap = useMemo(() => new Map<string, FieldInfo>(), []);
175175
const { mediaAdapters } = useApiContext();
176176
const [securityId, setSecurityId] = useState(0);
@@ -179,16 +179,19 @@ export default function Client({
179179
transformAuthInputs,
180180
);
181181

182-
const defaultValues: FormValues = useMemo(
183-
() => ({
182+
const defaultValues: FormValues = useMemo(() => {
183+
const requestData = examples.find(
184+
(example) => example.id === exampleId,
185+
)!.data;
186+
187+
return {
184188
path: requestData.path,
185189
query: requestData.query,
186190
header: requestData.header,
187191
body: requestData.body,
188192
cookie: requestData.cookie,
189-
}),
190-
[requestData],
191-
);
193+
};
194+
}, [examples, exampleId]);
192195

193196
const form = useForm<FormValues>({
194197
defaultValues,
@@ -238,7 +241,7 @@ export default function Client({
238241
bodyMediaType: body?.mediaType,
239242
};
240243
values._encoded ??= encodeRequestData(data, mediaAdapters, parameters);
241-
updater.setData(data, values._encoded);
244+
setExampleData(data, values._encoded);
242245
});
243246

244247
useEffect(() => {
@@ -264,6 +267,13 @@ export default function Client({
264267
// eslint-disable-next-line react-hooks/exhaustive-deps -- mounted once only
265268
}, []);
266269

270+
useEffect(() => {
271+
form.reset(initAuthValues(defaultValues, inputs));
272+
273+
return () => fieldInfoMap.clear();
274+
// eslint-disable-next-line react-hooks/exhaustive-deps -- ignore other parts
275+
}, [defaultValues]);
276+
267277
useEffect(() => {
268278
form.reset((values) => initAuthValues(values, inputs));
269279

@@ -276,17 +286,9 @@ export default function Client({
276286
return values;
277287
});
278288
};
279-
// eslint-disable-next-line react-hooks/exhaustive-deps -- mounted once only
289+
// eslint-disable-next-line react-hooks/exhaustive-deps -- ignore other parts
280290
}, [inputs]);
281291

282-
useEffect(() => {
283-
return () => {
284-
fieldInfoMap.clear();
285-
form.reset(initAuthValues(defaultValues, inputs));
286-
};
287-
// eslint-disable-next-line react-hooks/exhaustive-deps -- for on change
288-
}, [requestDataKey]);
289-
290292
const onSubmit = form.handleSubmit((value) => {
291293
testQuery.start(mapInputs(value));
292294
});

packages/openapi/src/requests/generators/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
1-
import type { CodeSample } from '@/ui/operation';
21
import * as CURL from './curl';
32
import * as JS from './javascript';
43
import * as Go from './go';
54
import * as Python from './python';
65
import * as Java from './java';
76
import * as CSharp from './csharp';
7+
import type { CodeUsageGenerator } from '@/ui/operation/api-example';
88

9-
export const defaultSamples: CodeSample[] = [
9+
export const defaultSamples: CodeUsageGenerator[] = [
1010
{
11+
id: 'curl',
1112
label: 'cURL',
1213
source: CURL.generator,
1314
lang: 'bash',
1415
},
1516
{
17+
id: 'js',
1618
label: 'JavaScript',
1719
source: JS.generator,
1820
lang: 'js',
1921
},
2022
{
23+
id: 'go',
2124
label: 'Go',
2225
source: Go.generator,
2326
lang: 'go',
2427
},
2528
{
29+
id: 'python',
2630
label: 'Python',
2731
source: Python.generator,
2832
lang: 'python',
2933
},
3034
{
35+
id: 'java',
3136
label: 'Java',
3237
source: Java.generator,
3338
lang: 'java',
3439
},
3540
{
41+
id: 'csharp',
3642
label: 'C#',
3743
source: CSharp.generator,
3844
lang: 'csharp',

packages/openapi/src/server/create.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { createProxy } from '@/server/proxy';
2-
import type { CodeSample } from '@/ui/operation';
32
import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
43
import {
54
processDocument,
65
type ProcessedDocument,
76
} from '@/utils/process-document';
7+
import type { CodeUsageGenerator } from '@/ui/operation/api-example';
88

99
/**
1010
* schema id -> file path, URL, or downloaded schema object
@@ -73,6 +73,14 @@ export function createOpenAPI(options: OpenAPIOptions = {}): OpenAPIServer {
7373
};
7474
}
7575

76-
export function createCodeSample<T>(options: CodeSample<T>): CodeSample {
77-
return options as CodeSample;
76+
export function createCodeSample<T>(
77+
options: Partial<CodeUsageGenerator<T>>,
78+
): CodeUsageGenerator {
79+
const {
80+
lang = 'unknown',
81+
id = lang,
82+
...rest
83+
} = options as Partial<CodeUsageGenerator>;
84+
85+
return { id, lang, ...rest };
7886
}

packages/openapi/src/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { OpenAPIV3_1 as V3_1 } from 'openapi-types';
22
import type { default as Slugger } from 'github-slugger';
3-
import { type Renderer } from '@/ui/renderer';
43
import type { NoReference } from '@/utils/schema';
54
import type { ProcessedDocument } from '@/utils/process-document';
65
import type { MediaAdapter } from '@/requests/media/adapter';
76
import type { OpenAPIOptions } from '@/server';
87
import type { CreateAPIPageOptions } from './ui/api-page';
8+
import type { CodeUsageGenerator } from './ui/operation/api-example';
99

1010
export type Document = V3_1.Document;
1111
export type OperationObject = V3_1.OperationObject;
@@ -17,15 +17,18 @@ export type TagObject = V3_1.TagObject;
1717
export type ServerObject = V3_1.ServerObject;
1818
export type CallbackObject = V3_1.CallbackObject;
1919
export type ServerVariableObject = V3_1.ServerVariableObject;
20+
export type ResponseObject = V3_1.ResponseObject;
2021

2122
export type MethodInformation = NoReference<OperationObject> & {
2223
method: string;
24+
'x-codeSamples'?: CodeUsageGenerator[];
25+
'x-selectedCodeSample'?: string;
26+
'x-exclusiveCodeSample'?: string;
2327
};
2428

2529
export interface RenderContext
2630
extends Pick<OpenAPIOptions, 'proxyUrl'>,
2731
CreateAPIPageOptions {
28-
renderer: Renderer;
2932
servers: NoReference<ServerObject>[];
3033
slugger: Slugger;
3134

packages/openapi/src/ui/api-page.tsx

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import Slugger from 'github-slugger';
2-
import { CodeSample, Operation } from '@/ui/operation';
2+
import { Operation } from '@/ui/operation';
33
import type { MethodInformation, RenderContext } from '@/types';
44
import { createMethod, NoReference } from '@/utils/schema';
5-
import { createRenders } from '@/ui/renderer';
65
import type { OpenAPIV3_1 } from 'openapi-types';
76
import type { ProcessedDocument } from '@/utils/process-document';
87
import { defaultAdapters, MediaAdapter } from '@/requests/media/adapter';
9-
import { FC, ReactNode } from 'react';
8+
import { ComponentProps, FC, ReactNode } from 'react';
109
import type {
1110
HighlightOptionsCommon,
1211
HighlightOptionsThemes,
1312
} from 'fumadocs-core/highlight';
1413
import type { OpenAPIServer } from '@/server';
1514
import type { APIPageClientOptions } from './client';
15+
import { cn } from 'fumadocs-ui/utils/cn';
16+
import type { CodeUsageGenerator, ResponseTab } from './operation/api-example';
17+
import { ApiProvider } from './contexts/api';
1618

1719
type Awaitable<T> = T | Promise<T>;
1820

@@ -33,9 +35,11 @@ export interface CreateAPIPageOptions {
3335
| false;
3436

3537
/**
36-
* Generate code samples for endpoint.
38+
* Generate example code usage for endpoints.
3739
*/
38-
generateCodeSamples?: (method: MethodInformation) => Awaitable<CodeSample[]>;
40+
generateCodeSamples?: (
41+
method: MethodInformation,
42+
) => Awaitable<CodeUsageGenerator[]>;
3943

4044
shikiOptions?: Omit<HighlightOptionsCommon, 'lang' | 'components'> &
4145
HighlightOptionsThemes;
@@ -59,6 +63,28 @@ export interface CreateAPIPageOptions {
5963
* @defaultValue false
6064
*/
6165
showExampleInFields?: boolean;
66+
67+
renderResponseTabs?: (
68+
tabs: ResponseTab[],
69+
ctx: RenderContext,
70+
) => Awaitable<ReactNode>;
71+
72+
renderAPIExampleLayout?: (
73+
slots: {
74+
selector: ReactNode;
75+
usageTabs: ReactNode;
76+
responseTabs: ReactNode;
77+
},
78+
ctx: RenderContext,
79+
) => Awaitable<ReactNode>;
80+
81+
/**
82+
* @param generators - codegens for API example usages
83+
*/
84+
renderAPIExampleUsageTabs?: (
85+
generators: CodeUsageGenerator<unknown>[],
86+
ctx: RenderContext,
87+
) => Awaitable<ReactNode>;
6288
};
6389

6490
/**
@@ -76,7 +102,7 @@ export interface CreateAPIPageOptions {
76102
path: string;
77103
method: MethodInformation;
78104
ctx: RenderContext;
79-
}) => ReactNode | Promise<ReactNode>;
105+
}) => Awaitable<ReactNode>;
80106
};
81107

82108
client?: APIPageClientOptions;
@@ -135,9 +161,6 @@ export function createAPIPage(
135161
schema: processed,
136162
proxyUrl: server.options.proxyUrl,
137163
showResponseSchema: options.showResponseSchema,
138-
renderer: {
139-
...createRenders(),
140-
},
141164
shikiOptions: options.shikiOptions,
142165
generateTypeScriptSchema: options.generateTypeScriptSchema,
143166
generateCodeSamples: options.generateCodeSamples,
@@ -153,6 +176,32 @@ export function createAPIPage(
153176
};
154177
}
155178

179+
function Root({
180+
children,
181+
className,
182+
ctx,
183+
...props
184+
}: { ctx: RenderContext } & ComponentProps<'div'>) {
185+
const mediaAdapters: Record<string, MediaAdapter> = {};
186+
for (const k in ctx.mediaAdapters) {
187+
const adapter = ctx.mediaAdapters[k];
188+
189+
if (adapter.client) mediaAdapters[k] = adapter.client;
190+
}
191+
192+
return (
193+
<div className={cn('flex flex-col gap-24 text-sm', className)} {...props}>
194+
<ApiProvider
195+
mediaAdapters={mediaAdapters}
196+
servers={ctx.servers}
197+
shikiOptions={ctx.shikiOptions}
198+
>
199+
{children}
200+
</ApiProvider>
201+
</div>
202+
);
203+
}
204+
156205
async function APIPage({
157206
hasHead = false,
158207
operations,
@@ -164,7 +213,7 @@ async function APIPage({
164213
const { dereferenced } = ctx.schema;
165214

166215
return (
167-
<ctx.renderer.Root ctx={ctx}>
216+
<Root ctx={ctx}>
168217
{operations?.map((item) => {
169218
const pathItem = dereferenced.paths?.[item.path];
170219
if (!pathItem)
@@ -216,6 +265,6 @@ async function APIPage({
216265
/>
217266
);
218267
})}
219-
</ctx.renderer.Root>
268+
</Root>
220269
);
221270
}

packages/openapi/src/ui/client/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
'use client';
12
import type { PlaygroundClientOptions } from '@/playground/client';
3+
import type { OperationClientOptions } from '../operation/client';
24

35
export interface APIPageClientOptions {
46
playground?: PlaygroundClientOptions;
7+
operation?: OperationClientOptions;
58
}
69

710
export function defineClientConfig(

0 commit comments

Comments
 (0)