@gitbook/react-openapi 1.1.10 → 1.2.0

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 +14 -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 +40 -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 +83 -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
package/dist/types.d.ts CHANGED
@@ -1,54 +1,4 @@
1
1
  import type { OpenAPICustomOperationProperties, OpenAPICustomSpecProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
2
- export interface OpenAPIClientContext {
3
- /**
4
- * Icons used in the block.
5
- */
6
- icons: {
7
- chevronDown: React.ReactNode;
8
- chevronRight: React.ReactNode;
9
- plus: React.ReactNode;
10
- };
11
- /**
12
- * Force all sections to be opened by default.
13
- * @default false
14
- */
15
- defaultInteractiveOpened?: boolean;
16
- /**
17
- * The key of the block
18
- */
19
- blockKey?: string;
20
- /**
21
- * Optional id attached to the heading and used as an anchor.
22
- */
23
- id?: string;
24
- }
25
- export interface OpenAPIContext extends OpenAPIClientContext {
26
- /**
27
- * Render a code block.
28
- */
29
- renderCodeBlock: (props: {
30
- code: string;
31
- syntax: string;
32
- }) => React.ReactNode;
33
- /**
34
- * Render the heading of the operation.
35
- */
36
- renderHeading: (props: {
37
- deprecated?: boolean;
38
- title: string;
39
- stability?: string;
40
- }) => React.ReactNode;
41
- /**
42
- * Render the document of the operation.
43
- */
44
- renderDocument: (props: {
45
- document: object;
46
- }) => React.ReactNode;
47
- /**
48
- * Specification URL.
49
- */
50
- specUrl: string;
51
- }
52
2
  export type OpenAPISecurityWithRequired = OpenAPIV3.SecuritySchemeObject & {
53
3
  required?: boolean;
54
4
  };
@@ -62,3 +12,11 @@ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
62
12
  /** Securities that should be used for this operation */
63
13
  securities: [string, OpenAPISecurityWithRequired][];
64
14
  }
