@gitbook/react-openapi 1.0.3 → 1.0.5
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 +15 -0
- package/dist/InteractiveSection.jsx +10 -9
- package/dist/OpenAPICodeSample.jsx +8 -9
- package/dist/OpenAPIDisclosure.d.ts +2 -1
- package/dist/OpenAPIDisclosure.jsx +2 -1
- package/dist/OpenAPIDisclosureGroup.d.ts +1 -1
- package/dist/OpenAPIDisclosureGroup.jsx +3 -2
- package/dist/OpenAPIOperation.jsx +2 -2
- package/dist/OpenAPIPath.d.ts +3 -2
- package/dist/OpenAPIPath.jsx +4 -15
- package/dist/OpenAPIRequestBody.jsx +1 -1
- package/dist/OpenAPIResponse.jsx +1 -1
- package/dist/OpenAPIResponseExample.jsx +10 -10
- package/dist/OpenAPIResponses.d.ts +1 -1
- package/dist/OpenAPIResponses.jsx +5 -5
- package/dist/OpenAPISchema.d.ts +5 -1
- package/dist/OpenAPISchema.jsx +30 -21
- package/dist/OpenAPISchemaName.d.ts +4 -3
- package/dist/OpenAPISchemaName.jsx +1 -1
- package/dist/OpenAPISecurities.jsx +2 -2
- package/dist/OpenAPISpec.jsx +3 -4
- package/dist/OpenAPITabs.d.ts +3 -3
- package/dist/OpenAPITabs.jsx +52 -49
- package/dist/ScalarApiButton.jsx +1 -1
- package/dist/StaticSection.d.ts +10 -0
- package/dist/StaticSection.jsx +23 -0
- package/dist/code-samples.js +11 -11
- package/dist/generateSchemaExample.js +2 -1
- package/dist/resolveOpenAPIOperation.d.ts +3 -3
- package/dist/resolveOpenAPIOperation.js +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/useSyncedTabsGlobalState.d.ts +10 -1
- package/dist/useSyncedTabsGlobalState.js +19 -15
- package/dist/util/server.d.ts +1 -1
- package/dist/util/server.js +1 -3
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +4 -6
- package/package.json +2 -7
- package/src/InteractiveSection.tsx +13 -21
- package/src/OpenAPICodeSample.tsx +11 -12
- package/src/OpenAPIDisclosure.tsx +5 -3
- package/src/OpenAPIDisclosureGroup.tsx +13 -11
- package/src/OpenAPIOperation.tsx +3 -3
- package/src/OpenAPIOperationContext.tsx +1 -1
- package/src/OpenAPIPath.tsx +11 -10
- package/src/OpenAPIRequestBody.tsx +2 -2
- package/src/OpenAPIResponse.tsx +3 -3
- package/src/OpenAPIResponseExample.tsx +12 -19
- package/src/OpenAPIResponses.tsx +7 -7
- package/src/OpenAPISchema.test.ts +5 -5
- package/src/OpenAPISchema.tsx +77 -27
- package/src/OpenAPISchemaName.tsx +5 -4
- package/src/OpenAPISecurities.tsx +3 -3
- package/src/OpenAPISpec.tsx +3 -5
- package/src/OpenAPITabs.tsx +56 -67
- package/src/ScalarApiButton.tsx +3 -3
- package/src/StaticSection.tsx +59 -0
- package/src/code-samples.test.ts +66 -66
- package/src/code-samples.ts +14 -14
- package/src/generateSchemaExample.ts +3 -3
- package/src/json2xml.test.ts +1 -1
- package/src/resolveOpenAPIOperation.test.ts +6 -6
- package/src/resolveOpenAPIOperation.ts +7 -7
- package/src/stringifyOpenAPI.ts +1 -1
- package/src/useSyncedTabsGlobalState.ts +33 -21
- package/src/util/server.test.ts +3 -3
- package/src/util/server.ts +2 -3
- package/src/utils.ts +4 -4
package/src/OpenAPISchema.tsx
CHANGED
|
@@ -4,11 +4,11 @@ import { useId } from 'react';
|
|
|
4
4
|
|
|
5
5
|
import { InteractiveSection } from './InteractiveSection';
|
|
6
6
|
import { Markdown } from './Markdown';
|
|
7
|
+
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
|
8
|
+
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
|
9
|
+
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
7
10
|
import type { OpenAPIClientContext } from './types';
|
|
8
11
|
import { checkIsReference, resolveDescription } from './utils';
|
|
9
|
-
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
10
|
-
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
|
11
|
-
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
|
12
12
|
|
|
13
13
|
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
|
14
14
|
|
|
@@ -27,7 +27,7 @@ export function OpenAPISchemaProperty(
|
|
|
27
27
|
circularRefs?: CircularRefsIds;
|
|
28
28
|
context: OpenAPIClientContext;
|
|
29
29
|
className?: string;
|
|
30
|
-
}
|
|
30
|
+
}
|
|
31
31
|
) {
|
|
32
32
|
const {
|
|
33
33
|
schema,
|
|
@@ -49,20 +49,13 @@ export function OpenAPISchemaProperty(
|
|
|
49
49
|
|
|
50
50
|
if (alternatives?.[0]?.length) {
|
|
51
51
|
return (
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
context={context}
|
|
60
|
-
/>
|
|
61
|
-
))}
|
|
62
|
-
{parentCircularRef ? (
|
|
63
|
-
<OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
|
|
64
|
-
) : null}
|
|
65
|
-
</InteractiveSection>
|
|
52
|
+
<OpenAPISchemaAlternativesItem
|
|
53
|
+
{...props}
|
|
54
|
+
circularRefs={circularRefs}
|
|
55
|
+
context={context}
|
|
56
|
+
alternatives={alternatives}
|
|
57
|
+
parentCircularRef={parentCircularRef}
|
|
58
|
+
/>
|
|
66
59
|
);
|
|
67
60
|
}
|
|
68
61
|
|
|
@@ -126,9 +119,9 @@ export function OpenAPISchemaProperties(props: {
|
|
|
126
119
|
|
|
127
120
|
return (
|
|
128
121
|
<div id={id} className="openapi-schema-properties">
|
|
129
|
-
{properties.map((property) => (
|
|
122
|
+
{properties.map((property, index) => (
|
|
130
123
|
<OpenAPISchemaProperty
|
|
131
|
-
key={
|
|
124
|
+
key={index}
|
|
132
125
|
circularRefs={circularRefs}
|
|
133
126
|
{...property}
|
|
134
127
|
context={context}
|
|
@@ -173,6 +166,25 @@ function OpenAPISchemaAlternative(props: {
|
|
|
173
166
|
const id = useId();
|
|
174
167
|
const subProperties = getSchemaProperties(schema);
|
|
175
168
|
const description = resolveDescription(schema);
|
|
169
|
+
const alternatives = getSchemaAlternatives(schema, new Set(circularRefs?.keys()));
|
|
170
|
+
|
|
171
|
+
if (alternatives?.[0]?.length && !subProperties?.length) {
|
|
172
|
+
return (
|
|
173
|
+
<>
|
|
174
|
+
{description ? (
|
|
175
|
+
<Markdown source={description} className="openapi-schema-description" />
|
|
176
|
+
) : null}
|
|
177
|
+
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
|
|
178
|
+
<OpenAPISchemaAlternativesItem
|
|
179
|
+
schema={schema}
|
|
180
|
+
circularRefs={circularRefs}
|
|
181
|
+
context={context}
|
|
182
|
+
alternatives={alternatives}
|
|
183
|
+
/>
|
|
184
|
+
</OpenAPIDisclosure>
|
|
185
|
+
</>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
176
188
|
|
|
177
189
|
return (
|
|
178
190
|
<>
|
|
@@ -193,6 +205,35 @@ function OpenAPISchemaAlternative(props: {
|
|
|
193
205
|
);
|
|
194
206
|
}
|
|
195
207
|
|
|
208
|
+
function OpenAPISchemaAlternativesItem(
|
|
209
|
+
props: OpenAPISchemaPropertyEntry & {
|
|
210
|
+
circularRefs?: CircularRefsIds;
|
|
211
|
+
context: OpenAPIClientContext;
|
|
212
|
+
alternatives: OpenAPISchemaAlternatives;
|
|
213
|
+
parentCircularRef?: string;
|
|
214
|
+
}
|
|
215
|
+
) {
|
|
216
|
+
const id = useId();
|
|
217
|
+
const { schema, circularRefs, context, alternatives, parentCircularRef } = props;
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<InteractiveSection id={id} className={clsx('openapi-schema')}>
|
|
221
|
+
<OpenAPISchemaPresentation {...props} />
|
|
222
|
+
{alternatives[0].map((alternative, index) => (
|
|
223
|
+
<OpenAPISchemaAlternative
|
|
224
|
+
key={`alternative-${index}`}
|
|
225
|
+
schema={alternative}
|
|
226
|
+
circularRefs={circularRefs}
|
|
227
|
+
context={context}
|
|
228
|
+
/>
|
|
229
|
+
))}
|
|
230
|
+
{parentCircularRef ? (
|
|
231
|
+
<OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
|
|
232
|
+
) : null}
|
|
233
|
+
</InteractiveSection>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
196
237
|
/**
|
|
197
238
|
* Render a circular reference to a schema.
|
|
198
239
|
*/
|
|
@@ -336,13 +377,18 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
|
|
|
336
377
|
return null;
|
|
337
378
|
}
|
|
338
379
|
|
|
380
|
+
type OpenAPISchemaAlternatives = [
|
|
381
|
+
OpenAPIV3.SchemaObject[],
|
|
382
|
+
OpenAPIV3.DiscriminatorObject | undefined,
|
|
383
|
+
];
|
|
384
|
+
|
|
339
385
|
/**
|
|
340
386
|
* Get the alternatives to display for a schema.
|
|
341
387
|
*/
|
|
342
388
|
export function getSchemaAlternatives(
|
|
343
389
|
schema: OpenAPIV3.SchemaObject,
|
|
344
|
-
ancestors: Set<OpenAPIV3.SchemaObject> = new Set()
|
|
345
|
-
): null |
|
|
390
|
+
ancestors: Set<OpenAPIV3.SchemaObject> = new Set()
|
|
391
|
+
): null | OpenAPISchemaAlternatives {
|
|
346
392
|
const downAncestors = new Set(ancestors).add(schema);
|
|
347
393
|
|
|
348
394
|
if (schema.anyOf) {
|
|
@@ -363,14 +409,16 @@ export function getSchemaAlternatives(
|
|
|
363
409
|
function flattenAlternatives(
|
|
364
410
|
alternativeType: 'oneOf' | 'allOf' | 'anyOf',
|
|
365
411
|
alternatives: OpenAPIV3.SchemaObject[],
|
|
366
|
-
ancestors: Set<OpenAPIV3.SchemaObject
|
|
412
|
+
ancestors: Set<OpenAPIV3.SchemaObject>
|
|
367
413
|
): OpenAPIV3.SchemaObject[] {
|
|
368
414
|
return alternatives.reduce((acc, alternative) => {
|
|
369
415
|
if (!!alternative[alternativeType] && !ancestors.has(alternative)) {
|
|
370
|
-
|
|
416
|
+
acc.push(...(getSchemaAlternatives(alternative, ancestors)?.[0] || []));
|
|
417
|
+
} else {
|
|
418
|
+
acc.push(alternative);
|
|
371
419
|
}
|
|
372
420
|
|
|
373
|
-
return
|
|
421
|
+
return acc;
|
|
374
422
|
}, [] as OpenAPIV3.SchemaObject[]);
|
|
375
423
|
}
|
|
376
424
|
|
|
@@ -378,7 +426,7 @@ export function getSchemaTitle(
|
|
|
378
426
|
schema: OpenAPIV3.SchemaObject,
|
|
379
427
|
|
|
380
428
|
/** If the title is inferred in a oneOf with discriminator, we can use it to optimize the title */
|
|
381
|
-
discriminator?: OpenAPIV3.DiscriminatorObject
|
|
429
|
+
discriminator?: OpenAPIV3.DiscriminatorObject
|
|
382
430
|
): string {
|
|
383
431
|
// Try using the discriminator
|
|
384
432
|
if (discriminator?.propertyName && schema.properties) {
|
|
@@ -406,7 +454,9 @@ export function getSchemaTitle(
|
|
|
406
454
|
if (schema.format) {
|
|
407
455
|
type += ` · ${schema.format}`;
|
|
408
456
|
}
|
|
409
|
-
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if ('anyOf' in schema) {
|
|
410
460
|
type = 'any of';
|
|
411
461
|
} else if ('oneOf' in schema) {
|
|
412
462
|
type = 'one of';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
1
|
+
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
|
+
import type React from 'react';
|
|
2
3
|
|
|
3
4
|
interface OpenAPISchemaNameProps {
|
|
4
5
|
schema?: OpenAPIV3.SchemaObject;
|
|
5
|
-
propertyName?: string | JSX.Element;
|
|
6
|
+
propertyName?: string | React.JSX.Element;
|
|
6
7
|
required?: boolean;
|
|
7
8
|
type?: string;
|
|
8
9
|
}
|
|
@@ -11,7 +12,7 @@ interface OpenAPISchemaNameProps {
|
|
|
11
12
|
* Display the schema name row.
|
|
12
13
|
* It includes the property name, type, required and deprecated status.
|
|
13
14
|
*/
|
|
14
|
-
export function OpenAPISchemaName(props: OpenAPISchemaNameProps)
|
|
15
|
+
export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
|
|
15
16
|
const { schema, type, propertyName, required } = props;
|
|
16
17
|
|
|
17
18
|
const additionalItems = schema && getAdditionalItems(schema);
|
|
@@ -52,7 +53,7 @@ function getAdditionalItems(schema: OpenAPIV3.SchemaObject): string {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
if (schema.nullable) {
|
|
55
|
-
additionalItems =
|
|
56
|
+
additionalItems = ' | nullable';
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
return additionalItems;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
|
2
|
-
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
|
3
2
|
import { InteractiveSection } from './InteractiveSection';
|
|
4
3
|
import { Markdown } from './Markdown';
|
|
5
4
|
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
|
5
|
+
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
|
6
6
|
import { resolveDescription } from './utils';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -65,7 +65,7 @@ function getLabelForType(security: OpenAPIV3_1.SecuritySchemeObject) {
|
|
|
65
65
|
return <OpenAPISchemaName propertyName="Authorization" type="string" required />;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
if (security.scheme
|
|
68
|
+
if (security.scheme === 'bearer') {
|
|
69
69
|
const description = resolveDescription(security);
|
|
70
70
|
return (
|
|
71
71
|
<>
|
|
@@ -73,7 +73,7 @@ function getLabelForType(security: OpenAPIV3_1.SecuritySchemeObject) {
|
|
|
73
73
|
{/** Show a default description if none is provided */}
|
|
74
74
|
{!description ? (
|
|
75
75
|
<Markdown
|
|
76
|
-
source={`Bearer authentication header of the form Bearer ${
|
|
76
|
+
source={`Bearer authentication header of the form Bearer ${'<token>'}.`}
|
|
77
77
|
className="openapi-securities-description"
|
|
78
78
|
/>
|
|
79
79
|
) : null}
|
package/src/OpenAPISpec.tsx
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import type { OpenAPI } from '@gitbook/openapi-parser';
|
|
4
2
|
|
|
5
|
-
import { InteractiveSection } from './InteractiveSection';
|
|
6
3
|
import { OpenAPIRequestBody } from './OpenAPIRequestBody';
|
|
7
4
|
import { OpenAPIResponses } from './OpenAPIResponses';
|
|
8
5
|
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
|
9
6
|
import { OpenAPISecurities } from './OpenAPISecurities';
|
|
7
|
+
import { StaticSection } from './StaticSection';
|
|
10
8
|
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
|
11
9
|
import { parameterToProperty } from './utils';
|
|
12
10
|
|
|
@@ -32,7 +30,7 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
|
|
|
32
30
|
|
|
33
31
|
{parameterGroups.map((group) => {
|
|
34
32
|
return (
|
|
35
|
-
<
|
|
33
|
+
<StaticSection
|
|
36
34
|
key={group.key}
|
|
37
35
|
className="openapi-parameters"
|
|
38
36
|
header={group.label}
|
|
@@ -41,7 +39,7 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
|
|
|
41
39
|
properties={group.parameters.map(parameterToProperty)}
|
|
42
40
|
context={context}
|
|
43
41
|
/>
|
|
44
|
-
</
|
|
42
|
+
</StaticSection>
|
|
45
43
|
);
|
|
46
44
|
})}
|
|
47
45
|
|
package/src/OpenAPITabs.tsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
4
|
-
import { Key, Tab, TabList, TabPanel, Tabs, TabsProps } from 'react-aria-components';
|
|
3
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { type Key, Tab, TabList, TabPanel, Tabs, type TabsProps } from 'react-aria-components';
|
|
5
|
+
import { useEventCallback } from 'usehooks-ts';
|
|
5
6
|
import { Markdown } from './Markdown';
|
|
6
|
-
import {
|
|
7
|
-
import { useIntersectionObserver } from 'usehooks-ts';
|
|
7
|
+
import { getOrCreateTabStoreByKey } from './useSyncedTabsGlobalState';
|
|
8
8
|
|
|
9
|
-
export type
|
|
9
|
+
export type TabItem = {
|
|
10
10
|
key: Key;
|
|
11
11
|
label: string;
|
|
12
12
|
body: React.ReactNode;
|
|
@@ -14,8 +14,8 @@ export type Tab = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
type OpenAPITabsContextData = {
|
|
17
|
-
items:
|
|
18
|
-
selectedTab:
|
|
17
|
+
items: TabItem[];
|
|
18
|
+
selectedTab: TabItem | null;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const OpenAPITabsContext = createContext<OpenAPITabsContextData | null>(null);
|
|
@@ -32,71 +32,57 @@ function useOpenAPITabsContext() {
|
|
|
32
32
|
* The OpenAPI Tabs wrapper component.
|
|
33
33
|
*/
|
|
34
34
|
export function OpenAPITabs(
|
|
35
|
-
props: React.PropsWithChildren<TabsProps & { items:
|
|
35
|
+
props: React.PropsWithChildren<TabsProps & { items: TabItem[]; stateKey?: string }>
|
|
36
36
|
) {
|
|
37
37
|
const { children, items, stateKey } = props;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const [syncedTabs, setSyncedTabs] = useSyncedTabsGlobalState<Tab>();
|
|
46
|
-
const [selectedTabKey, setSelectedTabKey] = useState(() => {
|
|
47
|
-
if (isVisible && stateKey && syncedTabs && syncedTabs.has(stateKey)) {
|
|
48
|
-
const tabFromState = syncedTabs.get(stateKey);
|
|
49
|
-
return tabFromState?.key ?? items[0]?.key;
|
|
38
|
+
const [tabKey, setTabKey] = useState<Key | null>(() => {
|
|
39
|
+
if (stateKey && typeof window !== 'undefined') {
|
|
40
|
+
const store = getOrCreateTabStoreByKey(stateKey);
|
|
41
|
+
const tabKey = store.getState().tabKey;
|
|
42
|
+
if (tabKey) {
|
|
43
|
+
return tabKey;
|
|
44
|
+
}
|
|
50
45
|
}
|
|
51
|
-
return items[0]?.key;
|
|
46
|
+
return items[0]?.key ?? null;
|
|
52
47
|
});
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
setSelectedTabKey(key);
|
|
57
|
-
if (stateKey) {
|
|
58
|
-
const tab = items.find((item) => item.key === key);
|
|
59
|
-
|
|
60
|
-
if (!tab) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
setSyncedTabs((state) => {
|
|
65
|
-
const newState = new Map(state);
|
|
66
|
-
newState.set(stateKey, tab);
|
|
67
|
-
return newState;
|
|
68
|
-
});
|
|
48
|
+
const selectTab = useEventCallback((key: Key | null) => {
|
|
49
|
+
if (!key || key === tabKey) {
|
|
50
|
+
return;
|
|
69
51
|
}
|
|
70
|
-
|
|
71
|
-
|
|
52
|
+
const tab = items.find((item) => item.key === key);
|
|
53
|
+
if (!tab) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setTabKey(key);
|
|
57
|
+
});
|
|
58
|
+
const selectedTab = items.find((item) => item.key === tabKey) ?? items[0] ?? null;
|
|
59
|
+
const cancelDeferRef = useRef<(() => void) | null>(null);
|
|
72
60
|
useEffect(() => {
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!items.some((item) => item.key === tabFromState?.key)) {
|
|
77
|
-
return setSelectedTab(defaultTab);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (tabFromState && tabFromState?.key !== selectedTab?.key) {
|
|
81
|
-
const tabFromItems = items.find((item) => item.key === tabFromState.key);
|
|
82
|
-
|
|
83
|
-
if (!tabFromItems) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
setSelectedTab(tabFromItems);
|
|
88
|
-
}
|
|
61
|
+
if (!stateKey) {
|
|
62
|
+
return undefined;
|
|
89
63
|
}
|
|
90
|
-
|
|
91
|
-
|
|
64
|
+
const store = getOrCreateTabStoreByKey(stateKey);
|
|
65
|
+
return store.subscribe((state) => {
|
|
66
|
+
cancelDeferRef.current?.();
|
|
67
|
+
cancelDeferRef.current = defer(() => selectTab(state.tabKey));
|
|
68
|
+
});
|
|
69
|
+
}, [stateKey, selectTab]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
return () => cancelDeferRef.current?.();
|
|
72
|
+
}, []);
|
|
92
73
|
const contextValue = useMemo(() => ({ items, selectedTab }), [items, selectedTab]);
|
|
93
|
-
|
|
94
74
|
return (
|
|
95
75
|
<OpenAPITabsContext.Provider value={contextValue}>
|
|
96
76
|
<Tabs
|
|
97
77
|
className="openapi-tabs"
|
|
98
|
-
onSelectionChange={
|
|
99
|
-
|
|
78
|
+
onSelectionChange={(tabKey) => {
|
|
79
|
+
selectTab(tabKey);
|
|
80
|
+
if (stateKey) {
|
|
81
|
+
const store = getOrCreateTabStoreByKey(stateKey);
|
|
82
|
+
store.setState({ tabKey });
|
|
83
|
+
}
|
|
84
|
+
}}
|
|
85
|
+
selectedKey={tabKey}
|
|
100
86
|
>
|
|
101
87
|
{children}
|
|
102
88
|
</Tabs>
|
|
@@ -104,6 +90,11 @@ export function OpenAPITabs(
|
|
|
104
90
|
);
|
|
105
91
|
}
|
|
106
92
|
|
|
93
|
+
const defer = (fn: () => void) => {
|
|
94
|
+
const id = setTimeout(fn, 0);
|
|
95
|
+
return () => clearTimeout(id);
|
|
96
|
+
};
|
|
97
|
+
|
|
107
98
|
/**
|
|
108
99
|
* The OpenAPI Tabs list component.
|
|
109
100
|
* This component should be used as a child of the OpenAPITabs component.
|
|
@@ -116,14 +107,14 @@ export function OpenAPITabsList() {
|
|
|
116
107
|
<TabList className="openapi-tabs-list">
|
|
117
108
|
{items.map((tab) => (
|
|
118
109
|
<Tab
|
|
110
|
+
key={tab.key}
|
|
111
|
+
id={tab.key}
|
|
119
112
|
style={({ isFocusVisible }) => ({
|
|
120
113
|
outline: isFocusVisible
|
|
121
114
|
? '2px solid rgb(var(--primary-color-500)/0.4)'
|
|
122
115
|
: 'none',
|
|
123
116
|
})}
|
|
124
117
|
className="openapi-tabs-tab"
|
|
125
|
-
key={`Tab-${tab.key}`}
|
|
126
|
-
id={tab.key}
|
|
127
118
|
>
|
|
128
119
|
{tab.label}
|
|
129
120
|
</Tab>
|
|
@@ -144,12 +135,10 @@ export function OpenAPITabsPanels() {
|
|
|
144
135
|
return null;
|
|
145
136
|
}
|
|
146
137
|
|
|
138
|
+
const key = selectedTab.key.toString();
|
|
139
|
+
|
|
147
140
|
return (
|
|
148
|
-
<TabPanel
|
|
149
|
-
key={`TabPanel-${selectedTab.key}`}
|
|
150
|
-
id={selectedTab.key.toString()}
|
|
151
|
-
className="openapi-tabs-panel"
|
|
152
|
-
>
|
|
141
|
+
<TabPanel key={key} id={key} className="openapi-tabs-panel">
|
|
153
142
|
{selectedTab.body}
|
|
154
143
|
{selectedTab.description ? (
|
|
155
144
|
<Markdown source={selectedTab.description} className="openapi-tabs-footer" />
|
package/src/ScalarApiButton.tsx
CHANGED
|
@@ -4,8 +4,8 @@ import { ApiClientModalProvider, useApiClientModal } from '@scalar/api-client-re
|
|
|
4
4
|
import { useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
|
|
7
|
-
import { useOpenAPIOperationContext } from './OpenAPIOperationContext';
|
|
8
7
|
import { useEventCallback } from 'usehooks-ts';
|
|
8
|
+
import { useOpenAPIOperationContext } from './OpenAPIOperationContext';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Button which launches the Scalar API Client
|
|
@@ -48,7 +48,7 @@ export function ScalarApiButton({
|
|
|
48
48
|
path={path}
|
|
49
49
|
specUrl={specUrl}
|
|
50
50
|
/>,
|
|
51
|
-
document.body
|
|
51
|
+
document.body
|
|
52
52
|
)}
|
|
53
53
|
</div>
|
|
54
54
|
);
|
|
@@ -88,7 +88,7 @@ function ScalarModalController(props: {
|
|
|
88
88
|
useImperativeHandle(
|
|
89
89
|
props.controllerRef,
|
|
90
90
|
() => ({ openClient: openClient ? () => openClient() : undefined }),
|
|
91
|
-
[openClient]
|
|
91
|
+
[openClient]
|
|
92
92
|
);
|
|
93
93
|
|
|
94
94
|
// Open the client when the component is mounted.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { type ComponentPropsWithoutRef, forwardRef } from 'react';
|
|
3
|
+
|
|
4
|
+
export function Section(props: ComponentPropsWithoutRef<'div'>) {
|
|
5
|
+
return <div {...props} className={clsx('openapi-section', props.className)} />;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function SectionHeader(props: ComponentPropsWithoutRef<'div'>) {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
{...props}
|
|
12
|
+
className={clsx(
|
|
13
|
+
'openapi-section-header',
|
|
14
|
+
props.className && `${props.className}-header`
|
|
15
|
+
)}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function SectionHeaderContent(props: ComponentPropsWithoutRef<'div'>) {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
{...props}
|
|
24
|
+
className={clsx(
|
|
25
|
+
'openapi-section-header-content',
|
|
26
|
+
props.className && `${props.className}-header-content`
|
|
27
|
+
)}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const SectionBody = forwardRef(function SectionBody(
|
|
33
|
+
props: ComponentPropsWithoutRef<'div'>,
|
|
34
|
+
ref: React.ForwardedRef<HTMLDivElement>
|
|
35
|
+
) {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
ref={ref}
|
|
39
|
+
{...props}
|
|
40
|
+
className={clsx('openapi-section-body', props.className && `${props.className}-body`)}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export function StaticSection(props: {
|
|
46
|
+
className: string;
|
|
47
|
+
header: React.ReactNode;
|
|
48
|
+
children: React.ReactNode;
|
|
49
|
+
}) {
|
|
50
|
+
const { className, header, children } = props;
|
|
51
|
+
return (
|
|
52
|
+
<Section className={className}>
|
|
53
|
+
<SectionHeader className={className}>
|
|
54
|
+
<SectionHeaderContent className={className}>{header}</SectionHeaderContent>
|
|
55
|
+
</SectionHeader>
|
|
56
|
+
<SectionBody className={className}>{children}</SectionBody>
|
|
57
|
+
</Section>
|
|
58
|
+
);
|
|
59
|
+
}
|