@gitbook/react-openapi 1.1.10 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/InteractiveSection.d.ts +4 -0
  3. package/dist/InteractiveSection.jsx +11 -11
  4. package/dist/OpenAPICodeSample.d.ts +2 -1
  5. package/dist/OpenAPICodeSample.jsx +6 -5
  6. package/dist/OpenAPICodeSampleInteractive.d.ts +3 -0
  7. package/dist/OpenAPICodeSampleInteractive.jsx +19 -43
  8. package/dist/OpenAPICodeSampleSelector.d.ts +3 -4
  9. package/dist/OpenAPICodeSampleSelector.jsx +6 -11
  10. package/dist/OpenAPICopyButton.d.ts +2 -0
  11. package/dist/OpenAPICopyButton.jsx +5 -2
  12. package/dist/OpenAPIDisclosure.d.ts +4 -3
  13. package/dist/OpenAPIDisclosure.jsx +8 -11
  14. package/dist/OpenAPIDisclosureGroup.d.ts +7 -3
  15. package/dist/OpenAPIDisclosureGroup.jsx +18 -18
  16. package/dist/OpenAPIExample.d.ts +4 -22
  17. package/dist/OpenAPIExample.jsx +5 -72
  18. package/dist/OpenAPIMediaType.d.ts +21 -0
  19. package/dist/OpenAPIMediaType.jsx +61 -0
  20. package/dist/OpenAPIOperation.d.ts +3 -2
  21. package/dist/OpenAPIOperation.jsx +9 -68
  22. package/dist/OpenAPIOperationDescription.d.ts +9 -0
  23. package/dist/OpenAPIOperationDescription.jsx +22 -0
  24. package/dist/OpenAPIOperationStability.d.ts +9 -0
  25. package/dist/OpenAPIOperationStability.jsx +27 -0
  26. package/dist/OpenAPIPath.d.ts +2 -0
  27. package/dist/OpenAPIPath.jsx +3 -2
  28. package/dist/OpenAPIRequestBody.d.ts +3 -1
  29. package/dist/OpenAPIRequestBody.jsx +4 -3
  30. package/dist/OpenAPIResponse.d.ts +1 -1
  31. package/dist/OpenAPIResponse.jsx +1 -1
  32. package/dist/OpenAPIResponseExample.d.ts +3 -2
  33. package/dist/OpenAPIResponseExample.jsx +24 -63
  34. package/dist/OpenAPIResponseExampleContent.d.ts +19 -0
  35. package/dist/OpenAPIResponseExampleContent.jsx +57 -0
  36. package/dist/OpenAPIResponses.d.ts +1 -1
  37. package/dist/OpenAPIResponses.jsx +49 -36
  38. package/dist/OpenAPISchema.d.ts +1 -1
  39. package/dist/OpenAPISchema.jsx +103 -15
  40. package/dist/OpenAPISchemaName.d.ts +2 -0
  41. package/dist/OpenAPISchemaName.jsx +19 -10
  42. package/dist/OpenAPISchemaServer.d.ts +1 -1
  43. package/dist/OpenAPISecurities.d.ts +2 -1
  44. package/dist/OpenAPISecurities.jsx +11 -10
  45. package/dist/OpenAPISelect.d.ts +10 -3
  46. package/dist/OpenAPISelect.jsx +20 -9
  47. package/dist/OpenAPISpec.d.ts +3 -2
  48. package/dist/OpenAPISpec.jsx +11 -9
  49. package/dist/OpenAPIWebhook.d.ts +10 -0
  50. package/dist/OpenAPIWebhook.jsx +23 -0
  51. package/dist/OpenAPIWebhookExample.d.ts +6 -0
  52. package/dist/OpenAPIWebhookExample.jsx +41 -0
  53. package/dist/ScalarApiButton.d.ts +2 -0
  54. package/dist/ScalarApiButton.jsx +4 -3
  55. package/dist/StaticSection.d.ts +4 -1
  56. package/dist/StaticSection.jsx +13 -4
  57. package/dist/code-samples.js +57 -39
  58. package/dist/common/OpenAPIColumnSpec.d.ts +6 -0
  59. package/dist/common/OpenAPIColumnSpec.jsx +20 -0
  60. package/dist/common/OpenAPIOperationDescription.d.ts +6 -0
  61. package/dist/common/OpenAPIOperationDescription.jsx +19 -0
  62. package/dist/common/OpenAPIStability.d.ts +4 -0
  63. package/dist/common/OpenAPIStability.jsx +15 -0
  64. package/dist/common/OpenAPISummary.d.ts +6 -0
  65. package/dist/common/OpenAPISummary.jsx +30 -0
  66. package/dist/context.d.ts +23 -2
  67. package/dist/context.js +32 -0
  68. package/dist/getOrCreateStoreByKey.d.ts +1 -1
  69. package/dist/getOrCreateStoreByKey.js +0 -1
  70. package/dist/index.d.ts +5 -1
  71. package/dist/index.js +3 -0
  72. package/dist/resolveOpenAPIWebhook.d.ts +11 -0
  73. package/dist/resolveOpenAPIWebhook.js +127 -0
  74. package/dist/schemas/OpenAPISchemas.d.ts +2 -2
  75. package/dist/schemas/OpenAPISchemas.jsx +19 -23
  76. package/dist/stringifyOpenAPI.d.ts +1 -1
  77. package/dist/stringifyOpenAPI.js +6 -3
  78. package/dist/translate.d.ts +10 -0
  79. package/dist/translate.jsx +75 -0
  80. package/dist/translations/de.d.ts +37 -0
  81. package/dist/translations/de.js +37 -0
  82. package/dist/translations/en.d.ts +37 -0
  83. package/dist/translations/en.js +37 -0
  84. package/dist/translations/es.d.ts +37 -0
  85. package/dist/translations/es.js +37 -0
  86. package/dist/translations/fr.d.ts +37 -0
  87. package/dist/translations/fr.js +37 -0
  88. package/dist/translations/index.d.ts +341 -0
  89. package/dist/translations/index.js +27 -0
  90. package/dist/translations/ja.d.ts +37 -0
  91. package/dist/translations/ja.js +37 -0
  92. package/dist/translations/nl.d.ts +37 -0
  93. package/dist/translations/nl.js +37 -0
  94. package/dist/translations/no.d.ts +37 -0
  95. package/dist/translations/no.js +37 -0
  96. package/dist/translations/pt-br.d.ts +37 -0
  97. package/dist/translations/pt-br.js +37 -0
  98. package/dist/translations/types.d.ts +5 -0
  99. package/dist/translations/types.js +1 -0
  100. package/dist/translations/zh.d.ts +37 -0
  101. package/dist/translations/zh.js +37 -0
  102. package/dist/tsconfig.build.tsbuildinfo +1 -1
  103. package/dist/types.d.ts +8 -50
  104. package/dist/util/example.d.ts +35 -0
  105. package/dist/util/example.jsx +103 -0
  106. package/dist/utils.d.ts +18 -0
  107. package/dist/utils.js +57 -0
  108. package/package.json +3 -3
  109. package/src/InteractiveSection.tsx +16 -14
  110. package/src/OpenAPICodeSample.tsx +22 -4
  111. package/src/OpenAPICodeSampleInteractive.tsx +38 -58
  112. package/src/OpenAPICodeSampleSelector.tsx +19 -12
  113. package/src/OpenAPICopyButton.tsx +7 -2
  114. package/src/OpenAPIDisclosure.tsx +20 -22
  115. package/src/OpenAPIDisclosureGroup.tsx +41 -22
  116. package/src/OpenAPIExample.tsx +8 -82
  117. package/src/OpenAPIMediaType.tsx +139 -0
  118. package/src/OpenAPIOperation.tsx +11 -100
  119. package/src/OpenAPIOperationDescription.tsx +34 -0
  120. package/src/OpenAPIOperationStability.tsx +39 -0
  121. package/src/OpenAPIPath.tsx +4 -1
  122. package/src/OpenAPIRequestBody.tsx +9 -4
  123. package/src/OpenAPIResponse.tsx +2 -2
  124. package/src/OpenAPIResponseExample.tsx +39 -108
  125. package/src/OpenAPIResponseExampleContent.tsx +123 -0
  126. package/src/OpenAPIResponses.tsx +84 -62
  127. package/src/OpenAPISchema.test.ts +80 -0
  128. package/src/OpenAPISchema.tsx +123 -16
  129. package/src/OpenAPISchemaName.tsx +26 -11
  130. package/src/OpenAPISchemaServer.tsx +1 -1
  131. package/src/OpenAPISecurities.tsx +33 -12
  132. package/src/OpenAPISelect.tsx +42 -16
  133. package/src/OpenAPISpec.tsx +21 -10
  134. package/src/OpenAPIWebhook.tsx +33 -0
  135. package/src/OpenAPIWebhookExample.tsx +60 -0
  136. package/src/ScalarApiButton.tsx +6 -6
  137. package/src/StaticSection.tsx +37 -5
  138. package/src/code-samples.test.ts +3 -1
  139. package/src/code-samples.ts +67 -54
  140. package/src/common/OpenAPIColumnSpec.tsx +31 -0
  141. package/src/common/OpenAPIOperationDescription.tsx +31 -0
  142. package/src/common/OpenAPIStability.tsx +23 -0
  143. package/src/common/OpenAPISummary.tsx +45 -0
  144. package/src/context.ts +37 -2
  145. package/src/getOrCreateStoreByKey.ts +1 -3
  146. package/src/index.ts +5 -1
  147. package/src/resolveOpenAPIWebhook.ts +99 -0
  148. package/src/schemas/OpenAPISchemas.tsx +34 -34
  149. package/src/stringifyOpenAPI.ts +11 -3
  150. package/src/translate.tsx +80 -0
  151. package/src/translations/de.ts +37 -0
  152. package/src/translations/en.ts +37 -0
  153. package/src/translations/es.ts +37 -0
  154. package/src/translations/fr.ts +37 -0
  155. package/src/translations/index.ts +33 -0
  156. package/src/translations/ja.ts +37 -0
  157. package/src/translations/nl.ts +37 -0
  158. package/src/translations/no.ts +37 -0
  159. package/src/translations/pt-br.ts +37 -0
  160. package/src/translations/types.ts +7 -0
  161. package/src/translations/zh.ts +37 -0
  162. package/src/types.ts +11 -53
  163. package/src/util/example.tsx +129 -0
  164. package/src/utils.ts +67 -0
