@gitbook/react-openapi 0.7.1 → 1.0.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 (115) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/InteractiveSection.d.ts +4 -6
  3. package/dist/InteractiveSection.jsx +96 -0
  4. package/dist/Markdown.d.ts +1 -2
  5. package/dist/Markdown.jsx +5 -0
  6. package/dist/OpenAPICodeSample.d.ts +2 -4
  7. package/dist/OpenAPICodeSample.jsx +143 -0
  8. package/dist/OpenAPIDisclosure.d.ts +12 -0
  9. package/dist/OpenAPIDisclosure.jsx +32 -0
  10. package/dist/OpenAPIDisclosureGroup.d.ts +19 -0
  11. package/dist/OpenAPIDisclosureGroup.jsx +81 -0
  12. package/dist/OpenAPIOperation.d.ts +2 -4
  13. package/dist/OpenAPIOperation.jsx +51 -0
  14. package/dist/OpenAPIOperationContext.d.ts +16 -0
  15. package/dist/OpenAPIOperationContext.jsx +26 -0
  16. package/dist/OpenAPIPath.d.ts +8 -0
  17. package/dist/OpenAPIPath.jsx +54 -0
  18. package/dist/OpenAPIRequestBody.d.ts +3 -4
  19. package/dist/OpenAPIRequestBody.jsx +19 -0
  20. package/dist/OpenAPIResponse.d.ts +4 -4
  21. package/dist/OpenAPIResponse.jsx +49 -0
  22. package/dist/OpenAPIResponseExample.d.ts +2 -4
  23. package/dist/OpenAPIResponseExample.jsx +108 -0
  24. package/dist/OpenAPIResponses.d.ts +3 -4
  25. package/dist/OpenAPIResponses.jsx +36 -0
  26. package/dist/OpenAPISchema.d.ts +11 -8
  27. package/dist/OpenAPISchema.jsx +295 -0
  28. package/dist/OpenAPISchemaName.d.ts +12 -0
  29. package/dist/OpenAPISchemaName.jsx +15 -0
  30. package/dist/OpenAPISecurities.d.ts +2 -4
  31. package/dist/OpenAPISecurities.jsx +55 -0
  32. package/dist/OpenAPIServerURL.d.ts +2 -3
  33. package/dist/OpenAPIServerURL.jsx +67 -0
  34. package/dist/OpenAPIServerURLVariable.d.ts +2 -3
  35. package/dist/OpenAPIServerURLVariable.jsx +8 -0
  36. package/dist/OpenAPISpec.d.ts +3 -4
  37. package/dist/OpenAPISpec.jsx +91 -0
  38. package/dist/OpenAPITabs.d.ts +25 -0
  39. package/dist/OpenAPITabs.jsx +67 -0
  40. package/dist/ScalarApiButton.d.ts +3 -3
  41. package/dist/ScalarApiButton.jsx +51 -0
  42. package/dist/code-samples.d.ts +4 -0
  43. package/dist/code-samples.js +103 -38
  44. package/dist/fetchOpenAPIOperation.d.ts +9 -54
  45. package/dist/fetchOpenAPIOperation.js +178 -107
  46. package/dist/generateSchemaExample.d.ts +2 -2
  47. package/dist/generateSchemaExample.js +28 -100
  48. package/dist/index.d.ts +3 -2
  49. package/dist/index.js +2 -1
  50. package/dist/resolveOpenAPIOperation.d.ts +11 -0
  51. package/dist/resolveOpenAPIOperation.js +194 -0
  52. package/dist/stringifyOpenAPI.d.ts +4 -0
  53. package/dist/stringifyOpenAPI.js +6 -0
  54. package/dist/tsconfig.build.tsbuildinfo +1 -0
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/dist/types.d.ts +11 -12
  57. package/dist/utils.d.ts +6 -1
  58. package/dist/utils.js +15 -2
  59. package/package.json +11 -10
  60. package/src/InteractiveSection.tsx +119 -78
  61. package/src/Markdown.tsx +2 -3
  62. package/src/OpenAPICodeSample.tsx +35 -21
  63. package/src/OpenAPIDisclosure.tsx +50 -0
  64. package/src/OpenAPIDisclosureGroup.tsx +136 -0
  65. package/src/OpenAPIOperation.tsx +36 -42
  66. package/src/OpenAPIOperationContext.tsx +45 -0
  67. package/src/OpenAPIPath.tsx +65 -0
  68. package/src/OpenAPIRequestBody.tsx +3 -14
  69. package/src/OpenAPIResponse.tsx +39 -43
  70. package/src/OpenAPIResponseExample.tsx +89 -31
  71. package/src/OpenAPIResponses.tsx +51 -15
  72. package/src/OpenAPISchema.test.ts +1 -1
  73. package/src/OpenAPISchema.tsx +124 -92
  74. package/src/OpenAPISchemaName.tsx +27 -0
  75. package/src/OpenAPISecurities.tsx +45 -24
  76. package/src/OpenAPIServerURL.tsx +17 -10
  77. package/src/OpenAPIServerURLVariable.tsx +2 -4
  78. package/src/OpenAPISpec.tsx +56 -53
  79. package/src/OpenAPITabs.tsx +113 -0
  80. package/src/ScalarApiButton.tsx +84 -7
  81. package/src/code-samples.test.ts +51 -0
  82. package/src/code-samples.ts +95 -31
  83. package/src/generateSchemaExample.ts +25 -151
  84. package/src/index.ts +3 -2
  85. package/src/resolveOpenAPIOperation.test.ts +177 -0
  86. package/src/resolveOpenAPIOperation.ts +163 -0
  87. package/src/stringifyOpenAPI.ts +6 -0
  88. package/src/types.ts +17 -10
  89. package/src/utils.ts +17 -2
  90. package/dist/InteractiveSection.js +0 -47
  91. package/dist/Markdown.js +0 -6
  92. package/dist/OpenAPICodeSample.js +0 -110
  93. package/dist/OpenAPIOperation.js +0 -38
  94. package/dist/OpenAPIRequestBody.js +0 -18
  95. package/dist/OpenAPIResponse.js +0 -32
  96. package/dist/OpenAPIResponseExample.js +0 -54
  97. package/dist/OpenAPIResponses.js +0 -18
  98. package/dist/OpenAPISchema.js +0 -235
  99. package/dist/OpenAPISchema.test.d.ts +0 -1
  100. package/dist/OpenAPISchema.test.js +0 -91
  101. package/dist/OpenAPISecurities.js +0 -42
  102. package/dist/OpenAPIServerURL.js +0 -51
  103. package/dist/OpenAPIServerURLVariable.js +0 -10
  104. package/dist/OpenAPISpec.js +0 -70
  105. package/dist/ScalarApiButton.js +0 -14
  106. package/dist/fetchOpenAPIOperation.test.d.ts +0 -1
  107. package/dist/fetchOpenAPIOperation.test.js +0 -152
  108. package/dist/resolveOpenAPIPath.d.ts +0 -7
  109. package/dist/resolveOpenAPIPath.js +0 -112
  110. package/dist/resolveOpenAPIPath.test.d.ts +0 -1
  111. package/dist/resolveOpenAPIPath.test.js +0 -39
  112. package/src/fetchOpenAPIOperation.test.ts +0 -185
  113. package/src/fetchOpenAPIOperation.ts +0 -230
  114. package/src/resolveOpenAPIPath.test.ts +0 -60
  115. package/src/resolveOpenAPIPath.ts +0 -145