15
+ export interface OpenAPIWebhookData extends OpenAPICustomSpecProperties {
16
+ name: string;
17
+ method: string;
18
+ /** Servers to be used for this operation */
19
+ servers: OpenAPIV3.ServerObject[];
20
+ /** Spec of the webhook */
21
+ operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
22
+ }
@@ -0,0 +1,35 @@
1
+ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
+ import type { OpenAPIContext } from '../context';
3
+ /**
4
+ * Generate an example from a reference object.
5
+ */
6
+ export declare function getExampleFromReference(ref: OpenAPIV3.ReferenceObject, context: OpenAPIContext): OpenAPIV3.ExampleObject;
7
+ /**
8
+ * Get examples from a media type object.
9
+ */
10
+ export declare function getExamplesFromMediaTypeObject(args: {
11
+ mediaType: string;
12
+ mediaTypeObject: OpenAPIV3.MediaTypeObject;
13
+ context: OpenAPIContext;
14
+ }): {
15
+ key: string;
16
+ example: OpenAPIV3.ExampleObject;
17
+ }[];
18
+ /**
19
+ * Get example from a schema object.
20
+ */
21
+ export declare function getExampleFromSchema(args: {
22
+ schema: OpenAPIV3.SchemaObject;
23
+ }): OpenAPIV3.ExampleObject;
24
+ /**
25
+ * Get the examples from a media type object.
26
+ */
27
+ export declare function getExamples(props: {
28
+ mediaTypeObject: OpenAPIV3.MediaTypeObject;
29
+ mediaType: string;
30
+ context: OpenAPIContext;
31
+ }): {
32
+ key: string;
33
+ label: string;
34
+ body: import("react").JSX.Element;
35
+ }[];
@@ -0,0 +1,103 @@
1
+ import { OpenAPIExample } from '../OpenAPIExample';
2
+ import { generateSchemaExample } from '../generateSchemaExample';
3
+ import { tString } from '../translate';
4
+ import { checkIsReference } from '../utils';
5
+ /**
6
+ * Generate an example from a reference object.
7
+ */
8
+ export function getExampleFromReference(ref, context) {
9
+ return {
10
+ summary: tString(context.translation, 'unresolved_reference'),
11
+ value: { $ref: ref.$ref },
12
+ };
13
+ }
14
+ /**
15
+ * Get examples from a media type object.
16
+ */
17
+ export function getExamplesFromMediaTypeObject(args) {
18
+ var _a;
19
+ var _b, _c;
20
+ var mediaTypeObject = args.mediaTypeObject, mediaType = args.mediaType, context = args.context;
21
+ if (mediaTypeObject.examples) {
22
+ return Object.entries(mediaTypeObject.examples).map(function (_a) {
23
+ var key = _a[0], example = _a[1];
24
+ return {
25
+ key: key,
26
+ example: checkIsReference(example)
27
+ ? getExampleFromReference(example, context)
28
+ : example,
29
+ };
30
+ });
31
+ }
32
+ if (mediaTypeObject.example) {
33
+ return [{ key: 'default', example: { value: mediaTypeObject.example } }];
34
+ }
35
+ if (mediaTypeObject.schema) {
36
+ if (mediaType === 'application/xml') {
37
+ // @TODO normally we should use the name of the schema but we don't have it
38
+ // fix it when we got the reference name
39
+ var root = (_c = (_b = mediaTypeObject.schema.xml) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : 'object';
40
+ return [
41
+ {
42
+ key: 'default',
43
+ example: {
44
+ value: (_a = {},
45
+ _a[root] = generateSchemaExample(mediaTypeObject.schema, {
46
+ xml: mediaType === 'application/xml',
47
+ mode: 'read',
48
+ }),
49
+ _a),
50
+ },
51
+ },
52
+ ];
53
+ }
54
+ return [
55
+ {
56
+ key: 'default',
57
+ example: {
58
+ value: generateSchemaExample(mediaTypeObject.schema, {
59
+ mode: 'read',
60
+ }),
61
+ },
62
+ },
63
+ ];
64
+ }
65
+ return [];
66
+ }
67
+ /**
68
+ * Get example from a schema object.
69
+ */
70
+ export function getExampleFromSchema(args) {
71
+ var schema = args.schema;
72
+ if (schema.example) {
73
+ return { value: schema.example };
74
+ }
75
+ return { value: generateSchemaExample(schema, { mode: 'read' }) };
76
+ }
77
+ /**
78
+ * Get the examples from a media type object.
79
+ */
80
+ export function getExamples(props) {
81
+ var mediaTypeObject = props.mediaTypeObject, mediaType = props.mediaType, context = props.context;
82
+ var examples = getExamplesFromMediaTypeObject({ mediaTypeObject: mediaTypeObject, mediaType: mediaType, context: context });
83
+ var syntax = getSyntaxFromMediaType(mediaType);
84
+ return examples.map(function (example) {
85
+ return {
86
+ key: example.key,
87
+ label: example.example.summary || example.key,
88
+ body: (<OpenAPIExample example={example.example} context={props.context} syntax={syntax}/>),
89
+ };
90
+ });
91
+ }
92
+ /**
93
+ * Get the syntax from a media type.
94
+ */
95
+ function getSyntaxFromMediaType(mediaType) {
96
+ if (mediaType.includes('json')) {
97
+ return 'json';
98
+ }
99
+ if (mediaType === 'application/xml') {
100
+ return 'xml';
101
+ }
102
+ return 'text';
103
+ }
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
2
+ import type { OpenAPIUniversalContext } from './context';
2
3
  export declare function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject;
3
4
  export declare function createStateKey(key: string, scope?: string): string;
4
5
  /**
@@ -29,3 +30,20 @@ export declare function parameterToProperty(parameter: OpenAPIV3.ParameterObject
29
30
  schema: OpenAPIV3.SchemaObject;
30
31
  required: boolean | undefined;
31
32
  };
33
+ /**
34
+ * Get the class name for a status code.
35
+ * 1xx: informational
36
+ * 2xx: success
37
+ * 3xx: redirect
38
+ * 4xx, 5xx: error
39
+ */
40
+ export declare function getStatusCodeClassName(statusCode: number | string): string;
41
+ /**
42
+ * Get a default label for a status code.
43
+ * This is used when there is no label provided in the OpenAPI spec.
44
+ * 1xx: Information
45
+ * 2xx: Success
46
+ * 3xx: Redirect
47
+ * 4xx, 5xx: Error
48
+ */
49
+ export declare function getStatusCodeDefaultLabel(statusCode: number | string, context: OpenAPIUniversalContext): string;
package/dist/utils.js CHANGED
@@ -10,6 +10,7 @@ var __assign = (this && this.__assign) || function () {
10
10
  return __assign.apply(this, arguments);
11
11
  };
12
12
  import { stringifyOpenAPI } from './stringifyOpenAPI';
13
+ import { tString } from './translate';
13
14
  export function checkIsReference(input) {
14
15
  return typeof input === 'object' && !!input && '$ref' in input;
15
16
  }
@@ -127,3 +128,59 @@ function shouldDisplayExample(schema) {
127
128
  schema.example !== null &&
128
129
  Object.keys(schema.example).length > 0));
