Skip to content

Commit 44ed9ec

Browse files
authored
feat: add dropdown/logic for multiple http response schemas (#3280)
1 parent 2b772b8 commit 44ed9ec

File tree

7 files changed

+330
-31
lines changed

7 files changed

+330
-31
lines changed

packages/fern-docs/bundle/src/components/api-reference/endpoints/EndpointContentCodeSnippets.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,13 +304,21 @@ export const EndpointContentCodeSnippets = memo(
304304
UnmemoizedEndpointContentCodeSnippets
305305
);
306306

307-
function renderResponseTitle(title: string, statusCode: number | string) {
307+
export function renderResponseTitle(
308+
title: string,
309+
statusCode: number | string,
310+
hideTitle?: boolean
311+
) {
308312
return (
309313
<span className="inline-flex items-center gap-2">
310314
<StatusCodeBadge statusCode={statusCode} />
311-
<span className={`text-intent-${statusCodeToIntent(String(statusCode))}`}>
312-
{title}
313-
</span>
315+
{!hideTitle && (
316+
<span
317+
className={`text-intent-${statusCodeToIntent(String(statusCode))}`}
318+
>
319+
{title}
320+
</span>
321+
)}
314322
</span>
315323
);
316324
}

packages/fern-docs/bundle/src/components/api-reference/endpoints/EndpointContentLeft.tsx

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from "../type-definitions/TypeDefinitionContext";
1414
import { WithSeparator } from "../type-definitions/TypeDefinitionDetails";
1515
import { EndpointErrorGroup } from "./EndpointErrorGroup";
16+
import { EndpointMultipleResponseSection } from "./EndpointMultipleResponseSection";
1617
import {
1718
EndpointRequestSection,
1819
createEndpointRequestDescriptionFallback,
@@ -217,31 +218,39 @@ export async function EndpointContentLeft({
217218
</TypeDefinitionAnchorPart>
218219
<TypeDefinitionResponse>
219220
<TypeDefinitionAnchorPart part="response">
220-
{endpoint.responses?.[0] != null && (
221-
<EndpointSection
222-
title="Response"
223-
description={
224-
<MdxServerComponentProseSuspense
225-
size="sm"
226-
className="text-(color:--grayscale-a11)"
227-
mdx={endpoint.responses[0].description}
228-
fallback={
229-
<ResponseSummaryFallback
230-
response={endpoint.responses[0]}
231-
types={types}
232-
/>
233-
}
234-
/>
235-
}
236-
>
237-
<TypeDefinitionAnchorPart part="body">
238-
<EndpointResponseSection
239-
body={endpoint.responses[0].body}
240-
types={types}
241-
/>
242-
</TypeDefinitionAnchorPart>
243-
</EndpointSection>
244-
)}
221+
{endpoint.responses?.[0] != null ? (
222+
endpoint.responses.length > 1 ? (
223+
<EndpointMultipleResponseSection
224+
method={endpoint.method}
225+
responses={endpoint.responses}
226+
types={types}
227+
/>
228+
) : (
229+
<EndpointSection
230+
title="Response"
231+
description={
232+
<MdxServerComponentProseSuspense
233+
size="sm"
234+
className="text-(color:--grayscale-a11)"
235+
mdx={endpoint.responses[0].description}
236+
fallback={
237+
<ResponseSummaryFallback
238+
response={endpoint.responses[0]}
239+
types={types}
240+
/>
241+
}
242+
/>
243+
}
244+
>
245+
<TypeDefinitionAnchorPart part="body">
246+
<EndpointResponseSection
247+
body={endpoint.responses[0].body}
248+
types={types}
249+
/>
250+
</TypeDefinitionAnchorPart>
251+
</EndpointSection>
252+
)
253+
) : null}
245254
{showErrors && endpoint.errors && endpoint.errors.length > 0 && (
246255
<TypeDefinitionAnchorPart part="error">
247256
<EndpointSection title="Errors" hideSeparator>

packages/fern-docs/bundle/src/components/api-reference/endpoints/EndpointContext.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { noop } from "ts-essentials";
77
import {
88
EndpointDefinition,
99
ErrorResponse,
10+
HttpResponse,
1011
Protocol,
1112
} from "@fern-api/fdr-sdk/api-definition";
1213
import { useCurrentAnchor } from "@fern-docs/components/hooks/use-anchor";
@@ -18,11 +19,19 @@ export const EndpointContext = React.createContext<
1819
{
1920
selectedError: ErrorResponse | undefined;
2021
setSelectedError: (error: ErrorResponse | undefined) => void;
22+
selectedResponse: HttpResponse | undefined;
23+
setSelectedResponse: (response: HttpResponse | undefined) => void;
24+
setSelectedResponseByStatusCode: (
25+
statusCode: number | string | undefined
26+
) => void;
2127
endpointProtocol: Protocol | undefined;
2228
} & Omit<ReturnType<typeof useExampleSelection>, "defaultLanguage">
2329
>({
2430
selectedError: undefined,
2531
setSelectedError: noop,
32+
selectedResponse: undefined,
33+
setSelectedResponse: noop,
34+
setSelectedResponseByStatusCode: noop,
2635
selectedExample: undefined,
2736
examplesByStatusCode: {},
2837
examplesByKeyAndStatusCode: {},
@@ -53,6 +62,30 @@ export function EndpointContextProvider({
5362
setSelectedExampleKey,
5463
} = useExampleSelection(endpoint);
5564

65+
const [selectedResponse, setSelectedResponse] = React.useState<
66+
HttpResponse | undefined
67+
>(endpoint.responses?.[0]);
68+
69+
const responseByStatusCode = React.useMemo(() => {
70+
const map: Record<string, HttpResponse> = {};
71+
endpoint.responses?.forEach((response) => {
72+
map[String(response.statusCode)] = response;
73+
});
74+
return map;
75+
}, [endpoint.responses]);
76+
77+
const setSelectedResponseByStatusCode = React.useCallback(
78+
(statusCode: number | string | undefined) => {
79+
if (statusCode != null) {
80+
const response = responseByStatusCode[String(statusCode)];
81+
if (response) {
82+
setSelectedResponse(response);
83+
}
84+
}
85+
},
86+
[responseByStatusCode, setSelectedResponse]
87+
);
88+
5689
const setStatusCode = React.useCallback(
5790
(statusCode: number | string | undefined) => {
5891
setSelectedExampleKey((prev) => {
@@ -105,6 +138,9 @@ export function EndpointContextProvider({
105138
() => ({
106139
selectedError,
107140
setSelectedError: handleSelectError,
141+
selectedResponse,
142+
setSelectedResponse,
143+
setSelectedResponseByStatusCode,
108144
selectedExample,
109145
examplesByStatusCode,
110146
examplesByKeyAndStatusCode,
@@ -116,6 +152,9 @@ export function EndpointContextProvider({
116152
[
117153
selectedError,
118154
handleSelectError,
155+
selectedResponse,
156+
setSelectedResponse,
157+
setSelectedResponseByStatusCode,
119158
selectedExample,
120159
examplesByStatusCode,
121160
examplesByKeyAndStatusCode,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use client";
2+
3+
import React, { useCallback } from "react";
4+
5+
import { ApiDefinition } from "@fern-api/fdr-sdk";
6+
import { HttpResponse } from "@fern-api/fdr-sdk/api-definition";
7+
8+
import { MdxServerComponentProseSuspense } from "@/mdx/components/server-component";
9+
10+
import { TypeDefinitionAnchorPart } from "../type-definitions/TypeDefinitionContext";
11+
import { renderResponseTitle } from "./EndpointContentCodeSnippets";
12+
import { useEndpointContext } from "./EndpointContext";
13+
import { EndpointResponseSection } from "./EndpointResponseSection";
14+
import { EndpointSection } from "./EndpointSection";
15+
import { ResponseSummaryFallback } from "./response-summary-fallback";
16+
17+
export interface EndpointMultipleResponseSectionProps {
18+
method: ApiDefinition.HttpMethod;
19+
responses: HttpResponse[];
20+
types: Record<string, ApiDefinition.TypeDefinition>;
21+
}
22+
23+
export function EndpointMultipleResponseSection({
24+
method,
25+
responses,
26+
types,
27+
}: EndpointMultipleResponseSectionProps) {
28+
const { selectedResponse, setSelectedResponse, setSelectedExampleKey } =
29+
useEndpointContext();
30+
31+
const getResponseId = useCallback(
32+
(response: HttpResponse) => {
33+
const title =
34+
ApiDefinition.getMessageForStatus(response.statusCode, method) ??
35+
"Response";
36+
37+
return renderResponseTitle(title, response.statusCode, true);
38+
},
39+
[method]
40+
);
41+
42+
if (!selectedResponse) {
43+
return null;
44+
}
45+
46+
return (
47+
<EndpointSection
48+
title="Response"
49+
description={
50+
<MdxServerComponentProseSuspense
51+
size="sm"
52+
className="text-(color:--grayscale-a11)"
53+
mdx={selectedResponse.description}
54+
fallback={
55+
<ResponseSummaryFallback
56+
response={selectedResponse}
57+
types={types}
58+
/>
59+
}
60+
/>
61+
}
62+
multipleResponsesProps={{
63+
responses,
64+
selectedResponse,
65+
setSelectedResponse,
66+
getResponseId,
67+
setSelectedExampleKey,
68+
}}
69+
>
70+
<TypeDefinitionAnchorPart part="body">
71+
<EndpointResponseSection body={selectedResponse.body} types={types} />
72+
</TypeDefinitionAnchorPart>
73+
</EndpointSection>
74+
);
75+
}

packages/fern-docs/bundle/src/components/api-reference/endpoints/EndpointSection.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,57 @@
1-
import React from "react";
1+
import React, { SetStateAction } from "react";
22

3+
import { RESET } from "jotai/utils";
4+
5+
import { HttpResponse } from "@fern-api/fdr-sdk/api-definition";
36
import { Separator } from "@fern-docs/components/Separator";
47

58
import { ErrorBoundary } from "@/components/error-boundary";
69

10+
import { SelectedExampleKey } from "../type-definitions/EndpointContent";
11+
import { ResponseSelect } from "./MultipleResponsesSelect";
712
import { SectionContainer, TypeDefinitionAnchor } from "./TypeDefinitionAnchor";
813

914
export function EndpointSection({
1015
title,
1116
description,
1217
children,
1318
hideSeparator,
19+
multipleResponsesProps,
1420
}: {
1521
title: React.ReactNode;
1622
description?: React.ReactNode;
1723
children: React.ReactNode;
1824
hideSeparator?: boolean;
25+
multipleResponsesProps?: {
26+
responses: HttpResponse[];
27+
selectedResponse: HttpResponse;
28+
setSelectedResponse: (response: HttpResponse) => void;
29+
getResponseId: (response: HttpResponse) => React.JSX.Element;
30+
setSelectedExampleKey: (
31+
update: typeof RESET | SetStateAction<SelectedExampleKey>
32+
) => void;
33+
};
1934
}) {
2035
return (
2136
<ErrorBoundary>
2237
<SectionContainer className="space-y-3">
2338
<TypeDefinitionAnchor>
24-
<h3 className="mt-0">{title}</h3>
39+
{multipleResponsesProps ? (
40+
<div className="mt-0 flex flex-row items-center gap-2">
41+
<h3 className="mb-0 mt-0">{title}</h3>
42+
<ResponseSelect
43+
responses={multipleResponsesProps.responses}
44+
selectedResponse={multipleResponsesProps.selectedResponse}
45+
setSelectedResponse={multipleResponsesProps.setSelectedResponse}
46+
getResponseId={multipleResponsesProps.getResponseId}
47+
setSelectedExampleKey={
48+
multipleResponsesProps.setSelectedExampleKey
49+
}
50+
/>
51+
</div>
52+
) : (
53+
<h3 className="mt-0">{title}</h3>
54+
)}
2555
</TypeDefinitionAnchor>
2656
{description}
2757
{hideSeparator ? null : <Separator />}

packages/fern-docs/bundle/src/components/api-reference/endpoints/ErrorExampleSelect.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ExamplesByStatusCode,
1616
StatusCode,
1717
} from "../type-definitions/EndpointContent";
18+
import { useEndpointContext } from "./EndpointContext";
1819

1920
export declare namespace ErrorExampleSelect {
2021
export interface Props {
@@ -36,9 +37,12 @@ export const ErrorExampleSelect: FC<
3637
examplesByStatusCode,
3738
getExampleId,
3839
}) => {
40+
const { setSelectedResponseByStatusCode } = useEndpointContext();
41+
3942
const handleValueChange = (value: string) => {
4043
const [statusCode, responseIndex] = value.split(":");
4144
setSelectedExampleKey(String(statusCode ?? ""), Number(responseIndex ?? 0));
45+
setSelectedResponseByStatusCode(statusCode ?? "");
4246
};
4347

4448
const statusCode = selectedExample?.exampleCall.responseStatusCode;

0 commit comments

Comments
 (0)