package/dist/types.d.ts CHANGED
@@ -1,6 +1,4 @@
1
- export type IconComponent = React.ComponentType<{
2
- className?: string;
3
- }>;
1
+ import type { OpenAPICustomOperationProperties, OpenAPICustomSpecProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
4
2
  export interface OpenAPIContextProps extends OpenAPIClientContext {
5
3
  CodeBlock: React.ComponentType<{
6
4
  code: string;
@@ -13,6 +11,7 @@ export interface OpenAPIClientContext {
13
11
  icons: {
14
12
  chevronDown: React.ReactNode;
15
13
  chevronRight: React.ReactNode;
14
+ plus: React.ReactNode;
16
15
  };
17
16
  /**
18
17
  * Force all sections to be opened by default.
@@ -26,13 +25,13 @@ export interface OpenAPIClientContext {
26
25
  /** Optional id attached to the OpenAPI Operation heading and used as an anchor */
27
26
  id?: string;
28
27
  }
29
- export interface OpenAPIFetcher {
30
- /**
31
- * Fetch an OpenAPI file by its URL. It should return a fully parsed OpenAPI v3 document.
32
- */
33
- fetch: (url: string) => Promise<any>;
34
- /**
35
- * Parse markdown to the react element to render.
36
- */
37
- parseMarkdown?: (input: string) => Promise<string>;
28
+ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
29
+ path: string;
30
+ method: string;
31
+ /** Servers to be used for this operation */
32
+ servers: OpenAPIV3.ServerObject[];
33
+ /** Spec of the operation */
34
+ operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
35
+ /** Securities that should be used for this operation */
36
+ securities: [string, OpenAPIV3.SecuritySchemeObject][];
38
37
  }
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,8 @@
1
- import { OpenAPIV3 } from 'openapi-types';
1
+ import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
  export declare function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T;
3
+ export declare function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject;
3
4
  export declare function createStateKey(key: string, scope?: string): string;
5
+ /**
6
+ * Resolve the description of an object.
7
+ */
8
+ export declare function resolveDescription(object: AnyObject): string | undefined;
package/dist/utils.js CHANGED
@@ -1,9 +1,22 @@
1
1
  export function noReference(input) {
2
- if (typeof input === 'object' && !!input && '$ref' in input) {
2
+ if (checkIsReference(input)) {
3
3
  throw new Error('Reference found');
4
4
  }
5
5
  return input;
6
6
  }
7
+ export function checkIsReference(input) {
8
+ return typeof input === 'object' && !!input && '$ref' in input;
9
+ }
7
10
  export function createStateKey(key, scope) {
8
- return scope ? `${scope}_${key}` : key;
11
+ return scope ? "".concat(scope, "_").concat(key) : key;
12
+ }
13
+ /**
14
+ * Resolve the description of an object.
15
+ */
16
+ export function resolveDescription(object) {
17
+ return 'x-description-html' in object && typeof object['x-description-html'] === 'string'
18
+ ? object['x-description-html']
19
+ : typeof object.description === 'string'
20
+ ? object.description
21
+ : undefined;
9
22
  }
package/package.json CHANGED
@@ -8,29 +8,30 @@
8
8
  "default": "./dist/index.js"
9
9
  }
10
10
  },
11
- "version": "0.7.1",
11
+ "version": "1.0.0",
12
+ "sideEffects": false,
12
13
  "dependencies": {
14
+ "@gitbook/openapi-parser": "workspace:*",
13
15
  "@scalar/api-client-react": "1.0.87",
14
- "classnames": "^2.5.1",
16
+ "@scalar/oas-utils": "^0.2.101",
17
+ "clsx": "^2.1.1",
15
18
  "flatted": "^3.2.9",
16
- "openapi-types": "^12.1.3",
17
- "swagger2openapi": "^7.0.8",
18
- "yaml": "1.10.2"
19
+ "react-aria-components": "^1.6.0",
20
+ "react-aria": "^3.37.0",
21
+ "usehooks-ts": "^3.1.0"
19
22
  },
20
23
  "devDependencies": {
21
- "@types/swagger2openapi": "^7.0.4",
22
24
  "bun-types": "^1.1.20",
23
25
  "typescript": "^5.5.3"
24
26
  },
25
27
  "peerDependencies": {
26
- "react": "*",
27
- "recoil": "^0.7.7"
28
+ "react": "*"
28
29
  },
29
30
  "scripts": {
30
- "build": "tsc",
31
+ "build": "tsc --project tsconfig.build.json",
31
32
  "typecheck": "tsc --noEmit",
32
33
  "unit": "bun test",
33
- "dev": "tsc -w",
34
+ "dev": "bun run build -- --watch",
34
35
  "clean": "rm -rf ./dist"
35
36
  },
36
37
  "files": [
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
 
3
- import classNames from 'classnames';
4
- import React from 'react';
5
- import { atom, useRecoilState } from 'recoil';
3
+ import clsx from 'clsx';
4
+ import { useCallback, useRef, useState, useSyncExternalStore } from 'react';
5
+ import { mergeProps, useButton, useDisclosure, useFocusRing } from 'react-aria';
6
+ import { useDisclosureState } from 'react-stately';
6
7
 
7
8
  interface InteractiveSectionTab {
8
9
  key: string;
@@ -10,10 +11,29 @@ interface InteractiveSectionTab {
10
11
  body: React.ReactNode;
11
12
  }
12
13
 
13
- const syncedTabsAtom = atom<Record<string, string>>({
14
- key: 'syncedTabState',
15
- default: {},
16
- });
14
+ let globalState: Record<string, string> = {};
15
+ const listeners = new Set<() => void>();
16
+
17
+ function useSyncedTabsGlobalState() {
18
+ const subscribe = useCallback((callback: () => void) => {
19
+ listeners.add(callback);
20
+ return () => listeners.delete(callback);
21
+ }, []);
22
+
23
+ const getSnapshot = useCallback(() => globalState, []);
24
+
25
+ const setSyncedTabs = useCallback(
26
+ (updater: (tabs: Record<string, string>) => Record<string, string>) => {
27
+ globalState = updater(globalState);
28
+ listeners.forEach((listener) => listener());
29
+ },
30
+ [],
31
+ );
32
+
33
+ const tabs = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
34
+
35
+ return [tabs, setSyncedTabs] as const;
36
+ }
17
37
 
18
38
  /**
19
39
  * To optimize rendering, most of the components are server-components,
@@ -27,15 +47,14 @@ export function InteractiveSection(props: {
27
47
  toggeable?: boolean;
28
48
  /** Default state of the toggle */
29
49
  defaultOpened?: boolean;
30
- /** Icons to display for the toggle */
31
- toggleOpenIcon?: React.ReactNode;
32
- toggleCloseIcon?: React.ReactNode;
50
+ /** Icon to display for the toggle */
51
+ toggleIcon?: React.ReactNode;
33
52
  /** Tabs of content to display */
34
53
  tabs?: Array<InteractiveSectionTab>;
35
54
  /** Default tab to have opened */
36
55
  defaultTab?: string;
37
56
  /** Content of the header */
38
- header: React.ReactNode;
57
+ header?: React.ReactNode;
39
58
  /** Body of the section */
40
59
  children?: React.ReactNode;
41
60
  /** Children to display within the container */
@@ -53,99 +72,121 @@ export function InteractiveSection(props: {
53
72
  header,
54
73
  children,
55
74
  overlay,
56
- toggleOpenIcon = '▶',
57
- toggleCloseIcon = '▼',
75
+ toggleIcon = '▶',
58
76
  stateKey,
59
77
  } = props;
60
- const [syncedTabs, setSyncedTabs] = useRecoilState(syncedTabsAtom);
78
+ const [syncedTabs, setSyncedTabs] = useSyncedTabsGlobalState();
61
79
  const tabFromState =
62
80
  stateKey && stateKey in syncedTabs
63
81
  ? tabs.find((tab) => tab.key === syncedTabs[stateKey])
64
82
  : undefined;
65
-
66
- const [opened, setOpened] = React.useState(defaultOpened);
67
- const [selectedTabKey, setSelectedTab] = React.useState(tabFromState?.key ?? defaultTab);
83
+ const [selectedTabKey, setSelectedTab] = useState(tabFromState?.key ?? defaultTab);
68
84
  const selectedTab: InteractiveSectionTab | undefined =
69
85
  tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
70
86
 
87
+ const state = useDisclosureState({
88
+ defaultExpanded: defaultOpened,
89
+ });
90
+ const panelRef = useRef<HTMLDivElement | null>(null);
91
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
92
+ const { buttonProps: triggerProps, panelProps } = useDisclosure({}, state, panelRef);
93
+ const { buttonProps } = useButton(triggerProps, triggerRef);
94
+ const { isFocusVisible, focusProps } = useFocusRing();
95
+
71
96
  return (
72
97
  <div
73
98
  id={id}
74
- className={classNames(
99
+ className={clsx(
75
100
  'openapi-section',
76
101
  toggeable ? 'openapi-section-toggeable' : null,
77
102
  className,
78
- toggeable ? `${className}-${opened ? 'opened' : 'closed'}` : null,
103
+ toggeable ? `${className}-${state.isExpanded ? 'opened' : 'closed'}` : null,
79
104
  )}
80
105
  >
81
- <div
82
- onClick={() => {
83
- if (toggeable) {
84
- setOpened(!opened);
85
- }
86
- }}
87
- className={classNames('openapi-section-header', `${className}-header`)}
88
- >
106
+ {header ? (
89
107
  <div
90
- className={classNames(
91
- 'openapi-section-header-content',
92
- `${className}-header-content`,
93
- )}
108
+ onClick={() => {
109
+ if (toggeable) {
110
+ state.toggle();
111
+ }
112
+ }}
113
+ className={clsx('openapi-section-header', `${className}-header`)}
94
114
  >
95
- {header}
115
+ <div
116
+ className={clsx(
117
+ 'openapi-section-header-content',
118
+ `${className}-header-content`,
119
+ )}
120
+ >
121
+ {(children || selectedTab?.body) && toggeable ? (
122
+ <button
123
+ {...mergeProps(buttonProps, focusProps)}
124
+ ref={triggerRef}
125
+ className={clsx('openapi-section-toggle', `${className}-toggle`)}
126
+ style={{
127
+ outline: isFocusVisible
128
+ ? '2px solid rgb(var(--primary-color-500) / 0.4)'
129
+ : 'none',
130
+ }}
131
+ >
132
+ {toggleIcon}
133
+ </button>
134
+ ) : null}
135
+ {header}
136
+ </div>
137
+ <div
138
+ className={clsx(
139
+ 'openapi-section-header-controls',
140
+ `${className}-header-controls`,
141
+ )}
142
+ onClick={(event) => {
143
+ event.stopPropagation();
144
+ }}
145
+ >
146
+ {tabs.length > 1 ? (
147
+ <select
148
+ className={clsx(
149
+ 'openapi-section-select',
150
+ 'openapi-select',
151
+ `${className}-tabs-select`,
152
+ )}
153
+ value={selectedTab?.key ?? ''}
154
+ onChange={(event) => {
155
+ setSelectedTab(event.target.value);
156
+ if (stateKey) {
157
+ setSyncedTabs((state) => ({
158
+ ...state,
159
+ [stateKey]: event.target.value,
160
+ }));
161
+ }
162
+ state.expand();
163
+ }}
164
+ >
165
+ {tabs.map((tab) => (
166
+ <option key={tab.key} value={tab.key}>
167
+ {tab.label}
168
+ </option>
169
+ ))}
170
+ </select>
171
+ ) : null}
172
+ </div>
96
173
  </div>
174
+ ) : null}
175
+ {(!toggeable || state.isExpanded) && (children || selectedTab?.body) ? (
97
176
  <div
98
- className={classNames(
99
- 'openapi-section-header-controls',
100
- `${className}-header-controls`,
101
- )}
102
- onClick={(event) => {
103
- event.stopPropagation();
104
- }}
177
+ ref={panelRef}
178
+ {...panelProps}
179
+ className={clsx('openapi-section-body', `${className}-body`)}
105
180
  >
106
- {tabs.length ? (
107
- <select
108
- className={classNames(
109
- 'openapi-section-select',
110
- 'openapi-select',
111
- `${className}-tabs-select`,
112
- )}
113
- value={selectedTab.key}
114
- onChange={(event) => {
115
- setSelectedTab(event.target.value);
116
- if (stateKey) {
117
- setSyncedTabs((state) => ({
118
- ...state,
119
- [stateKey]: event.target.value,
120
- }));
121
- }
122
- setOpened(true);
123
- }}
124
- >
125
- {tabs.map((tab) => (
126
- <option key={tab.key} value={tab.key}>
127
- {tab.label}
128
- </option>
129
- ))}
130
- </select>
131
- ) : null}
132
- {(children || selectedTab?.body) && toggeable ? (
133
- <button
134
- className={classNames('openapi-section-toggle', `${className}-toggle`)}
135
- onClick={() => setOpened(!opened)}
136
- >
137
- {opened ? toggleCloseIcon : toggleOpenIcon}
138
- </button>
139
- ) : null}
140
- </div>
141
- </div>
142
- {(!toggeable || opened) && (children || selectedTab?.body) ? (
143
- <div className={classNames('openapi-section-body', `${className}-body`)}>
144
181
  {children}
145
182
  {selectedTab?.body}
146
183
  </div>
147
184
  ) : null}
148
- {overlay}
185
+ {overlay ? (
186
+ <div className={clsx('openapi-section-overlay', `${className}-overlay`)}>
187
+ {overlay}
188
+ </div>
189
+ ) : null}
149
190
  </div>
150
191
  );
151
192
  }
package/src/Markdown.tsx CHANGED
@@ -1,12 +1,11 @@
1
- import * as React from 'react';
2
- import classNames from 'classnames';
1
+ import clsx from 'clsx';
3
2
 
4
3
  export function Markdown(props: { source: string; className?: string }) {
5
4
  const { source, className } = props;
6
5
 
7
6
  return (
8
7
  <div
9
- className={classNames('openapi-markdown', className)}
8
+ className={clsx('openapi-markdown', className)}
10
9
  dangerouslySetInnerHTML={{ __html: source }}
11
10
  />
12
11
  );
@@ -1,13 +1,11 @@
1
- import * as React from 'react';
2
-
3
1
  import { CodeSampleInput, codeSampleGenerators } from './code-samples';
4
- import { OpenAPIOperationData } from './fetchOpenAPIOperation';
5
2
  import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
6
3
  import { InteractiveSection } from './InteractiveSection';
7
4
  import { getServersURL } from './OpenAPIServerURL';
8
- import { ScalarApiButton } from './ScalarApiButton';
9
- import { OpenAPIContextProps } from './types';
5
+ import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
10
6
  import { noReference } from './utils';
7
+ import { stringifyOpenAPI } from './stringifyOpenAPI';
8
+ import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
11
9
 
12
10
  /**
13
11
  * Display code samples to execute the operation.
@@ -32,15 +30,15 @@ export function OpenAPICodeSample(props: {
32
30
  const example = param.schema
33
31
  ? generateSchemaExample(noReference(param.schema))
34
32
  : undefined;
35
- if (example !== undefined) {
33
+ if (example !== undefined && param.name) {
36
34
  headersObject[param.name] =
37
- typeof example !== 'string' ? JSON.stringify(example) : example;
35
+ typeof example !== 'string' ? stringifyOpenAPI(example) : example;
38
36
  }
39
37
  } else if (param.in === 'query' && param.required) {
40
38
  const example = param.schema
41
39
  ? generateSchemaExample(noReference(param.schema))
42
40
  : undefined;
43
- if (example !== undefined) {
41
+ if (example !== undefined && param.name) {
44
42
  searchParams.append(
45
43
  param.name,
46
44
  String(Array.isArray(example) ? example[0] : example),
@@ -50,7 +48,10 @@ export function OpenAPICodeSample(props: {
50
48
  });
51
49
 
52
50
  const requestBody = noReference(data.operation.requestBody);
53
- const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined;
51
+ const requestBodyContentEntries = requestBody?.content
52
+ ? Object.entries(requestBody.content)
53
+ : undefined;
54
+ const requestBodyContent = requestBodyContentEntries?.[0];
54
55
 
55
56
  const input: CodeSampleInput = {
56
57
  url:
@@ -108,21 +109,17 @@ export function OpenAPICodeSample(props: {
108
109
  const codeSamplesDisabled =
109
110
  data['x-codeSamples'] === false || data.operation['x-codeSamples'] === false;
110
111
  const samples = customCodeSamples ?? (!codeSamplesDisabled ? autoCodeSamples : []);
112
+
111
113
  if (samples.length === 0) {
112
114
  return null;
113
115
  }
114
116
 
115
117
  return (
116
- <InteractiveSection
117
- header="Request"
118
- className="openapi-codesample"
119
- tabs={samples}
120
- overlay={
121
- data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
122
- <ScalarApiButton method={data.method} path={data.path} />
123
- )
124
- }
125
- />
118
+ <OpenAPITabs items={samples}>
119
+ <InteractiveSection header={<OpenAPITabsList />} className="openapi-codesample">
120
+ <OpenAPITabsPanels />
121
+ </InteractiveSection>
122
+ </OpenAPITabs>
126
123
  );
127
124
  }
128
125
 
@@ -130,6 +127,7 @@ function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
130
127
  [key: string]: string;
131
128
  } {
132
129
  const security = securities[0];
130
+
133
131
  if (!security) {
134
132
  return {};
135
133
  }
@@ -137,12 +135,28 @@ function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
137
135
  switch (security[1].type) {
138
136
  case 'http': {
139
137
  let scheme = security[1].scheme;
140
- if (scheme === 'bearer') {
138
+ let format = security[1].bearerFormat ?? 'YOUR_SECRET_TOKEN';
139
+
140
+ if (scheme?.includes('bearer')) {
141
141
  scheme = 'Bearer';
142
+ } else if (scheme?.includes('basic')) {
143
+ scheme = 'Basic';
144
+ format = 'username:password';
145
+ } else if (scheme?.includes('token')) {
146
+ scheme = 'Token';
142
147
  }
143
148
 
144
149
  return {
145
- Authorization: scheme + ' ' + (security[1].bearerFormat ?? '<token>'),
150
+ Authorization: scheme + ' ' + format,
151
+ };
152
+ }
153
+ case 'apiKey': {
154
+ if (security[1].in !== 'header') return {};
155
+
156
+ const name = security[1].name ?? 'Authorization';
157
+
158
+ return {
159
+ [name]: 'YOUR_API_KEY',
146
160
  };
147
161
  }
148
162
  default: {
@@ -0,0 +1,50 @@
1
+ import { useRef } from 'react';
2
+ import type { OpenAPIClientContext } from './types';
3
+ import { mergeProps, useButton, useDisclosure, useFocusRing } from 'react-aria';
4
+ import { useDisclosureState } from 'react-stately';
5
+
6
+ interface Props {
7
+ context: OpenAPIClientContext;
8
+ children: React.ReactNode;
9
+ label?: string;
10
+ }
11
+
12
+ /**
13
+ * Display an interactive OpenAPI disclosure.
14
+ * The label is optional and defaults to "child attributes".
15
+ */
16
+ export function OpenAPIDisclosure({ context, children, label }: Props): JSX.Element {
17
+ const state = useDisclosureState({});
18
+ const panelRef = useRef<HTMLDivElement | null>(null);
19
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
20
+ const { buttonProps: triggerProps, panelProps } = useDisclosure({}, state, panelRef);
21
+ const { buttonProps } = useButton(triggerProps, triggerRef);
22
+ const { isFocusVisible, focusProps } = useFocusRing();
23
+
24
+ return (
25
+ <div className="openapi-disclosure">
26
+ <button
27
+ ref={triggerRef}
28
+ {...mergeProps(buttonProps, focusProps)}
29
+ slot="trigger"
30
+ className="openapi-disclosure-trigger"
31
+ style={{
32
+ outline: isFocusVisible
33
+ ? '2px solid rgb(var(--primary-color-500) / 0.4)'
34
+ : 'none',
35
+ }}
36
+ >
37
+ {context.icons.plus}
38
+ <span>
39
+ {`${state.isExpanded ? 'Hide' : 'Show'} ${label ? label : `child attributes`}`}
40
+ </span>
41
+ </button>
42
+
43
+ {state.isExpanded && (
44
+ <div ref={panelRef} {...panelProps} className="openapi-disclosure-panel">
45
+ {children}
46
+ </div>
47
+ )}
48
+ </div>
49
+ );
50
+ }