Skip to content

Commit e914903

Browse files
RND-3387: synchronize response tabs (#2457)
Co-authored-by: Samy Pessé <[email protected]>
1 parent b6a9967 commit e914903

File tree

9 files changed

+46
-8
lines changed

9 files changed

+46
-8
lines changed

.changeset/purple-forks-refuse.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@gitbook/react-openapi': minor
3+
'gitbook': patch
4+
---
5+
6+
Synchronize response and response example tabs

packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
5656
CodeBlock: PlainCodeBlock,
5757
defaultInteractiveOpened: context.mode === 'print',
5858
id: block.meta?.id,
59+
blockKey: block.key,
5960
}}
6061
className="openapi-block"
6162
/>

packages/react-openapi/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@
1515
"classnames": "^2.5.1",
1616
"flatted": "^3.2.9",
1717
"openapi-types": "^12.1.3",
18-
"yaml": "1.10.2",
19-
"swagger2openapi": "^7.0.8"
18+
"swagger2openapi": "^7.0.8",
19+
"yaml": "1.10.2"
2020
},
2121
"devDependencies": {
2222
"@types/swagger2openapi": "^7.0.4",
2323
"bun-types": "^1.1.20",
2424
"typescript": "^5.5.3"
2525
},
2626
"peerDependencies": {
27-
"react": "*"
27+
"react": "*",
28+
"recoil": "^0.7.7"
2829
},
2930
"scripts": {
3031
"build": "tsc",

packages/react-openapi/src/InteractiveSection.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
import classNames from 'classnames';
44
import React from 'react';
5+
import { atom, useRecoilState } from 'recoil';
56

67
interface InteractiveSectionTab {
78
key: string;
89
label: string;
910
body: React.ReactNode;
1011
}
1112

13+
const syncedTabsAtom = atom<Record<string, string>>({
14+
key: 'syncedTabState',
15+
default: {},
16+
});
17+
1218
/**
1319
* To optimize rendering, most of the components are server-components,
1420
* and the interactiveness is mainly handled by a few key components like this one.
@@ -34,6 +40,8 @@ export function InteractiveSection(props: {
3440
children?: React.ReactNode;
3541
/** Children to display within the container */
3642
overlay?: React.ReactNode;
43+
/** An optional key referencing a value in global state */
44+
stateKey?: string;
3745
}) {
3846
const {
3947
id,
@@ -47,12 +55,18 @@ export function InteractiveSection(props: {
4755
overlay,
4856
toggleOpenIcon = '▶',
4957
toggleCloseIcon = '▼',
58+
stateKey,
5059
} = props;
60+
const [syncedTabs, setSyncedTabs] = useRecoilState(syncedTabsAtom);
61+
const tabFromState =
62+
stateKey && stateKey in syncedTabs
63+
? tabs.find((tab) => tab.key === syncedTabs[stateKey])
64+
: undefined;
5165

5266
const [opened, setOpened] = React.useState(defaultOpened);
53-
const [selectedTabKey, setSelectedTab] = React.useState(defaultTab);
67+
const [selectedTabKey, setSelectedTab] = React.useState(tabFromState?.key ?? defaultTab);
5468
const selectedTab: InteractiveSectionTab | undefined =
55-
tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
69+
tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
5670

5771
return (
5872
<div
@@ -99,6 +113,12 @@ export function InteractiveSection(props: {
99113
value={selectedTab.key}
100114
onChange={(event) => {
101115
setSelectedTab(event.target.value);
116+
if (stateKey) {
117+
setSyncedTabs((state) => ({
118+
...state,
119+
[stateKey]: event.target.value,
120+
}));
121+
}
102122
setOpened(true);
103123
}}
104124
>

packages/react-openapi/src/OpenAPIOperation.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function OpenAPIOperation(props: {
2424
const clientContext: OpenAPIClientContext = {
2525
defaultInteractiveOpened: context.defaultInteractiveOpened,
2626
icons: context.icons,
27+
blockKey: context.blockKey,
2728
};
2829

2930
return (

packages/react-openapi/src/OpenAPIResponseExample.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { InteractiveSection } from './InteractiveSection';
33
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
44
import { generateSchemaExample } from './generateSchemaExample';
55
import { OpenAPIContextProps } from './types';
6-
import { noReference } from './utils';
6+
import { createStateKey, noReference } from './utils';
77

88
/**
99
* Display an example of the response content.
@@ -78,6 +78,7 @@ export function OpenAPIResponseExample(props: {
7878

7979
return (
8080
<InteractiveSection
81+
stateKey={createStateKey('response', context.blockKey)}
8182
header="Response"
8283
className="openapi-response-example"
8384
tabs={examples}

packages/react-openapi/src/OpenAPIResponses.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import classNames from 'classnames';
33
import { OpenAPIV3 } from 'openapi-types';
4-
import { noReference } from './utils';
4+
import { createStateKey, noReference } from './utils';
55
import { OpenAPIResponse } from './OpenAPIResponse';
66
import { OpenAPIClientContext } from './types';
77
import { InteractiveSection } from './InteractiveSection';
@@ -17,6 +17,7 @@ export function OpenAPIResponses(props: {
1717

1818
return (
1919
<InteractiveSection
20+
stateKey={createStateKey('response', context.blockKey)}
2021
header="Response"
2122
className={classNames('openapi-responses')}
2223
tabs={Object.entries(responses).map(([statusCode, response]) => {

packages/react-openapi/src/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export interface OpenAPIClientContext {
1515
* @default false
1616
*/
1717
defaultInteractiveOpened?: boolean;
18-
18+
/**
19+
* The key of the block
20+
*/
21+
blockKey?: string;
1922
/** Optional id attached to the OpenAPI Operation heading and used as an anchor */
2023
id?: string;
2124
}

packages/react-openapi/src/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
77

88
return input;
99
}
10+
11+
export function createStateKey(key: string, scope?: string) {
12+
return scope ? `${scope}_${key}` : key;
13+
}

0 commit comments

Comments
 (0)