Compare commits

...

1 Commits

Author SHA1 Message Date
Nolann Biron 11254e6ee8 v1 2025-02-17 18:22:30 +01:00
4 changed files with 146 additions and 29 deletions
@@ -5,6 +5,9 @@ import { checkIsReference, createStateKey, resolveDescription } from './utils';
import { stringifyOpenAPI } from './stringifyOpenAPI';
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
import { InteractiveSection } from './InteractiveSection';
import { Markdown } from './Markdown';
import React from 'react';
import { OpenAPIResponseMultipleExample } from './OpenAPIResponseMultipleExample';
/**
* Display an example of the response content.
@@ -61,18 +64,20 @@ export function OpenAPIResponseExample(props: {
};
}
const example = handleUnresolvedReference(
const examples = handleUnresolvedReference(
(() => {
const { examples, example } = mediaTypeObject;
if (examples) {
const key = Object.keys(examples)[0];
if (key) {
// @TODO handle multiple examples
const firstExample = examples[key];
if (firstExample) {
return firstExample;
}
if (Array.isArray(examples)) {
return examples.map((example) => ({
value: example,
}));
}
return Object.entries(examples).map(([key, value]) => ({
summary: key,
value: value,
}));
}
if (example) {
@@ -88,21 +93,23 @@ export function OpenAPIResponseExample(props: {
})(),
);
if (!examples || (Array.isArray(examples) && examples.length === 0)) {
return {
key: key,
label: key,
body: <OpenAPIEmptyResponseExample description={responseObject.description} />,
};
}
return {
key: key,
label: key,
description: resolveDescription(responseObject),
body: example?.value ? (
<context.CodeBlock
code={
typeof example.value === 'string'
? example.value
: stringifyOpenAPI(example.value, null, 2)
}
syntax="json"
body: (
<OpenAPIResponseExampleBody
examples={examples}
context={context}
description={responseObject.description}
/>
) : (
<OpenAPIEmptyResponseExample />
),
};
})
@@ -123,17 +130,62 @@ export function OpenAPIResponseExample(props: {
);
}
function OpenAPIEmptyResponseExample() {
function OpenAPIEmptyResponseExample(props: { description?: string }) {
const { description } = props;
return (
<pre className="openapi-response-example-empty">
<p>No body</p>
</pre>
<>
<pre className="openapi-response-example-empty">
<p>No body</p>
</pre>
{description ? <Markdown source={description} className="openapi-tabs-footer" /> : null}
</>
);
}
function OpenAPIResponseExampleBody(props: {
examples: OpenAPIV3.ExampleObject[] | OpenAPIV3.ExampleObject;
context: OpenAPIContextProps;
description?: string;
}) {
const { examples, context, description } = props;
if (!Array.isArray(examples)) {
return (
<>
<context.CodeBlock
code={
typeof examples.value === 'string'
? examples.value
: stringifyOpenAPI(examples.value, null, 2)
}
syntax="json"
/>
{description ? (
<Markdown source={description} className="openapi-tabs-footer" />
) : null}
</>
);
}
if (examples.length === 0) {
return <OpenAPIEmptyResponseExample />;
}
return <OpenAPIResponseMultipleExample examples={examples} context={context} />;
}
function handleUnresolvedReference(
input: OpenAPIV3.ExampleObject | null,
): OpenAPIV3.ExampleObject | null {
input: OpenAPIV3.ExampleObject | OpenAPIV3.ExampleObject[] | null,
): OpenAPIV3.ExampleObject | OpenAPIV3.ExampleObject[] | null {
if (Array.isArray(input)) {
return input.map(
(example) => handleUnresolvedReference(example) as OpenAPIV3.ExampleObject,
);
}
const isReference = checkIsReference(input?.value);
if (isReference) {
@@ -0,0 +1,25 @@
import { OpenAPIV3 } from '@scalar/openapi-types';
import { OpenAPIContextProps } from './types';
import { OpenAPIResponseMultipleExampleClient } from './OpenAPIResponseMultipleExampleClient';
export function OpenAPIResponseMultipleExample(props: {
examples: OpenAPIV3.ExampleObject[];
context: OpenAPIContextProps;
}) {
const { examples, context } = props;
return (
<OpenAPIResponseMultipleExampleClient examples={examples}>
<context.CodeBlock
syntax="json"
code=""
// code={
// typeof example?.value === 'string'
// ? example?.value
// : stringifyOpenAPI(example?.value, null, 2)
// }
/>
{/* )} */}
</OpenAPIResponseMultipleExampleClient>
);
}
@@ -0,0 +1,44 @@
'use client';
import { OpenAPIV3 } from '@scalar/openapi-types';
import React, { cloneElement, ReactNode } from 'react';
export function OpenAPIResponseMultipleExampleClient(props: {
examples: OpenAPIV3.ExampleObject[];
children: ReactNode;
}) {
const { examples, children: Comp } = props;
const examplesWithId = examples.map((example, index) => ({
...example,
id: `example-${index}`,
}));
const [selectedExample, setSelectedExample] = React.useState(examplesWithId[0]);
const handleSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
const selected = examplesWithId.find((ex) => ex.id === e.target.value);
if (!selected) return;
setSelectedExample(selected);
};
return (
<>
{/* {typeof children === 'function' ? children({ example: selectedExample }) : children} */}
{cloneElement(Comp, { code: selectedExample?.value })}
<div className="openapi-tabs-footer">
<select onChange={handleSelect} value={selectedExample?.id}>
{examplesWithId.map((example, index) => (
<option key={index} value={example.id}>
{example.summary || `Example ${index + 1}`}
</option>
))}
</select>
</div>
</>
);
}
@@ -2,7 +2,6 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { Key, Tab, TabList, TabPanel, Tabs, TabsProps } from 'react-aria-components';
import { Markdown } from './Markdown';
import { useSyncedTabsGlobalState } from './useSyncedTabsGlobalState';
import { useIntersectionObserver } from 'usehooks-ts';
@@ -145,9 +144,6 @@ export function OpenAPITabsPanels() {
className="openapi-tabs-panel"
>
{selectedTab.body}
{selectedTab.description ? (
<Markdown source={selectedTab.description} className="openapi-tabs-footer" />
) : null}
</TabPanel>
);
}