@gitbook/react-openapi 0.7.1 → 1.0.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.
- package/CHANGELOG.md +49 -0
- package/dist/InteractiveSection.d.ts +4 -8
- package/dist/InteractiveSection.jsx +60 -0
- package/dist/Markdown.d.ts +1 -2
- package/dist/Markdown.jsx +5 -0
- package/dist/OpenAPICodeSample.d.ts +2 -4
- package/dist/OpenAPICodeSample.jsx +141 -0
- package/dist/OpenAPIDisclosure.d.ts +12 -0
- package/dist/OpenAPIDisclosure.jsx +32 -0
- package/dist/OpenAPIDisclosureGroup.d.ts +19 -0
- package/dist/OpenAPIDisclosureGroup.jsx +81 -0
- package/dist/OpenAPIOperation.d.ts +2 -4
- package/dist/OpenAPIOperation.jsx +51 -0
- package/dist/OpenAPIOperationContext.d.ts +16 -0
- package/dist/OpenAPIOperationContext.jsx +26 -0
- package/dist/OpenAPIPath.d.ts +8 -0
- package/dist/OpenAPIPath.jsx +54 -0
- package/dist/OpenAPIRequestBody.d.ts +4 -5
- package/dist/OpenAPIRequestBody.jsx +22 -0
- package/dist/OpenAPIResponse.d.ts +4 -4
- package/dist/OpenAPIResponse.jsx +39 -0
- package/dist/OpenAPIResponseExample.d.ts +2 -4
- package/dist/OpenAPIResponseExample.jsx +108 -0
- package/dist/OpenAPIResponses.d.ts +3 -4
- package/dist/OpenAPIResponses.jsx +35 -0
- package/dist/OpenAPISchema.d.ts +11 -8
- package/dist/OpenAPISchema.jsx +285 -0
- package/dist/OpenAPISchemaName.d.ts +12 -0
- package/dist/OpenAPISchemaName.jsx +15 -0
- package/dist/OpenAPISecurities.d.ts +2 -4
- package/dist/OpenAPISecurities.jsx +55 -0
- package/dist/OpenAPIServerURL.d.ts +2 -3
- package/dist/OpenAPIServerURL.jsx +67 -0
- package/dist/OpenAPIServerURLVariable.d.ts +2 -3
- package/dist/OpenAPIServerURLVariable.jsx +8 -0
- package/dist/OpenAPISpec.d.ts +3 -4
- package/dist/OpenAPISpec.jsx +91 -0
- package/dist/OpenAPITabs.d.ts +26 -0
- package/dist/OpenAPITabs.jsx +103 -0
- package/dist/ScalarApiButton.d.ts +3 -3
- package/dist/ScalarApiButton.jsx +51 -0
- package/dist/code-samples.d.ts +4 -0
- package/dist/code-samples.js +103 -38
- package/dist/generateSchemaExample.d.ts +2 -2
- package/dist/generateSchemaExample.js +29 -102
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/resolveOpenAPIOperation.d.ts +11 -0
- package/dist/resolveOpenAPIOperation.js +194 -0
- package/dist/stringifyOpenAPI.d.ts +4 -0
- package/dist/stringifyOpenAPI.js +6 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/types.d.ts +11 -12
- package/dist/useSyncedTabsGlobalState.d.ts +1 -0
- package/dist/useSyncedTabsGlobalState.js +16 -0
- package/dist/utils.d.ts +6 -2
- package/dist/utils.js +13 -6
- package/package.json +12 -10
- package/src/InteractiveSection.tsx +90 -86
- package/src/Markdown.tsx +2 -3
- package/src/OpenAPICodeSample.tsx +43 -31
- package/src/OpenAPIDisclosure.tsx +50 -0
- package/src/OpenAPIDisclosureGroup.tsx +136 -0
- package/src/OpenAPIOperation.tsx +36 -42
- package/src/OpenAPIOperationContext.tsx +45 -0
- package/src/OpenAPIPath.tsx +65 -0
- package/src/OpenAPIRequestBody.tsx +10 -17
- package/src/OpenAPIResponse.tsx +27 -45
- package/src/OpenAPIResponseExample.tsx +89 -31
- package/src/OpenAPIResponses.tsx +48 -17
- package/src/OpenAPISchema.test.ts +1 -1
- package/src/OpenAPISchema.tsx +129 -108
- package/src/OpenAPISchemaName.tsx +27 -0
- package/src/OpenAPISecurities.tsx +45 -24
- package/src/OpenAPIServerURL.tsx +17 -10
- package/src/OpenAPIServerURLVariable.tsx +2 -4
- package/src/OpenAPISpec.tsx +58 -58
- package/src/OpenAPITabs.tsx +153 -0
- package/src/ScalarApiButton.tsx +84 -7
- package/src/code-samples.test.ts +51 -0
- package/src/code-samples.ts +95 -31
- package/src/generateSchemaExample.ts +26 -153
- package/src/index.ts +3 -2
- package/src/resolveOpenAPIOperation.test.ts +177 -0
- package/src/resolveOpenAPIOperation.ts +164 -0
- package/src/stringifyOpenAPI.ts +6 -0
- package/src/types.ts +17 -10
- package/src/useSyncedTabsGlobalState.ts +23 -0
- package/src/utils.ts +14 -7
- package/dist/InteractiveSection.js +0 -47
- package/dist/Markdown.js +0 -6
- package/dist/OpenAPICodeSample.js +0 -110
- package/dist/OpenAPIOperation.js +0 -38
- package/dist/OpenAPIRequestBody.js +0 -18
- package/dist/OpenAPIResponse.js +0 -32
- package/dist/OpenAPIResponseExample.js +0 -54
- package/dist/OpenAPIResponses.js +0 -18
- package/dist/OpenAPISchema.js +0 -235
- package/dist/OpenAPISchema.test.d.ts +0 -1
- package/dist/OpenAPISchema.test.js +0 -91
- package/dist/OpenAPISecurities.js +0 -42
- package/dist/OpenAPIServerURL.js +0 -51
- package/dist/OpenAPIServerURLVariable.js +0 -10
- package/dist/OpenAPISpec.js +0 -70
- package/dist/ScalarApiButton.js +0 -14
- package/dist/fetchOpenAPIOperation.d.ts +0 -72
- package/dist/fetchOpenAPIOperation.js +0 -124
- package/dist/fetchOpenAPIOperation.test.d.ts +0 -1
- package/dist/fetchOpenAPIOperation.test.js +0 -152
- package/dist/resolveOpenAPIPath.d.ts +0 -7
- package/dist/resolveOpenAPIPath.js +0 -112
- package/dist/resolveOpenAPIPath.test.d.ts +0 -1
- package/dist/resolveOpenAPIPath.test.js +0 -39
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/fetchOpenAPIOperation.test.ts +0 -185
- package/src/fetchOpenAPIOperation.ts +0 -230
- package/src/resolveOpenAPIPath.test.ts +0 -60
- package/src/resolveOpenAPIPath.ts +0 -145
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { useRef, useState } 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,11 +11,6 @@ interface InteractiveSectionTab {
|
|
|
10
11
|
body: React.ReactNode;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const syncedTabsAtom = atom<Record<string, string>>({
|
|
14
|
-
key: 'syncedTabState',
|
|
15
|
-
default: {},
|
|
16
|
-
});
|
|
17
|
-
|
|
18
14
|
/**
|
|
19
15
|
* To optimize rendering, most of the components are server-components,
|
|
20
16
|
* and the interactiveness is mainly handled by a few key components like this one.
|
|
@@ -27,21 +23,18 @@ export function InteractiveSection(props: {
|
|
|
27
23
|
toggeable?: boolean;
|
|
28
24
|
/** Default state of the toggle */
|
|
29
25
|
defaultOpened?: boolean;
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
toggleCloseIcon?: React.ReactNode;
|
|
26
|
+
/** Icon to display for the toggle */
|
|
27
|
+
toggleIcon?: React.ReactNode;
|
|
33
28
|
/** Tabs of content to display */
|
|
34
29
|
tabs?: Array<InteractiveSectionTab>;
|
|
35
30
|
/** Default tab to have opened */
|
|
36
31
|
defaultTab?: string;
|
|
37
32
|
/** Content of the header */
|
|
38
|
-
header
|
|
33
|
+
header?: React.ReactNode;
|
|
39
34
|
/** Body of the section */
|
|
40
35
|
children?: React.ReactNode;
|
|
41
36
|
/** Children to display within the container */
|
|
42
37
|
overlay?: React.ReactNode;
|
|
43
|
-
/** An optional key referencing a value in global state */
|
|
44
|
-
stateKey?: string;
|
|
45
38
|
}) {
|
|
46
39
|
const {
|
|
47
40
|
id,
|
|
@@ -53,99 +46,110 @@ export function InteractiveSection(props: {
|
|
|
53
46
|
header,
|
|
54
47
|
children,
|
|
55
48
|
overlay,
|
|
56
|
-
|
|
57
|
-
toggleCloseIcon = '▼',
|
|
58
|
-
stateKey,
|
|
49
|
+
toggleIcon = '▶',
|
|
59
50
|
} = props;
|
|
60
|
-
const [syncedTabs, setSyncedTabs] = useRecoilState(syncedTabsAtom);
|
|
61
|
-
const tabFromState =
|
|
62
|
-
stateKey && stateKey in syncedTabs
|
|
63
|
-
? tabs.find((tab) => tab.key === syncedTabs[stateKey])
|
|
64
|
-
: undefined;
|
|
65
51
|
|
|
66
|
-
const [
|
|
67
|
-
const [selectedTabKey, setSelectedTab] = React.useState(tabFromState?.key ?? defaultTab);
|
|
52
|
+
const [selectedTabKey, setSelectedTab] = useState(defaultTab);
|
|
68
53
|
const selectedTab: InteractiveSectionTab | undefined =
|
|
69
|
-
|
|
54
|
+
tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
|
|
55
|
+
|
|
56
|
+
const state = useDisclosureState({
|
|
57
|
+
defaultExpanded: defaultOpened,
|
|
58
|
+
});
|
|
59
|
+
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
60
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
61
|
+
const { buttonProps: triggerProps, panelProps } = useDisclosure({}, state, panelRef);
|
|
62
|
+
const { buttonProps } = useButton(triggerProps, triggerRef);
|
|
63
|
+
const { isFocusVisible, focusProps } = useFocusRing();
|
|
70
64
|
|
|
71
65
|
return (
|
|
72
66
|
<div
|
|
73
67
|
id={id}
|
|
74
|
-
className={
|
|
68
|
+
className={clsx(
|
|
75
69
|
'openapi-section',
|
|
76
70
|
toggeable ? 'openapi-section-toggeable' : null,
|
|
77
71
|
className,
|
|
78
|
-
toggeable ? `${className}-${
|
|
72
|
+
toggeable ? `${className}-${state.isExpanded ? 'opened' : 'closed'}` : null,
|
|
79
73
|
)}
|
|
80
74
|
>
|
|
81
|
-
|
|
82
|
-
onClick={() => {
|
|
83
|
-
if (toggeable) {
|
|
84
|
-
setOpened(!opened);
|
|
85
|
-
}
|
|
86
|
-
}}
|
|
87
|
-
className={classNames('openapi-section-header', `${className}-header`)}
|
|
88
|
-
>
|
|
75
|
+
{header ? (
|
|
89
76
|
<div
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
onClick={() => {
|
|
78
|
+
if (toggeable) {
|
|
79
|
+
state.toggle();
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
className={clsx('openapi-section-header', `${className}-header`)}
|
|
94
83
|
>
|
|
95
|
-
|
|
84
|
+
<div
|
|
85
|
+
className={clsx(
|
|
86
|
+
'openapi-section-header-content',
|
|
87
|
+
`${className}-header-content`,
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{(children || selectedTab?.body) && toggeable ? (
|
|
91
|
+
<button
|
|
92
|
+
{...mergeProps(buttonProps, focusProps)}
|
|
93
|
+
ref={triggerRef}
|
|
94
|
+
className={clsx('openapi-section-toggle', `${className}-toggle`)}
|
|
95
|
+
style={{
|
|
96
|
+
outline: isFocusVisible
|
|
97
|
+
? '2px solid rgb(var(--primary-color-500) / 0.4)'
|
|
98
|
+
: 'none',
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{toggleIcon}
|
|
102
|
+
</button>
|
|
103
|
+
) : null}
|
|
104
|
+
{header}
|
|
105
|
+
</div>
|
|
106
|
+
<div
|
|
107
|
+
className={clsx(
|
|
108
|
+
'openapi-section-header-controls',
|
|
109
|
+
`${className}-header-controls`,
|
|
110
|
+
)}
|
|
111
|
+
onClick={(event) => {
|
|
112
|
+
event.stopPropagation();
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{tabs.length > 1 ? (
|
|
116
|
+
<select
|
|
117
|
+
className={clsx(
|
|
118
|
+
'openapi-section-select',
|
|
119
|
+
'openapi-select',
|
|
120
|
+
`${className}-tabs-select`,
|
|
121
|
+
)}
|
|
122
|
+
value={selectedTab?.key ?? ''}
|
|
123
|
+
onChange={(event) => {
|
|
124
|
+
setSelectedTab(event.target.value);
|
|
125
|
+
state.expand();
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
{tabs.map((tab) => (
|
|
129
|
+
<option key={tab.key} value={tab.key}>
|
|
130
|
+
{tab.label}
|
|
131
|
+
</option>
|
|
132
|
+
))}
|
|
133
|
+
</select>
|
|
134
|
+
) : null}
|
|
135
|
+
</div>
|
|
96
136
|
</div>
|
|
137
|
+
) : null}
|
|
138
|
+
{(!toggeable || state.isExpanded) && (children || selectedTab?.body) ? (
|
|
97
139
|
<div
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)}
|
|
102
|
-
onClick={(event) => {
|
|
103
|
-
event.stopPropagation();
|
|
104
|
-
}}
|
|
140
|
+
ref={panelRef}
|
|
141
|
+
{...panelProps}
|
|
142
|
+
className={clsx('openapi-section-body', `${className}-body`)}
|
|
105
143
|
>
|
|
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
144
|
{children}
|
|
145
145
|
{selectedTab?.body}
|
|
146
146
|
</div>
|
|
147
147
|
) : null}
|
|
148
|
-
{overlay
|
|
148
|
+
{overlay ? (
|
|
149
|
+
<div className={clsx('openapi-section-overlay', `${className}-overlay`)}>
|
|
150
|
+
{overlay}
|
|
151
|
+
</div>
|
|
152
|
+
) : null}
|
|
149
153
|
</div>
|
|
150
154
|
);
|
|
151
155
|
}
|
package/src/Markdown.tsx
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import
|
|
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={
|
|
8
|
+
className={clsx('openapi-markdown', className)}
|
|
10
9
|
dangerouslySetInnerHTML={{ __html: source }}
|
|
11
10
|
/>
|
|
12
11
|
);
|
|
@@ -1,13 +1,12 @@
|
|
|
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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
5
|
+
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
6
|
+
import { createStateKey } from './utils';
|
|
7
|
+
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
8
|
+
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
|
9
|
+
import { checkIsReference } from './utils';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Display code samples to execute the operation.
|
|
@@ -22,25 +21,20 @@ export function OpenAPICodeSample(props: {
|
|
|
22
21
|
const searchParams = new URLSearchParams();
|
|
23
22
|
const headersObject: { [k: string]: string } = {};
|
|
24
23
|
|
|
25
|
-
data.operation.parameters?.forEach((
|
|
26
|
-
const param = noReference(rawParam);
|
|
24
|
+
data.operation.parameters?.forEach((param) => {
|
|
27
25
|
if (!param) {
|
|
28
26
|
return;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
if (param.in === 'header' && param.required) {
|
|
32
|
-
const example = param.schema
|
|
33
|
-
|
|
34
|
-
: undefined;
|
|
35
|
-
if (example !== undefined) {
|
|
30
|
+
const example = param.schema ? generateSchemaExample(param.schema) : undefined;
|
|
31
|
+
if (example !== undefined && param.name) {
|
|
36
32
|
headersObject[param.name] =
|
|
37
|
-
typeof example !== 'string' ?
|
|
33
|
+
typeof example !== 'string' ? stringifyOpenAPI(example) : example;
|
|
38
34
|
}
|
|
39
35
|
} else if (param.in === 'query' && param.required) {
|
|
40
|
-
const example = param.schema
|
|
41
|
-
|
|
42
|
-
: undefined;
|
|
43
|
-
if (example !== undefined) {
|
|
36
|
+
const example = param.schema ? generateSchemaExample(param.schema) : undefined;
|
|
37
|
+
if (example !== undefined && param.name) {
|
|
44
38
|
searchParams.append(
|
|
45
39
|
param.name,
|
|
46
40
|
String(Array.isArray(example) ? example[0] : example),
|
|
@@ -49,8 +43,13 @@ export function OpenAPICodeSample(props: {
|
|
|
49
43
|
}
|
|
50
44
|
});
|
|
51
45
|
|
|
52
|
-
const requestBody =
|
|
53
|
-
|
|
46
|
+
const requestBody = !checkIsReference(data.operation.requestBody)
|
|
47
|
+
? data.operation.requestBody
|
|
48
|
+
: undefined;
|
|
49
|
+
const requestBodyContentEntries = requestBody?.content
|
|
50
|
+
? Object.entries(requestBody.content)
|
|
51
|
+
: undefined;
|
|
52
|
+
const requestBodyContent = requestBodyContentEntries?.[0];
|
|
54
53
|
|
|
55
54
|
const input: CodeSampleInput = {
|
|
56
55
|
url:
|
|
@@ -108,21 +107,17 @@ export function OpenAPICodeSample(props: {
|
|
|
108
107
|
const codeSamplesDisabled =
|
|
109
108
|
data['x-codeSamples'] === false || data.operation['x-codeSamples'] === false;
|
|
110
109
|
const samples = customCodeSamples ?? (!codeSamplesDisabled ? autoCodeSamples : []);
|
|
110
|
+
|
|
111
111
|
if (samples.length === 0) {
|
|
112
112
|
return null;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
return (
|
|
116
|
-
<
|
|
117
|
-
header="
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
|
|
122
|
-
<ScalarApiButton method={data.method} path={data.path} />
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
/>
|
|
116
|
+
<OpenAPITabs stateKey={createStateKey('codesample')} items={samples}>
|
|
117
|
+
<InteractiveSection header={<OpenAPITabsList />} className="openapi-codesample">
|
|
118
|
+
<OpenAPITabsPanels />
|
|
119
|
+
</InteractiveSection>
|
|
120
|
+
</OpenAPITabs>
|
|
126
121
|
);
|
|
127
122
|
}
|
|
128
123
|
|
|
@@ -130,6 +125,7 @@ function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
|
|
|
130
125
|
[key: string]: string;
|
|
131
126
|
} {
|
|
132
127
|
const security = securities[0];
|
|
128
|
+
|
|
133
129
|
if (!security) {
|
|
134
130
|
return {};
|
|
135
131
|
}
|
|
@@ -137,12 +133,28 @@ function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
|
|
|
137
133
|
switch (security[1].type) {
|
|
138
134
|
case 'http': {
|
|
139
135
|
let scheme = security[1].scheme;
|
|
140
|
-
|
|
136
|
+
let format = security[1].bearerFormat ?? 'YOUR_SECRET_TOKEN';
|
|
137
|
+
|
|
138
|
+
if (scheme?.includes('bearer')) {
|
|
141
139
|
scheme = 'Bearer';
|
|
140
|
+
} else if (scheme?.includes('basic')) {
|
|
141
|
+
scheme = 'Basic';
|
|
142
|
+
format = 'username:password';
|
|
143
|
+
} else if (scheme?.includes('token')) {
|
|
144
|
+
scheme = 'Token';
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
return {
|
|
145
|
-
Authorization: scheme + ' ' +
|
|
148
|
+
Authorization: scheme + ' ' + format,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
case 'apiKey': {
|
|
152
|
+
if (security[1].in !== 'header') return {};
|
|
153
|
+
|
|
154
|
+
const name = security[1].name ?? 'Authorization';
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
[name]: 'YOUR_API_KEY',
|
|
146
158
|
};
|
|
147
159
|
}
|
|
148
160
|
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
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
groups: TDisclosureGroup[];
|
|
3
|
+
icon?: React.ReactNode;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type TDisclosureGroup = {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string | React.ReactNode;
|
|
9
|
+
tabs?: {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string | React.ReactNode;
|
|
12
|
+
body?: React.ReactNode;
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
import { mergeProps, useButton, useDisclosure, useFocusRing, useId } from 'react-aria';
|
|
17
|
+
import {
|
|
18
|
+
DisclosureGroupProps,
|
|
19
|
+
DisclosureGroupState,
|
|
20
|
+
useDisclosureGroupState,
|
|
21
|
+
useDisclosureState,
|
|
22
|
+
} from 'react-stately';
|
|
23
|
+
import { createContext, useContext, useRef, useState } from 'react';
|
|
24
|
+
|
|
25
|
+
const DisclosureGroupStateContext = createContext<DisclosureGroupState | null>(null);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Display an interactive OpenAPI disclosure group.
|
|
29
|
+
*/
|
|
30
|
+
export function OpenAPIDisclosureGroup(props: DisclosureGroupProps & Props) {
|
|
31
|
+
const { icon, groups } = props;
|
|
32
|
+
|
|
33
|
+
const state = useDisclosureGroupState(props);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<DisclosureGroupStateContext.Provider value={state}>
|
|
37
|
+
{groups.map((group) => (
|
|
38
|
+
<DisclosureItem icon={icon} key={group.id} group={group} />
|
|
39
|
+
))}
|
|
40
|
+
</DisclosureGroupStateContext.Provider>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function DisclosureItem(props: { group: TDisclosureGroup; icon?: React.ReactNode }) {
|
|
45
|
+
const { icon, group } = props;
|
|
46
|
+
|
|
47
|
+
const defaultId = useId();
|
|
48
|
+
const id = group.id || defaultId;
|
|
49
|
+
const groupState = useContext(DisclosureGroupStateContext);
|
|
50
|
+
const isExpanded = groupState?.expandedKeys.has(id) || false;
|
|
51
|
+
const state = useDisclosureState({
|
|
52
|
+
isExpanded,
|
|
53
|
+
onExpandedChange() {
|
|
54
|
+
if (groupState) {
|
|
55
|
+
groupState.toggleKey(id);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
61
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
62
|
+
const isDisabled = groupState?.isDisabled || !group.tabs?.length || false;
|
|
63
|
+
const { buttonProps: triggerProps, panelProps } = useDisclosure(
|
|
64
|
+
{
|
|
65
|
+
...props,
|
|
66
|
+
isExpanded,
|
|
67
|
+
isDisabled,
|
|
68
|
+
},
|
|
69
|
+
state,
|
|
70
|
+
panelRef,
|
|
71
|
+
);
|
|
72
|
+
const { buttonProps } = useButton(triggerProps, triggerRef);
|
|
73
|
+
const { isFocusVisible, focusProps } = useFocusRing();
|
|
74
|
+
|
|
75
|
+
const defaultTab = group.tabs?.[0]?.id || '';
|
|
76
|
+
const [selectedTabKey, setSelectedTabKey] = useState(defaultTab);
|
|
77
|
+
const selectedTab = group.tabs?.find((tab) => tab.id === selectedTabKey);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className="openapi-disclosure-group" aria-expanded={state.isExpanded}>
|
|
81
|
+
<div className="openapi-disclosure-group-header">
|
|
82
|
+
<button
|
|
83
|
+
slot="trigger"
|
|
84
|
+
ref={triggerRef}
|
|
85
|
+
{...mergeProps(buttonProps, focusProps)}
|
|
86
|
+
disabled={isDisabled}
|
|
87
|
+
style={{
|
|
88
|
+
outline: isFocusVisible
|
|
89
|
+
? '2px solid rgb(var(--primary-color-500)/0.4)'
|
|
90
|
+
: 'none',
|
|
91
|
+
}}
|
|
92
|
+
className="openapi-disclosure-group-trigger"
|
|
93
|
+
>
|
|
94
|
+
<div className="openapi-disclosure-group-icon">
|
|
95
|
+
{icon || (
|
|
96
|
+
<svg viewBox="0 0 24 24" className="openapi-disclosure-group-icon">
|
|
97
|
+
<path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
|
98
|
+
</svg>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{group.label}
|
|
103
|
+
</button>
|
|
104
|
+
{group.tabs ? (
|
|
105
|
+
<div className="openapi-disclosure-group-mediatype">
|
|
106
|
+
{group.tabs?.length > 1 ? (
|
|
107
|
+
<select
|
|
108
|
+
className="openapi-section-select openapi-select openapi-disclosure-group-tabs-select"
|
|
109
|
+
onClick={(event) => event.stopPropagation()}
|
|
110
|
+
value={selectedTab?.id}
|
|
111
|
+
onChange={(event) => {
|
|
112
|
+
setSelectedTabKey(event.target.value);
|
|
113
|
+
state.expand();
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
{group.tabs.map((tab) => (
|
|
117
|
+
<option key={tab.id} value={tab.id}>
|
|
118
|
+
{tab.label}
|
|
119
|
+
</option>
|
|
120
|
+
))}
|
|
121
|
+
</select>
|
|
122
|
+
) : !!group.tabs[0] ? (
|
|
123
|
+
<span>{group.tabs[0].label}</span>
|
|
124
|
+
) : null}
|
|
125
|
+
</div>
|
|
126
|
+
) : null}
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{state.isExpanded && selectedTab && (
|
|
130
|
+
<div className="openapi-disclosure-group-panel" ref={panelRef} {...panelProps}>
|
|
131
|
+
{selectedTab.body}
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
package/src/OpenAPIOperation.tsx
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import classNames from 'classnames';
|
|
3
|
-
import { ApiClientModalProvider } from '@scalar/api-client-react';
|
|
1
|
+
import clsx from 'clsx';
|
|
4
2
|
|
|
5
|
-
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
|
|
6
3
|
import { Markdown } from './Markdown';
|
|
7
4
|
import { OpenAPICodeSample } from './OpenAPICodeSample';
|
|
8
5
|
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
|
9
|
-
import { OpenAPIServerURL } from './OpenAPIServerURL';
|
|
10
6
|
import { OpenAPISpec } from './OpenAPISpec';
|
|
11
|
-
import { OpenAPIClientContext, OpenAPIContextProps } from './types';
|
|
7
|
+
import type { OpenAPIClientContext, OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
8
|
+
import { OpenAPIPath } from './OpenAPIPath';
|
|
9
|
+
import { resolveDescription } from './utils';
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* Display an interactive OpenAPI operation.
|
|
@@ -19,7 +17,7 @@ export function OpenAPIOperation(props: {
|
|
|
19
17
|
context: OpenAPIContextProps;
|
|
20
18
|
}) {
|
|
21
19
|
const { className, data, context } = props;
|
|
22
|
-
const { operation
|
|
20
|
+
const { operation } = data;
|
|
23
21
|
|
|
24
22
|
const clientContext: OpenAPIClientContext = {
|
|
25
23
|
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
|
@@ -27,46 +25,42 @@ export function OpenAPIOperation(props: {
|
|
|
27
25
|
blockKey: context.blockKey,
|
|
28
26
|
};
|
|
29
27
|
|
|
28
|
+
const description = resolveDescription(operation)?.trim();
|
|
29
|
+
|
|
30
30
|
return (
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<div className="openapi-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{operation
|
|
41
|
-
<
|
|
31
|
+
<div className={clsx('openapi-operation', className)}>
|
|
32
|
+
<div className="openapi-summary" id={context.id}>
|
|
33
|
+
<h2 className="openapi-summary-title" data-deprecated={operation.deprecated}>
|
|
34
|
+
{operation.summary}
|
|
35
|
+
</h2>
|
|
36
|
+
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
|
37
|
+
</div>
|
|
38
|
+
<div className="openapi-columns">
|
|
39
|
+
<div className="openapi-column-spec">
|
|
40
|
+
{operation['x-deprecated-sunset'] ? (
|
|
41
|
+
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
|
|
42
|
+
This operation is deprecated and will be sunset on{' '}
|
|
43
|
+
<span className="openapi-deprecated-sunset-date">
|
|
44
|
+
{operation['x-deprecated-sunset']}
|
|
45
|
+
</span>
|
|
46
|
+
{`.`}
|
|
47
|
+
</div>
|
|
42
48
|
) : null}
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
className={
|
|
46
|
-
'openapi-method',
|
|
47
|
-
`openapi-method-${method.toLowerCase()}`,
|
|
48
|
-
)}
|
|
49
|
-
>
|
|
50
|
-
{method.toUpperCase()}
|
|
51
|
-
</span>
|
|
52
|
-
<span className="openapi-url">
|
|
53
|
-
<OpenAPIServerURL servers={servers} />
|
|
54
|
-
{path}
|
|
55
|
-
</span>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
<div className={classNames('openapi-columns')}>
|
|
59
|
-
<div className={classNames('openapi-column-spec')}>
|
|
60
|
-
<OpenAPISpec rawData={toJSON(data)} context={clientContext} />
|
|
61
|
-
</div>
|
|
62
|
-
<div className={classNames('openapi-column-preview')}>
|
|
63
|
-
<div className={classNames('openapi-column-preview-body')}>
|
|
64
|
-
<OpenAPICodeSample {...props} />
|
|
65
|
-
<OpenAPIResponseExample {...props} />
|
|
49
|
+
{description ? (
|
|
50
|
+
<div className="openapi-intro">
|
|
51
|
+
<Markdown className="openapi-description" source={description} />
|
|
66
52
|
</div>
|
|
53
|
+
) : null}
|
|
54
|
+
<OpenAPIPath data={data} context={context} />
|
|
55
|
+
<OpenAPISpec data={data} context={clientContext} />
|
|
56
|
+
</div>
|
|
57
|
+
<div className="openapi-column-preview">
|
|
58
|
+
<div className="openapi-column-preview-body">
|
|
59
|
+
<OpenAPICodeSample {...props} />
|
|
60
|
+
<OpenAPIResponseExample {...props} />
|
|
67
61
|
</div>
|
|
68
62
|
</div>
|
|
69
63
|
</div>
|
|
70
|
-
</
|
|
64
|
+
</div>
|
|
71
65
|
);
|
|
72
66
|
}
|