Skip to content

Commit

Permalink
RND-3387: synchronize response tabs (#2457)
Browse files Browse the repository at this point in the history
Co-authored-by: Samy Pessé <[email protected]>
  • Loading branch information
BrettJephson and SamyPesse authored Sep 12, 2024
1 parent b6a9967 commit e914903
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changeset/purple-forks-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': minor
'gitbook': patch
---

Synchronize response and response example tabs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
Expand Down
7 changes: 4 additions & 3 deletions packages/react-openapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@
"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",
"bun-types": "^1.1.20",
"typescript": "^5.5.3"
},
"peerDependencies": {
"react": "*"
"react": "*",
"recoil": "^0.7.7"
},
"scripts": {
"build": "tsc",
Expand Down
24 changes: 22 additions & 2 deletions packages/react-openapi/src/InteractiveSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

import classNames from 'classnames';
import React from 'react';
import { atom, useRecoilState } from 'recoil';

interface InteractiveSectionTab {
key: string;
label: string;
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.
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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);
}}
>
Expand Down
1 change: 1 addition & 0 deletions packages/react-openapi/src/OpenAPIOperation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function OpenAPIOperation(props: {
const clientContext: OpenAPIClientContext = {
defaultInteractiveOpened: context.defaultInteractiveOpened,
icons: context.icons,
blockKey: context.blockKey,
};

return (
Expand Down
3 changes: 2 additions & 1 deletion packages/react-openapi/src/OpenAPIResponseExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -78,6 +78,7 @@ export function OpenAPIResponseExample(props: {

return (
<InteractiveSection
stateKey={createStateKey('response', context.blockKey)}
header="Response"
className="openapi-response-example"
tabs={examples}
Expand Down
3 changes: 2 additions & 1 deletion packages/react-openapi/src/OpenAPIResponses.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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]) => {
Expand Down
5 changes: 4 additions & 1 deletion packages/react-openapi/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/react-openapi/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit e914903

Please sign in to comment.