129
130
  }
131
+ /**
132
+ * Get the class name for a status code.
133
+ * 1xx: informational
134
+ * 2xx: success
135
+ * 3xx: redirect
136
+ * 4xx, 5xx: error
137
+ */
138
+ export function getStatusCodeClassName(statusCode) {
139
+ var category = getStatusCodeCategory(statusCode);
140
+ switch (category) {
141
+ case 1:
142
+ return 'informational';
143
+ case 2:
144
+ return 'success';
145
+ case 3:
146
+ return 'redirect';
147
+ case 4:
148
+ case 5:
149
+ return 'error';
150
+ default:
151
+ return 'unknown';
152
+ }
153
+ }
154
+ /**
155
+ * Get a default label for a status code.
156
+ * This is used when there is no label provided in the OpenAPI spec.
157
+ * 1xx: Information
158
+ * 2xx: Success
159
+ * 3xx: Redirect
160
+ * 4xx, 5xx: Error
161
+ */
162
+ export function getStatusCodeDefaultLabel(statusCode, context) {
163
+ var category = getStatusCodeCategory(statusCode);
164
+ switch (category) {
165
+ case 1:
166
+ return tString(context.translation, 'information');
167
+ case 2:
168
+ return tString(context.translation, 'success');
169
+ case 3:
170
+ return tString(context.translation, 'redirect');
171
+ case 4:
172
+ case 5:
173
+ return tString(context.translation, 'error');
174
+ default:
175
+ return '';
176
+ }
177
+ }
178
+ function getStatusCodeCategory(statusCode) {
179
+ var code = typeof statusCode === 'string' ? Number.parseInt(statusCode, 10) : statusCode;
180
+ if (Number.isNaN(code) || code < 100 || code >= 600) {
181
+ return 'unknown';
182
+ }
183
+ // Determine the category of the status code based on the first digit
184
+ var category = Math.floor(code / 100);
185
+ return category;
186
+ }
package/package.json CHANGED
@@ -8,12 +8,12 @@
8
8
  "default": "./dist/index.js"
9
9
  }
10
10
  },
11
- "version": "1.1.10",
11
+ "version": "1.2.0",
12
12
  "sideEffects": false,
