RND-3387: synchronize response tabs (#2457)

Co-authored-by: Samy Pessé <samypesse@gmail.com>
This commit is contained in:
Brett Jephson
2024-09-12 12:01:29 +02:00
committed by GitHub
parent b6a9967959
commit e9149036f6
9 changed files with 46 additions and 8 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': minor
'gitbook': patch
---
Synchronize response and response example tabs
@@ -56,6 +56,7 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
CodeBlock: PlainCodeBlock,
defaultInteractiveOpened: context.mode === 'print',
id: block.meta?.id,
blockKey: block.key,
}}
className="openapi-block"
/>
+4 -3
View File
@@ -15,8 +15,8 @@
"classnames": "^2.5.1",
"flatted": "^3.2.9",
"openapi-types": "^12.1.3",
"yaml": "1.10.2",
"swagger2openapi": "^7.0.8"
"swagger2openapi": "^7.0.8",
"yaml": "1.10.2"
},
"devDependencies": {
"@types/swagger2openapi": "^7.0.4",
@@ -24,7 +24,8 @@
"typescript": "^5.5.3"
},
"peerDependencies": {
"react": "*"
"react": "*",
"recoil": "^0.7.7"
},
"scripts": {
"build": "tsc",
@@ -2,6 +2,7 @@
import classNames from 'classnames';
import React from 'react';
import { atom, useRecoilState } from 'recoil';
interface InteractiveSectionTab {
key: string;
@@ -9,6 +10,11 @@ interface InteractiveSectionTab {
body: React.ReactNode;
}
const syncedTabsAtom = atom<Record<string, string>>({
key: 'syncedTabState',
default: {},
});
/**
* To optimize rendering, most of the components are server-components,
* and the interactiveness is mainly handled by a few key components like this one.
@@ -34,6 +40,8 @@ export function InteractiveSection(props: {
children?: React.ReactNode;
/** Children to display within the container */
overlay?: React.ReactNode;
/** An optional key referencing a value in global state */
stateKey?: string;
}) {
const {
id,
@@ -47,12 +55,18 @@ export function InteractiveSection(props: {
overlay,
toggleOpenIcon = '▶',
toggleCloseIcon = '▼',
stateKey,
} = props;
const [syncedTabs, setSyncedTabs] = useRecoilState(syncedTabsAtom);
const tabFromState =
stateKey && stateKey in syncedTabs
? tabs.find((tab) => tab.key === syncedTabs[stateKey])
: undefined;
const [opened, setOpened] = React.useState(defaultOpened);
const [selectedTabKey, setSelectedTab] = React.useState(defaultTab);
const [selectedTabKey, setSelectedTab] = React.useState(tabFromState?.key ?? defaultTab);
const selectedTab: InteractiveSectionTab | undefined =
tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
return (
<div
@@ -99,6 +113,12 @@ export function InteractiveSection(props: {
value={selectedTab.key}
onChange={(event) => {
setSelectedTab(event.target.value);
if (stateKey) {
setSyncedTabs((state) => ({
...state,
[stateKey]: event.target.value,
}));
}
setOpened(true);
}}
>
@@ -24,6 +24,7 @@ export function OpenAPIOperation(props: {
const clientContext: OpenAPIClientContext = {
defaultInteractiveOpened: context.defaultInteractiveOpened,
icons: context.icons,
blockKey: context.blockKey,
};
return (
@@ -3,7 +3,7 @@ import { InteractiveSection } from './InteractiveSection';
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
import { generateSchemaExample } from './generateSchemaExample';
import { OpenAPIContextProps } from './types';
import { noReference } from './utils';
import { createStateKey, noReference } from './utils';
/**
* Display an example of the response content.
@@ -78,6 +78,7 @@ export function OpenAPIResponseExample(props: {
return (
<InteractiveSection
stateKey={createStateKey('response', context.blockKey)}
header="Response"
className="openapi-response-example"
tabs={examples}
@@ -1,7 +1,7 @@
import * as React from 'react';
import classNames from 'classnames';
import { OpenAPIV3 } from 'openapi-types';
import { noReference } from './utils';
import { createStateKey, noReference } from './utils';
import { OpenAPIResponse } from './OpenAPIResponse';
import { OpenAPIClientContext } from './types';
import { InteractiveSection } from './InteractiveSection';
@@ -17,6 +17,7 @@ export function OpenAPIResponses(props: {
return (
<InteractiveSection
stateKey={createStateKey('response', context.blockKey)}
header="Response"
className={classNames('openapi-responses')}
tabs={Object.entries(responses).map(([statusCode, response]) => {
+4 -1
View File
@@ -15,7 +15,10 @@ export interface OpenAPIClientContext {
* @default false
*/
defaultInteractiveOpened?: boolean;
/**
* The key of the block
*/
blockKey?: string;
/** Optional id attached to the OpenAPI Operation heading and used as an anchor */
id?: string;
}
+4
View File
@@ -7,3 +7,7 @@ export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
return input;
}
export function createStateKey(key: string, scope?: string) {
return scope ? `${scope}_${key}` : key;
}