mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 17:43:24 +00:00
RND-3387: synchronize response tabs (#2457)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
This commit is contained in:
@@ -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,
|
CodeBlock: PlainCodeBlock,
|
||||||
defaultInteractiveOpened: context.mode === 'print',
|
defaultInteractiveOpened: context.mode === 'print',
|
||||||
id: block.meta?.id,
|
id: block.meta?.id,
|
||||||
|
blockKey: block.key,
|
||||||
}}
|
}}
|
||||||
className="openapi-block"
|
className="openapi-block"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"flatted": "^3.2.9",
|
"flatted": "^3.2.9",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
"yaml": "1.10.2",
|
"swagger2openapi": "^7.0.8",
|
||||||
"swagger2openapi": "^7.0.8"
|
"yaml": "1.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/swagger2openapi": "^7.0.4",
|
"@types/swagger2openapi": "^7.0.4",
|
||||||
@@ -24,7 +24,8 @@
|
|||||||
"typescript": "^5.5.3"
|
"typescript": "^5.5.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*"
|
"react": "*",
|
||||||
|
"recoil": "^0.7.7"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { atom, useRecoilState } from 'recoil';
|
||||||
|
|
||||||
interface InteractiveSectionTab {
|
interface InteractiveSectionTab {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -9,6 +10,11 @@ interface InteractiveSectionTab {
|
|||||||
body: React.ReactNode;
|
body: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const syncedTabsAtom = atom<Record<string, string>>({
|
||||||
|
key: 'syncedTabState',
|
||||||
|
default: {},
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To optimize rendering, most of the components are server-components,
|
* To optimize rendering, most of the components are server-components,
|
||||||
* and the interactiveness is mainly handled by a few key components like this one.
|
* 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?: React.ReactNode;
|
||||||
/** Children to display within the container */
|
/** Children to display within the container */
|
||||||
overlay?: React.ReactNode;
|
overlay?: React.ReactNode;
|
||||||
|
/** An optional key referencing a value in global state */
|
||||||
|
stateKey?: string;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@@ -47,12 +55,18 @@ export function InteractiveSection(props: {
|
|||||||
overlay,
|
overlay,
|
||||||
toggleOpenIcon = '▶',
|
toggleOpenIcon = '▶',
|
||||||
toggleCloseIcon = '▼',
|
toggleCloseIcon = '▼',
|
||||||
|
stateKey,
|
||||||
} = props;
|
} = 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 [opened, setOpened] = React.useState(defaultOpened);
|
||||||
const [selectedTabKey, setSelectedTab] = React.useState(defaultTab);
|
const [selectedTabKey, setSelectedTab] = React.useState(tabFromState?.key ?? defaultTab);
|
||||||
const selectedTab: InteractiveSectionTab | undefined =
|
const selectedTab: InteractiveSectionTab | undefined =
|
||||||
tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
|
tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -99,6 +113,12 @@ export function InteractiveSection(props: {
|
|||||||
value={selectedTab.key}
|
value={selectedTab.key}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setSelectedTab(event.target.value);
|
setSelectedTab(event.target.value);
|
||||||
|
if (stateKey) {
|
||||||
|
setSyncedTabs((state) => ({
|
||||||
|
...state,
|
||||||
|
[stateKey]: event.target.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
setOpened(true);
|
setOpened(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function OpenAPIOperation(props: {
|
|||||||
const clientContext: OpenAPIClientContext = {
|
const clientContext: OpenAPIClientContext = {
|
||||||
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
||||||
icons: context.icons,
|
icons: context.icons,
|
||||||
|
blockKey: context.blockKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { InteractiveSection } from './InteractiveSection';
|
|||||||
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
|
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
|
||||||
import { generateSchemaExample } from './generateSchemaExample';
|
import { generateSchemaExample } from './generateSchemaExample';
|
||||||
import { OpenAPIContextProps } from './types';
|
import { OpenAPIContextProps } from './types';
|
||||||
import { noReference } from './utils';
|
import { createStateKey, noReference } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an example of the response content.
|
* Display an example of the response content.
|
||||||
@@ -78,6 +78,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection
|
||||||
|
stateKey={createStateKey('response', context.blockKey)}
|
||||||
header="Response"
|
header="Response"
|
||||||
className="openapi-response-example"
|
className="openapi-response-example"
|
||||||
tabs={examples}
|
tabs={examples}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { noReference } from './utils';
|
import { createStateKey, noReference } from './utils';
|
||||||
import { OpenAPIResponse } from './OpenAPIResponse';
|
import { OpenAPIResponse } from './OpenAPIResponse';
|
||||||
import { OpenAPIClientContext } from './types';
|
import { OpenAPIClientContext } from './types';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
@@ -17,6 +17,7 @@ export function OpenAPIResponses(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection
|
||||||
|
stateKey={createStateKey('response', context.blockKey)}
|
||||||
header="Response"
|
header="Response"
|
||||||
className={classNames('openapi-responses')}
|
className={classNames('openapi-responses')}
|
||||||
tabs={Object.entries(responses).map(([statusCode, response]) => {
|
tabs={Object.entries(responses).map(([statusCode, response]) => {
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ export interface OpenAPIClientContext {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
defaultInteractiveOpened?: boolean;
|
defaultInteractiveOpened?: boolean;
|
||||||
|
/**
|
||||||
|
* The key of the block
|
||||||
|
*/
|
||||||
|
blockKey?: string;
|
||||||
/** Optional id attached to the OpenAPI Operation heading and used as an anchor */
|
/** Optional id attached to the OpenAPI Operation heading and used as an anchor */
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,3 +7,7 @@ export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
|
|||||||
|
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createStateKey(key: string, scope?: string) {
|
||||||
|
return scope ? `${scope}_${key}` : key;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user