13
13
  "dependencies": {
14
14
  "@gitbook/openapi-parser": "workspace:*",
15
- "@scalar/api-client-react": "^1.2.5",
16
- "@scalar/oas-utils": "^0.2.120",
15
+ "@scalar/api-client-react": "^1.2.19",
16
+ "@scalar/oas-utils": "^0.2.130",
17
17
  "clsx": "^2.1.1",
18
18
  "flatted": "^3.2.9",
19
19
  "json-xml-parse": "^1.3.0",
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import clsx from 'clsx';
4
- import { useRef, useState } from 'react';
4
+ import { useRef } from 'react';
5
5
  import { mergeProps, useButton, useDisclosure, useFocusRing } from 'react-aria';
6
6
  import { useDisclosureState } from 'react-stately';
7
- import { OpenAPISelect, OpenAPISelectItem } from './OpenAPISelect';
7
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
8
8
  import { Section, SectionBody, SectionHeader, SectionHeaderContent } from './StaticSection';
9
9
 
10
10
  interface InteractiveSectionTab {
@@ -35,6 +35,10 @@ export function InteractiveSection(props: {
35
35
  header?: React.ReactNode;
36
36
  /** Children to display within the container */
37
37
  overlay?: React.ReactNode;
38
+ /** State key to use with a store */
39
+ stateKey?: string;
40
+ /** Icon for the tabs select */
41
+ selectIcon?: React.ReactNode;
38
42
  }) {
39
43
  const {
40
44
  id,
@@ -46,12 +50,9 @@ export function InteractiveSection(props: {
46
50
  header,
47
51
  overlay,
48
52
  toggleIcon = '▶',
53
+ selectIcon,
54
+ stateKey = 'interactive-section',
49
55
  } = props;
50
-
51
- const [selectedTabKey, setSelectedTab] = useState(defaultTab);
52
- const selectedTab: InteractiveSectionTab | undefined =
53
- tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
54
-
55
56
  const state = useDisclosureState({
56
57
  defaultExpanded: defaultOpened,
57
58
  });
@@ -60,6 +61,10 @@ export function InteractiveSection(props: {
60
61
  const { buttonProps: triggerProps, panelProps } = useDisclosure({}, state, panelRef);
61
62
  const { buttonProps } = useButton(triggerProps, triggerRef);
62
63
  const { isFocusVisible, focusProps } = useFocusRing();
64
+ const store = useSelectState(stateKey, defaultTab);
65
+
66
+ const selectedTab: InteractiveSectionTab | undefined =
67
+ tabs.find((tab) => tab.key === store.key) ?? tabs[0];
63
68
 
64
69
  return (
65
70
  <Section
@@ -97,6 +102,7 @@ export function InteractiveSection(props: {
97
102
  ) : null}
98
103
  {header}
99
104
  </SectionHeaderContent>
105
+ {/* biome-ignore lint/a11y/useKeyWithClickEvents: we prevent default here */}
100
106
  <div
101
107
  className={clsx(
102
108
  'openapi-section-header-controls',
@@ -108,16 +114,12 @@ export function InteractiveSection(props: {
108
114
  >
109
115
  {tabs.length > 1 ? (
110
116
  <OpenAPISelect
111
- className={clsx(
112
- 'openapi-section-select',
113
- `${className}-tabs-select`
114
- )}
117
+ stateKey={stateKey}
115
118
  items={tabs}
116
- selectedKey={selectedTab?.key ?? ''}
117
- onSelectionChange={(key) => {
118
- setSelectedTab(String(key));
119
+ onSelectionChange={() => {
119
120
  state.expand();
120
121
  }}
122
+ icon={selectIcon}
121
123
  placement="bottom end"
122
124
  >
123
125
  {tabs.map((tab) => (
@@ -6,9 +6,10 @@ import {
6
6
  import { OpenAPICodeSampleBody } from './OpenAPICodeSampleSelector';
7
7
  import { ScalarApiButton } from './ScalarApiButton';
8
8
  import { type CodeSampleGenerator, codeSampleGenerators } from './code-samples';
9
+ import { type OpenAPIContext, getOpenAPIClientContext } from './context';
9
10
  import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample';
10
11
  import { stringifyOpenAPI } from './stringifyOpenAPI';
11
- import type { OpenAPIContext, OpenAPIOperationData } from './types';
12
+ import type { OpenAPIOperationData } from './types';
12
13
  import { getDefaultServerURL } from './util/server';
13
14
  import { checkIsReference } from './utils';
14
15
 
@@ -22,7 +23,7 @@ export function OpenAPICodeSample(props: {
22
23
  data: OpenAPIOperationData;
23
24
  context: OpenAPIContext;
24
25
  }) {
25
- const { data } = props;
26
+ const { data, context } = props;
26
27
 
27
28
  // If code samples are disabled at operation level, we don't display the code samples.
28
29
  if (data.operation['x-codeSamples'] === false) {
@@ -43,7 +44,14 @@ export function OpenAPICodeSample(props: {
43
44
  return null;
44
45
  }
45
46
 
46
- return <OpenAPICodeSampleBody data={data} items={samples} />;
47
+ return (
48
+ <OpenAPICodeSampleBody
49
+ context={getOpenAPIClientContext(context)}
50
+ data={data}
51
+ items={samples}
52
+ selectIcon={context.icons.chevronDown}
53
+ />
54
+ );
47
55
  }
48
56
 
49
57
  /**
@@ -146,6 +154,7 @@ function generateCodeSamples(props: {
146
154
  method={data.method}
147
155
  path={data.path}
148
156
  renderers={renderers}
157
+ blockKey={context.blockKey}
149
158
  />
150
159
  ),
151
160
  footer: (
@@ -206,11 +215,20 @@ function OpenAPICodeSampleFooter(props: {
206
215
  method={data.method}
207
216
  path={data.path}
208
217
  renderers={renderers}
218
+ selectIcon={context.icons.chevronDown}
219
+ blockKey={context.blockKey}
209
220
  />
210
221
  ) : (
211
222
  <span />
212
223
  )}
213
- {!hideTryItPanel && <ScalarApiButton method={method} path={path} specUrl={specUrl} />}
224
+ {!hideTryItPanel && (
225
+ <ScalarApiButton
226
+ context={getOpenAPIClientContext(context)}
227
+ method={method}
228
+ path={path}
229
+ specUrl={specUrl}
230
+ />
231
+ )}
214
232
  </div>
215
233
  );
216
234
  }
@@ -1,70 +1,43 @@
1
1
  'use client';
2
2
  import clsx from 'clsx';
3
- import { useCallback } from 'react';
4
- import { useStore } from 'zustand';
5
3
  import type { MediaTypeRenderer } from './OpenAPICodeSample';
6
- import { OpenAPISelect, OpenAPISelectItem } from './OpenAPISelect';
7
- import { getOrCreateStoreByKey } from './getOrCreateStoreByKey';
8
-
9
- type MediaTypeState = {
10
- mediaType: string;
11
- setMediaType: (mediaType: string) => void;
12
- };
13
-
14
- function useMediaTypeState(
15
- data: { method: string; path: string },
16
- defaultKey: string
17
- ): MediaTypeState {
18
- const { method, path } = data;
19
- const store = useStore(getOrCreateStoreByKey(`media-type-${method}-${path}`, defaultKey));
20
- if (typeof store.key !== 'string') {
21
- throw new Error('Media type key is not a string');
22
- }
23
- return {
24
- mediaType: store.key,
25
- setMediaType: useCallback((index: string) => store.setKey(index), [store.setKey]),
26
- };
27
- }
28
-
29
- function useMediaTypeSampleIndexState(data: { method: string; path: string }, mediaType: string) {
30
- const { method, path } = data;
31
- const store = useStore(
32
- getOrCreateStoreByKey(`media-type-sample-${mediaType}-${method}-${path}`, 0)
33
- );
34
- if (typeof store.key !== 'number') {
35
- throw new Error('Example key is not a number');
36
- }
37
- return {
38
- index: store.key,
39
- setIndex: useCallback((index: number) => store.setKey(index), [store.setKey]),
40
- };
41
- }
4
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
5
+ import { createStateKey } from './utils';
42
6
 
43
7
  export function OpenAPIMediaTypeExamplesSelector(props: {
44
8
  method: string;
45
9
  path: string;
46
10
  renderers: MediaTypeRenderer[];
11
+ selectIcon?: React.ReactNode;
12
+ blockKey?: string;
47
13
  }) {
48
- const { method, path, renderers } = props;
14
+ const { method, path, renderers, selectIcon, blockKey } = props;
49
15
  if (!renderers[0]) {
50
16
  throw new Error('No renderers provided');
51
17
  }
52
- const state = useMediaTypeState({ method, path }, renderers[0].mediaType);
53
- const selected = renderers.find((r) => r.mediaType === state.mediaType) || renderers[0];
18
+ const stateKey = createStateKey('request-body-media-type', blockKey);
19
+ const state = useSelectState(stateKey, renderers[0].mediaType);
20
+ const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
54
21
 
55
22
  return (
56
23
  <div className="openapi-codesample-selectors">
57
- <MediaTypeSelector state={state} renderers={renderers} />
58
- <ExamplesSelector method={method} path={path} renderer={selected} />
24
+ <MediaTypeSelector selectIcon={selectIcon} stateKey={stateKey} renderers={renderers} />
25
+ <ExamplesSelector
26
+ selectIcon={selectIcon}
27
+ method={method}
28
+ path={path}
29
+ renderer={selected}
30
+ />
59
31
  </div>
60
32
  );
61
33
  }
62
34
 
63
35
  function MediaTypeSelector(props: {
64
- state: MediaTypeState;
36
+ stateKey: string;
65
37
  renderers: MediaTypeRenderer[];
38
+ selectIcon?: React.ReactNode;
66
39
  }) {
67
- const { renderers, state } = props;
40
+ const { renderers, stateKey, selectIcon } = props;
68
41
 
69
42
  if (renderers.length < 2) {
70
43
  return null;
@@ -78,12 +51,12 @@ function MediaTypeSelector(props: {
78
51
  return (
79
52
  <OpenAPISelect
80
53
  className={clsx('openapi-select')}
81
- selectedKey={state.mediaType}
82
54
  items={renderers.map((renderer) => ({
83
55
  key: renderer.mediaType,
84
56
  label: renderer.mediaType,
85
57
  }))}
86
- onSelectionChange={(e) => state.setMediaType(String(e))}
58
+ icon={selectIcon}
59
+ stateKey={stateKey}
87
60
  placement="bottom start"
88
61
  >
89
62
  {items.map((item) => (
@@ -99,9 +72,9 @@ function ExamplesSelector(props: {
99
72
  method: string;
100
73
  path: string;
101
74
  renderer: MediaTypeRenderer;
75
+ selectIcon?: React.ReactNode;
102
76
  }) {
103
- const { method, path, renderer } = props;
104
- const state = useMediaTypeSampleIndexState({ method, path }, renderer.mediaType);
77
+ const { method, path, renderer, selectIcon } = props;
105
78
  if (renderer.examples.length < 2) {
106
79
  return null;
107
80
  }
@@ -114,8 +87,8 @@ function ExamplesSelector(props: {
114
87
  return (
115
88
  <OpenAPISelect
116
89
  items={items}
117
- selectedKey={state.index}
118
- onSelectionChange={(e) => state.setIndex(Number(e))}
90
+ icon={selectIcon}
91
+ stateKey={`media-type-sample-${renderer.mediaType}-${method}-${path}`}
119
92
  placement="bottom start"
120
93
  >
121
94
  {items.map((item) => (
@@ -131,14 +104,18 @@ export function OpenAPIMediaTypeExamplesBody(props: {
131
104
  method: string;
132
105
  path: string;
133
106
  renderers: MediaTypeRenderer[];
107
+ blockKey?: string;
134
108
  }) {
135
- const { renderers, method, path } = props;
109
+ const { renderers, method, path, blockKey } = props;
136
110
  if (!renderers[0]) {
137
111
  throw new Error('No renderers provided');
138
112
  }
139
- const mediaTypeState = useMediaTypeState({ method, path }, renderers[0].mediaType);
140
- const selected =
141
- renderers.find((r) => r.mediaType === mediaTypeState.mediaType) ?? renderers[0];
113
+
114
+ const mediaTypeState = useSelectState(
115
+ createStateKey('request-body-media-type', blockKey),
116
+ renderers[0].mediaType
117
+ );
118
+ const selected = renderers.find((r) => r.mediaType === mediaTypeState.key) ?? renderers[0];
142
119
  if (selected.examples.length === 0) {
143
120
  return selected.element;
144
121
  }
@@ -147,10 +124,13 @@ export function OpenAPIMediaTypeExamplesBody(props: {
147
124
 
148
125
  function ExamplesBody(props: { method: string; path: string; renderer: MediaTypeRenderer }) {
149
126
  const { method, path, renderer } = props;
150
- const exampleState = useMediaTypeSampleIndexState({ method, path }, renderer.mediaType);
151
- const example = renderer.examples[exampleState.index] ?? renderer.examples[0];
127
+ const exampleState = useSelectState(
128
+ `media-type-sample-${renderer.mediaType}-${method}-${path}`,
129
+ renderer.mediaType
130
+ );
131
+ const example = renderer.examples[Number(exampleState.key)] ?? renderer.examples[0];
152
132
  if (!example) {
153
- throw new Error(`No example found for index ${exampleState.index}`);
133
+ throw new Error(`No example found for key ${exampleState.key}`);
154
134
  }
155
135
  return example.element;
156
136
  }