@@ -0,0 +1,34 @@
1
+ import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
2
+ import { Markdown } from './Markdown';
3
+ import type { OpenAPIContext } from './context';
4
+ import { resolveDescription } from './utils';
5
+
6
+ /**
7
+ * Display the description of an OpenAPI operation.
8
+ */
9
+ export function OpenAPIOperationDescription(props: {
10
+ operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
11
+ context: OpenAPIContext;
12
+ }) {
13
+ const { operation } = props;
14
+ if (operation['x-gitbook-description-document']) {
15
+ return (
16
+ <div className="openapi-intro">
17
+ {props.context.renderDocument({
18
+ document: operation['x-gitbook-description-document'],
19
+ })}
20
+ </div>
21
+ );
22
+ }
23
+
24
+ const description = resolveDescription(operation);
25
+ if (!description) {
26
+ return null;
27
+ }
28
+
29
+ return (
30
+ <div className="openapi-intro">
31
+ <Markdown className="openapi-description" source={description} />
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,39 @@
1
+ import type { OpenAPIStability } from '@gitbook/openapi-parser';
2
+ import type { OpenAPIContext } from './context';
3
+ import { t } from './translate';
4
+
5
+ /**
6
+ * Display the stability of an OpenAPI operation.
7
+ */
8
+ export function OpenAPIOperationStability(props: {
9
+ stability: OpenAPIStability;
10
+ context: OpenAPIContext;
11
+ }) {
12
+ const { stability, context } = props;
13
+
14
+ const stabilityLabel = getStabilityLabel(stability, context);
15
+
16
+ if (!stabilityLabel) {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <div className={`openapi-stability openapi-stability-${stability}`}>{stabilityLabel}</div>
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Get the stability label for the given stability level.
27
+ */
28
+ function getStabilityLabel(stability: OpenAPIStability, context: OpenAPIContext) {
29
+ switch (stability) {
30
+ case 'experimental':
31
+ return t(context.translation, 'stability_experimental');
32
+ case 'alpha':
33
+ return t(context.translation, 'stability_alpha');
34
+ case 'beta':
35
+ return t(context.translation, 'stability_beta');
36
+ default:
37
+ return null;
38
+ }
39
+ }
@@ -1,4 +1,5 @@
1
1
  import { OpenAPICopyButton } from './OpenAPICopyButton';
2
+ import { type OpenAPIUniversalContext, getOpenAPIClientContext } from './context';
2
3
  import type { OpenAPIOperationData } from './types';
3
4
  import { getDefaultServerURL } from './util/server';
4
5
 
@@ -7,6 +8,7 @@ import { getDefaultServerURL } from './util/server';
7
8
  */
8
9
  export function OpenAPIPath(props: {
9
10
  data: OpenAPIOperationData;
11
+ context: OpenAPIUniversalContext;
10
12
  /** Whether to show the server URL.
11
13
  * @default true
12
14
  */
@@ -17,7 +19,7 @@ export function OpenAPIPath(props: {
17
19
  */
18
20
  canCopy?: boolean;
19
21
  }) {
20
- const { data, withServer = true, canCopy = true } = props;
22
+ const { data, context, withServer = true, canCopy = true } = props;
21
23
  const { method, path, operation } = data;
22
24
 
23
25
  const server = getDefaultServerURL(data.servers);
@@ -41,6 +43,7 @@ export function OpenAPIPath(props: {
41
43
  className="openapi-path-title"
42
44
  data-deprecated={operation.deprecated}
43
45
  isDisabled={!canCopy}
46
+ context={getOpenAPIClientContext(context)}
44
47
  >
45
48
  {element}
46
49
  </OpenAPICopyButton>
@@ -1,8 +1,10 @@
1
1
  import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
  import { InteractiveSection } from './InteractiveSection';
3
3
  import { OpenAPIRootSchema } from './OpenAPISchemaServer';
4
- import type { OpenAPIClientContext } from './types';
5
- import { checkIsReference } from './utils';
4
+ import type { OpenAPIClientContext } from './context';
5
+ import { t } from './translate';
6
+ import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
7
+ import { checkIsReference, createStateKey } from './utils';
6
8
 
7
9
  /**
8
10
  * Display an interactive request body.
@@ -10,8 +12,9 @@ import { checkIsReference } from './utils';
10
12
  export function OpenAPIRequestBody(props: {
11
13
  requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject;
12
14
  context: OpenAPIClientContext;
15
+ data: OpenAPIOperationData | OpenAPIWebhookData;
13
16
  }) {
14
- const { requestBody, context } = props;
17
+ const { requestBody, context, data } = props;
15
18
 
16
19
  if (checkIsReference(requestBody)) {
17
20
  return null;
@@ -19,8 +22,10 @@ export function OpenAPIRequestBody(props: {
19
22
 
20
23
  return (
21
24
  <InteractiveSection
22
- header="Body"
25
+ header={t(context.translation, 'name' in data ? 'payload' : 'body')}
23
26
  className="openapi-requestbody"
27
+ stateKey={createStateKey('request-body-media-type', context.blockKey)}
28
+ selectIcon={context.icons.chevronDown}
24
29
  tabs={Object.entries(requestBody.content ?? {}).map(
25
30
  ([contentType, mediaTypeObject]) => {
26
31
  return {
@@ -1,7 +1,7 @@
1
1
  import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
  import { OpenAPIDisclosure } from './OpenAPIDisclosure';
3
3
  import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
4
- import type { OpenAPIClientContext } from './types';
4
+ import type { OpenAPIClientContext } from './context';
5
5
  import { parameterToProperty, resolveDescription } from './utils';
6
6
 
7
7
  /**
@@ -27,7 +27,7 @@ export function OpenAPIResponse(props: {
27
27
  return (
28
28
  <div className="openapi-response-body">
29
29
  {headers.length > 0 ? (
30
- <OpenAPIDisclosure context={context} label="Headers">
30
+ <OpenAPIDisclosure icon={context.icons.plus} label="Headers">
31
31
  <OpenAPISchemaProperties
32
32
  properties={headers.map(([name, header]) =>
33
33
  parameterToProperty({ name, ...header })
@@ -1,21 +1,19 @@
1
1
  import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
  import { Markdown } from './Markdown';
3
- import {
4
- OpenAPIEmptyExample,
5
- OpenAPIExample,
6
- getExampleFromReference,
7
- getExamplesFromMediaTypeObject,
8
- } from './OpenAPIExample';
9
- import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
10
- import { StaticSection } from './StaticSection';
11
- import type { OpenAPIContext, OpenAPIOperationData } from './types';
12
- import { checkIsReference, createStateKey, resolveDescription } from './utils';
3
+ import { OpenAPIEmptyExample, OpenAPIExample } from './OpenAPIExample';
4
+ import { OpenAPIMediaTypeContent } from './OpenAPIMediaType';
5
+ import { OpenAPIResponseExampleContent } from './OpenAPIResponseExampleContent';
6
+ import { type OpenAPIContext, getOpenAPIClientContext } from './context';
7
+ import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
8
+ import { getExampleFromReference, getExamples } from './util/example';
9
+ import { createStateKey, getStatusCodeDefaultLabel } from './utils';
10
+ import { checkIsReference, resolveDescription } from './utils';
13
11
 
14
12
  /**
15
13
  * Display an example of the response content.
16
14
  */
17
15
  export function OpenAPIResponseExample(props: {
18
- data: OpenAPIOperationData;
16
+ data: OpenAPIOperationData | OpenAPIWebhookData;
19
17
  context: OpenAPIContext;
20
18
  }) {
21
19
  const { data, context } = props;
@@ -45,36 +43,41 @@ export function OpenAPIResponseExample(props: {
45
43
 
46
44
  const tabs = responses.map(([key, responseObject]) => {
47
45
  const description = resolveDescription(responseObject);
46
+ const label = description ? (
47
+ <Markdown source={description} />
48
+ ) : (
49
+ getStatusCodeDefaultLabel(key, context)
50
+ );
48
51
 
49
52
  if (checkIsReference(responseObject)) {
50
53
  return {
51
54
  key: key,
52
- label: key,
55
+ label,
56
+ statusCode: key,
53
57
  body: (
54
58
  <OpenAPIExample
55
- example={getExampleFromReference(responseObject)}
59
+ example={getExampleFromReference(responseObject, context)}
56
60
  context={context}
57
61
  syntax="json"
58
62
  />
59
63
  ),
60
- footer: description ? <Markdown source={description} /> : undefined,
61
64
  };
62
65
  }
63
66
 
64
67
  if (!responseObject.content || Object.keys(responseObject.content).length === 0) {
65
68
  return {
66
69
  key: key,
67
- label: key,
68
- body: <OpenAPIEmptyExample />,
69
- footer: description ? <Markdown source={description} /> : undefined,
70
+ label,
71
+ statusCode: key,
72
+ body: <OpenAPIEmptyExample context={context} />,
70
73
  };
71
74
  }
72
75
 
73
76
  return {
74
77
  key: key,
75
- label: key,
78
+ label,
79
+ statusCode: key,
76
80
  body: <OpenAPIResponse context={context} content={responseObject.content} />,
77
- footer: description ? <Markdown source={description} /> : undefined,
78
81
  };
79
82
  });
80
83
 
@@ -83,11 +86,11 @@ export function OpenAPIResponseExample(props: {
83
86
  }
84
87
 
85
88
  return (
86
- <OpenAPITabs stateKey={createStateKey('response-example')} items={tabs}>
87
- <StaticSection header={<OpenAPITabsList />} className="openapi-panel">
88
- <OpenAPITabsPanels />
89
- </StaticSection>
90
- </OpenAPITabs>
89
+ <OpenAPIResponseExampleContent
90
+ selectIcon={context.icons.chevronDown}
91
+ blockKey={context.blockKey}
92
+ items={tabs}
93
+ />
91
94
  );
92
95
  }
93
96
 
@@ -106,98 +109,26 @@ function OpenAPIResponse(props: {
106
109
  throw new Error('One media type is required');
107
110
  }
108
111
 
109
- if (entries.length === 1) {
110
- const [mediaType, mediaTypeObject] = firstEntry;
111
- return (
112
- <OpenAPIResponseMediaType
113
- context={context}
114
- mediaType={mediaType}
115
- mediaTypeObject={mediaTypeObject}
116
- />
117
- );
118
- }
119
-
120
112
  const tabs = entries.map((entry) => {
121
113
  const [mediaType, mediaTypeObject] = entry;
122
114
  return {
123
115
  key: mediaType,
124
116
  label: mediaType,
125
- body: (
126
- <OpenAPIResponseMediaType
127
- context={context}
128
- mediaType={mediaType}
129
- mediaTypeObject={mediaTypeObject}
130
- />
131
- ),
132
- };
133
- });
134
-
135
- return (
136
- <OpenAPITabs stateKey={createStateKey('response-media-types')} items={tabs}>
137
- <StaticSection header={<OpenAPITabsList />} className="openapi-response-media-types">
138
- <OpenAPITabsPanels />
139
- </StaticSection>
140
- </OpenAPITabs>
141
- );
142
- }
143
-
144
- function OpenAPIResponseMediaType(props: {
145
- mediaTypeObject: OpenAPIV3.MediaTypeObject;
146
- mediaType: string;
147
- context: OpenAPIContext;
148
- }) {
149
- const { mediaTypeObject, mediaType } = props;
150
- const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
151
- const syntax = getSyntaxFromMediaType(mediaType);
152
- const firstExample = examples[0];
153
-
154
- if (!firstExample) {
155
- return <OpenAPIEmptyExample />;
156
- }
157
-
158
- if (examples.length === 1) {
159
- return (
160
- <OpenAPIExample
161
- example={firstExample.example}
162
- context={props.context}
163
- syntax={syntax}
164
- />
165
- );
166
- }
167
-
168
- const tabs = examples.map((example) => {
169
- return {
170
- key: example.key,
171
- label: example.example.summary || example.key,
172
- body: (
173
- <OpenAPIExample example={example.example} context={props.context} syntax={syntax} />
174
- ),
117
+ body: <></>,
118
+ examples: getExamples({
119
+ mediaTypeObject,
120
+ mediaType,
121
+ context,
122
+ }),
175
123
  };
176
124
  });
177
125
 
178
126
  return (
179
- <OpenAPITabs stateKey={createStateKey('response-media-type-examples')} items={tabs}>
180
- <StaticSection
181
- header={<OpenAPITabsList />}
182
- className="openapi-response-media-type-examples"
183
- >
184
- <OpenAPITabsPanels />
185
- </StaticSection>
186
- </OpenAPITabs>
127
+ <OpenAPIMediaTypeContent
128
+ selectIcon={context.icons.chevronDown}
129
+ stateKey={createStateKey('response-media-types', context.blockKey)}
130
+ items={tabs}
131
+ context={getOpenAPIClientContext(context)}
132
+ />
187
133
  );
188
134
  }
189
-
190
- /**
191
- * Get the syntax from a media type.
192
- */
193
- function getSyntaxFromMediaType(mediaType: string): string {
194
- if (mediaType.includes('json')) {
195
- return 'json';
196
- }
197
-
198
- if (mediaType === 'application/xml') {
199
- return 'xml';
200
- }
201
-
202
- return 'text';
203
- }
@@ -0,0 +1,123 @@
1
+ 'use client';
2
+
3
+ import clsx from 'clsx';
4
+ import type { Key } from 'react-aria';
5
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
6
+ import { StaticSection } from './StaticSection';
7
+ import { createStateKey, getStatusCodeClassName } from './utils';
8
+
9
+ type OpenAPIResponseExampleItem = OpenAPISelectItem & {
10
+ statusCode: string;
11
+ body: React.ReactNode;
12
+ };
13
+
14
+ /**
15
+ * Get the state of the response examples select.
16
+ */
17
+ export function useResponseExamplesState(
18
+ blockKey: string | undefined,
19
+ initialKey: Key = 'default'
20
+ ) {
21
+ return useSelectState(getResponseExampleStateKey(blockKey), initialKey);
22
+ }
23
+
24
+ export function OpenAPIResponseExampleContent(props: {
25
+ items: OpenAPIResponseExampleItem[];
26
+ blockKey?: string;
27
+ selectIcon?: React.ReactNode;
28
+ }) {
29
+ const { blockKey, items, selectIcon } = props;
30
+
31
+ return (
32
+ <StaticSection
33
+ header={
34
+ <OpenAPIResponseExampleHeader
35
+ selectIcon={selectIcon}
36
+ blockKey={blockKey}
37
+ items={items}
38
+ />
39
+ }
40
+ className="openapi-response-examples"
41
+ >
42
+ <OpenAPIResponseExampleBody blockKey={blockKey} items={items} />
43
+ </StaticSection>
44
+ );
45
+ }
46
+
47
+ function OpenAPIResponseExampleHeader(props: {
48
+ items: OpenAPIResponseExampleItem[];
49
+ blockKey?: string;
50
+ selectIcon?: React.ReactNode;
51
+ }) {
52
+ const { items, blockKey, selectIcon } = props;
53
+
54
+ if (items.length === 1) {
55
+ const item = items[0];
56
+
57
+ if (!item) {
58
+ return null;
59
+ }
60
+
61
+ return (
62
+ <span className="openapi-response-examples-statuscode-title">
63
+ <span
64
+ className={clsx(
65
+ 'openapi-statuscode',
66
+ `openapi-statuscode-${getStatusCodeClassName(item.statusCode)}`,
67
+ 'openapi-response-examples-statuscode'
68
+ )}
69
+ >
70
+ {item.statusCode}
71
+ </span>
72
+ <span>{item.label}</span>
73
+ </span>
74
+ );
75
+ }
76
+
77
+ return (
78
+ <OpenAPISelect
79
+ items={items}
80
+ icon={selectIcon}
81
+ stateKey={getResponseExampleStateKey(blockKey)}
82
+ placement="bottom start"
83
+ >
84
+ {items.map((item) => (
85
+ <OpenAPISelectItem key={item.key} id={item.key} value={item}>
86
+ <span
87
+ className={clsx(
88
+ 'openapi-statuscode',
89
+ `openapi-statuscode-${getStatusCodeClassName(item.statusCode)}`,
90
+ 'openapi-response-examples-statuscode'
91
+ )}
92
+ >
93
+ {item.statusCode}
94
+ </span>
95
+ <span>{item.label}</span>
96
+ </OpenAPISelectItem>
97
+ ))}
98
+ </OpenAPISelect>
99
+ );
100
+ }
101
+
102
+ function OpenAPIResponseExampleBody(props: {
103
+ items: OpenAPIResponseExampleItem[];
104
+ blockKey?: string;
105
+ }) {
106
+ const { blockKey, items } = props;
107
+ const state = useResponseExamplesState(blockKey, items[0]?.key);
108
+
109
+ const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
110
+
111
+ if (!selectedItem) {
112
+ return null;
113
+ }
114
+
115
+ return <div className="openapi-response-examples-panel">{selectedItem.body}</div>;
116
+ }
117
+
118
+ /**
119
+ * Return the state key for the response examples.
120
+ */
121
+ function getResponseExampleStateKey(blockKey: string | undefined) {
122
+ return createStateKey('openapi-responses', blockKey);
123
+ }
@@ -1,9 +1,15 @@
1
+ 'use client';
2
+
1
3
  import type { OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
4
+ import clsx from 'clsx';
2
5
  import { Markdown } from './Markdown';
3
6
  import { OpenAPIDisclosureGroup } from './OpenAPIDisclosureGroup';
4
7
  import { OpenAPIResponse } from './OpenAPIResponse';
8
+ import { useResponseExamplesState } from './OpenAPIResponseExampleContent';
5
9
  import { StaticSection } from './StaticSection';
6
- import type { OpenAPIClientContext } from './types';
10
+ import type { OpenAPIClientContext } from './context';
11
+ import { t } from './translate';
12
+ import { createStateKey, getStatusCodeClassName, getStatusCodeDefaultLabel } from './utils';
7
13
 
8
14
  /**
9
15
  * Display an interactive response body.
@@ -14,70 +20,86 @@ export function OpenAPIResponses(props: {
14
20
  }) {
15
21
  const { responses, context } = props;
16
22
 
17
- return (
18
- <StaticSection header="Responses" className="openapi-responses">
19
- <OpenAPIDisclosureGroup
20
- allowsMultipleExpanded
21
- icon={context.icons.chevronRight}
22
- groups={Object.entries(responses).map(
23
- ([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => {
24
- const tabs = (() => {
25
- // If there is no content, but there are headers, we need to show the headers
26
- if (
27
- (!response.content || !Object.keys(response.content).length) &&
28
- response.headers &&
29
- Object.keys(response.headers).length
30
- ) {
31
- return [
32
- {
33
- id: 'default',
34
- body: (
35
- <OpenAPIResponse
36
- response={response}
37
- mediaType={{}}
38
- context={context}
39
- />
40
- ),
41
- },
42
- ];
43
- }
23
+ const groups = Object.entries(responses).map(
24
+ ([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => {
25
+ const tabs = (() => {
26
+ // If there is no content, but there are headers, we need to show the headers
27
+ if (
28
+ (!response.content || !Object.keys(response.content).length) &&
29
+ response.headers &&
30
+ Object.keys(response.headers).length
31
+ ) {
32
+ return [
33
+ {
34
+ key: 'default',
35
+ label: '',
36
+ body: (
37
+ <OpenAPIResponse
38
+ response={response}
39
+ mediaType={{}}
40
+ context={context}
41
+ />
42
+ ),
43
+ },
44
+ ];
45
+ }
44
46
 
45
- return Object.entries(response.content ?? {}).map(
46
- ([contentType, mediaType]) => ({
47
- id: contentType,
48
- label: contentType,
49
- body: (
50
- <OpenAPIResponse
51
- response={response}
52
- mediaType={mediaType}
53
- context={context}
54
- />
55
- ),
56
- })
57
- );
58
- })();
47
+ return Object.entries(response.content ?? {}).map(([contentType, mediaType]) => ({
48
+ key: contentType,
49
+ label: contentType,
50
+ body: (
51
+ <OpenAPIResponse
52
+ response={response}
53
+ mediaType={mediaType}
54
+ context={context}
55
+ />
56
+ ),
57
+ }));
58
+ })();
59
59
 
60
- const description = response.description;
60
+ const description = response.description;
61
61
 
62
- return {
63
- id: statusCode,
64
- label: (
65
- <div className="openapi-response-tab-content">
66
- <span className="openapi-response-statuscode">
67
- {statusCode}
68
- </span>
69
- {description ? (
70
- <Markdown
71
- source={description}
72
- className="openapi-response-description"
73
- />
74
- ) : null}
75
- </div>
76
- ),
77
- tabs,
78
- };
79
- }
80
- )}
62
+ return {
63
+ key: statusCode,
64
+ label: (
65
+ <div className="openapi-response-tab-content">
66
+ <span
67
+ className={clsx(
68
+ 'openapi-statuscode',
69
+ `openapi-statuscode-${getStatusCodeClassName(statusCode)}`
70
+ )}
71
+ >
72
+ {statusCode}
73
+ </span>
74
+ {description ? (
75
+ <Markdown
76
+ source={description}
77
+ className="openapi-response-description"
78
+ />
79
+ ) : (
80
+ getStatusCodeDefaultLabel(statusCode, context)
81
+ )}
82
+ </div>
83
+ ),
84
+ tabs,
85
+ };
86
+ }
87
+ );
88
+
89
+ const state = useResponseExamplesState(context.blockKey, groups[0]?.key);
90
+
91
+ return (
92
+ <StaticSection header={t(context.translation, 'responses')} className="openapi-responses">
93
+ <OpenAPIDisclosureGroup
94
+ icon={context.icons.chevronRight}
95
+ expandedKeys={state.key ? new Set([state.key]) : new Set()}
96
+ onExpandedChange={(keys) => {
97
+ const key = keys.values().next().value ?? null;
98
+ state.setKey(key);
99
+ }}
100
+ groups={groups}
101
+ selectIcon={context.icons.chevronDown}
102
+ selectStateKey={createStateKey('response-media-types', context.blockKey)}
81
103
  />
82
104
  </StaticSection>
83
105
  );