@gitbook/react-openapi 1.5.1 → 1.5.3

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 (87) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/InteractiveSection.js +59 -0
  3. package/dist/Markdown.js +10 -0
  4. package/dist/OpenAPICodeSample.js +219 -0
  5. package/dist/OpenAPICodeSampleInteractive.js +62 -0
  6. package/dist/OpenAPICodeSampleSelector.js +45 -0
  7. package/dist/OpenAPICopyButton.js +44 -0
  8. package/dist/OpenAPIDisclosure.js +28 -0
  9. package/dist/OpenAPIDisclosureGroup.js +89 -0
  10. package/dist/OpenAPIExample.js +41 -0
  11. package/dist/OpenAPIMediaType.js +58 -0
  12. package/dist/OpenAPIOperation.d.ts +15 -0
  13. package/dist/OpenAPIOperation.js +30 -0
  14. package/dist/OpenAPIOperationContext.d.ts +20 -0
  15. package/dist/OpenAPIOperationContext.js +30 -0
  16. package/dist/OpenAPIPath.js +25 -0
  17. package/dist/OpenAPIPathItem.js +22 -0
  18. package/dist/OpenAPIPathMultipleServers.js +43 -0
  19. package/dist/OpenAPIPrefillContextProvider.d.ts +26 -0
  20. package/dist/OpenAPIPrefillContextProvider.js +25 -0
  21. package/dist/OpenAPIRequestBody.js +28 -0
  22. package/dist/OpenAPIRequestBodyHeaderType.js +23 -0
  23. package/dist/OpenAPIRequiredScopes.js +67 -0
  24. package/dist/OpenAPIResponse.js +39 -0
  25. package/dist/OpenAPIResponseExample.js +75 -0
  26. package/dist/OpenAPIResponseExampleContent.js +61 -0
  27. package/dist/OpenAPIResponses.js +61 -0
  28. package/dist/OpenAPISchema.js +373 -0
  29. package/dist/OpenAPISchemaName.js +45 -0
  30. package/dist/OpenAPISchemaServer.js +13 -0
  31. package/dist/OpenAPISecurities.js +98 -0
  32. package/dist/OpenAPISelect.js +45 -0
  33. package/dist/OpenAPISpec.js +73 -0
  34. package/dist/OpenAPITooltip.js +23 -0
  35. package/dist/OpenAPIWebhook.d.ts +15 -0
  36. package/dist/OpenAPIWebhook.js +28 -0
  37. package/dist/OpenAPIWebhookExample.js +40 -0
  38. package/dist/ScalarApiButton.js +90 -0
  39. package/dist/StaticSection.js +37 -0
  40. package/dist/code-samples.js +305 -0
  41. package/dist/common/OpenAPIColumnSpec.js +23 -0
  42. package/dist/common/OpenAPIOperationDescription.js +18 -0
  43. package/dist/common/OpenAPIStability.js +17 -0
  44. package/dist/common/OpenAPISummary.js +27 -0
  45. package/dist/contentTypeChecks.js +34 -0
  46. package/dist/context.d.ts +74 -0
  47. package/dist/context.js +29 -0
  48. package/dist/decycle.js +41 -0
  49. package/dist/dereference.js +24 -0
  50. package/dist/formatPath.js +25 -0
  51. package/dist/generateSchemaExample.js +198 -0
  52. package/dist/getDisclosureLabel.js +17 -0
  53. package/dist/getOrCreateDisclosureStoreByKey.js +31 -0
  54. package/dist/getOrCreateStoreByKey.js +22 -0
  55. package/dist/index.d.ts +11 -662
  56. package/dist/index.js +9 -3871
  57. package/dist/json2xml.js +12 -0
  58. package/dist/resolveOpenAPIOperation.d.ts +15 -0
  59. package/dist/resolveOpenAPIOperation.js +105 -0
  60. package/dist/resolveOpenAPIWebhook.d.ts +15 -0
  61. package/dist/resolveOpenAPIWebhook.js +52 -0
  62. package/dist/schemas/OpenAPISchemaItem.js +26 -0
  63. package/dist/schemas/OpenAPISchemas.d.ts +19 -0
  64. package/dist/schemas/OpenAPISchemas.js +57 -0
  65. package/dist/schemas/resolveOpenAPISchemas.d.ts +15 -0
  66. package/dist/schemas/resolveOpenAPISchemas.js +17 -0
  67. package/dist/stringifyOpenAPI.js +14 -0
  68. package/dist/translate.js +43 -0
  69. package/dist/translations/de.js +50 -0
  70. package/dist/translations/en.d.ts +49 -0
  71. package/dist/translations/en.js +50 -0
  72. package/dist/translations/es.js +50 -0
  73. package/dist/translations/fr.js +50 -0
  74. package/dist/translations/index.d.ts +426 -0
  75. package/dist/translations/index.js +31 -0
  76. package/dist/translations/ja.js +50 -0
  77. package/dist/translations/nl.js +50 -0
  78. package/dist/translations/no.js +50 -0
  79. package/dist/translations/pt-br.js +50 -0
  80. package/dist/translations/types.d.ts +7 -0
  81. package/dist/translations/zh.js +50 -0
  82. package/dist/types.d.ts +38 -0
  83. package/dist/util/example.js +84 -0
  84. package/dist/util/server.js +38 -0
  85. package/dist/util/tryit-prefill.js +143 -0
  86. package/dist/utils.js +163 -0
  87. package/package.json +28 -12
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @gitbook/react-openapi
2
2
 
3
+ ## 1.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - b4a021a: Add heredoc support for cURL JSON body
8
+ - a512c90: Re-arrange OpenAPI Scopes for OAuth2
9
+ - df1966d: Bump Scalar
10
+ - b45feaf: Disable OpenAPI "Try it" when no servers are defined
11
+ - 10995e0: Use NPM Trusted publishing for publishing the package.
12
+ - f9f8011: Add alt text support to card covers
13
+ - 8ce7322: Add OpenAPI servers selection
14
+ - 2c3066e: Improve OAuth2 scopes handling in OpenAPI
15
+ - Updated dependencies [df1966d]
16
+ - Updated dependencies [10995e0]
17
+ - Updated dependencies [10995e0]
18
+ - @gitbook/openapi-parser@3.0.5
19
+ - @gitbook/expr@1.2.4
20
+
21
+ ## 1.5.2
22
+
23
+ ### Patch Changes
24
+
25
+ - 6142d6b: Mark as sideEffects, fix all package bundles
26
+ - Updated dependencies [6142d6b]
27
+ - @gitbook/openapi-parser@3.0.4
28
+ - @gitbook/expr@1.2.3
29
+
3
30
  ## 1.5.1
4
31
 
5
32
  ### Patch Changes
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+
3
+
4
+ import { Section, SectionBody, SectionHeader, SectionHeaderContent } from "./StaticSection.js";
5
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
6
+ import clsx from "classnames";
7
+ import { useRef } from "react";
8
+ import { mergeProps, useButton, useDisclosure, useFocusRing } from "react-aria";
9
+ import { useDisclosureState } from "react-stately";
10
+
11
+ //#region src/InteractiveSection.tsx
12
+ /**
13
+ * To optimize rendering, most of the components are server-components,
14
+ * and the interactiveness is mainly handled by a few key components like this one.
15
+ */
16
+ function InteractiveSection(props) {
17
+ const { id, className, toggeable = false, defaultOpened = true, tabs = [], defaultTab = tabs[0]?.key, header, overlay, toggleIcon = "▶", selectIcon, stateKey = "interactive-section" } = props;
18
+ const state = useDisclosureState({ defaultExpanded: defaultOpened });
19
+ const panelRef = useRef(null);
20
+ const triggerRef = useRef(null);
21
+ const { buttonProps: triggerProps, panelProps } = useDisclosure({}, state, panelRef);
22
+ const { buttonProps } = useButton(triggerProps, triggerRef);
23
+ const { isFocusVisible, focusProps } = useFocusRing();
24
+ const store = useSelectState(stateKey, defaultTab);
25
+ const selectedTab = tabs.find((tab) => tab.key === store.key) ?? tabs[0];
26
+ return <Section id={id} className={clsx("openapi-section", toggeable ? "openapi-section-toggeable" : null, className, toggeable ? `${className}-${state.isExpanded ? "opened" : "closed"}` : null)}>
27
+ {header ? <SectionHeader onClick={() => {
28
+ if (toggeable) state.toggle();
29
+ }} className={className}>
30
+ <SectionHeaderContent className={className}>
31
+ {selectedTab?.body && toggeable ? <button {...mergeProps(buttonProps, focusProps)} ref={triggerRef} className={clsx("openapi-section-toggle", `${className}-toggle`)} style={{ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500) / 0.4)" : "none" }}>
32
+ {toggleIcon}
33
+ </button> : null}
34
+ {header}
35
+ </SectionHeaderContent>
36
+ {}
37
+ <div className={clsx("openapi-section-header-controls", `${className}-header-controls`)} onClick={(event) => {
38
+ event.stopPropagation();
39
+ }}>
40
+ {tabs.length > 0 ? <OpenAPISelect stateKey={stateKey} items={tabs} onChange={() => {
41
+ state.expand();
42
+ }} icon={selectIcon} placement="bottom end">
43
+ {tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
44
+ {tab.label}
45
+ </OpenAPISelectItem>)}
46
+ </OpenAPISelect> : null}
47
+ </div>
48
+ </SectionHeader> : null}
49
+ {(!toggeable || state.isExpanded) && selectedTab?.body ? <SectionBody ref={panelRef} {...panelProps} className={className}>
50
+ {selectedTab?.body}
51
+ </SectionBody> : null}
52
+ {overlay ? <div className={clsx("openapi-section-overlay", `${className}-overlay`)}>
53
+ {overlay}
54
+ </div> : null}
55
+ </Section>;
56
+ }
57
+
58
+ //#endregion
59
+ export { InteractiveSection };
@@ -0,0 +1,10 @@
1
+ import clsx from "classnames";
2
+
3
+ //#region src/Markdown.tsx
4
+ function Markdown(props) {
5
+ const { source, className } = props;
6
+ return <div className={clsx("openapi-markdown", className)} dangerouslySetInnerHTML={{ __html: source }} />;
7
+ }
8
+
9
+ //#endregion
10
+ export { Markdown };
@@ -0,0 +1,219 @@
1
+ import { stringifyOpenAPI } from "./stringifyOpenAPI.js";
2
+ import { checkIsReference, extractOperationSecurityInfo } from "./utils.js";
3
+ import { getOpenAPIClientContext } from "./context.js";
4
+ import { generateMediaTypeExamples, generateSchemaExample } from "./generateSchemaExample.js";
5
+ import { OpenAPIMediaTypeExamplesBody, OpenAPIMediaTypeExamplesSelector } from "./OpenAPICodeSampleInteractive.js";
6
+ import { getDefaultServerURL } from "./util/server.js";
7
+ import { OpenAPICodeSampleBody } from "./OpenAPICodeSampleSelector.js";
8
+ import { resolvePrefillCodePlaceholderFromSecurityScheme, resolveURLWithPrefillCodePlaceholdersFromServer } from "./util/tryit-prefill.js";
9
+ import { ScalarApiButton } from "./ScalarApiButton.js";
10
+ import { codeSampleGenerators, parseHostAndPath } from "./code-samples.js";
11
+
12
+ //#region src/OpenAPICodeSample.tsx
13
+ const CUSTOM_CODE_SAMPLES_KEYS = [
14
+ "x-custom-examples",
15
+ "x-code-samples",
16
+ "x-codeSamples"
17
+ ];
18
+ /**
19
+ * Display code samples to execute the operation.
20
+ * It supports the Redocly custom syntax as well (https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples/)
21
+ */
22
+ function OpenAPICodeSample(props) {
23
+ const { data, context } = props;
24
+ if (data.operation["x-codeSamples"] === false) return null;
25
+ const customCodeSamples = getCustomCodeSamples(props);
26
+ if (data["x-codeSamples"] === false && !customCodeSamples) return null;
27
+ const samples = customCodeSamples ?? generateCodeSamples(props);
28
+ if (samples.length === 0) return null;
29
+ return <OpenAPICodeSampleBody context={getOpenAPIClientContext(context)} data={data} items={samples} selectIcon={context.icons.chevronDown} />;
30
+ }
31
+ /**
32
+ * Generate code samples for the operation.
33
+ */
34
+ function generateCodeSamples(props) {
35
+ const { data, context } = props;
36
+ const searchParams = new URLSearchParams();
37
+ const headersObject = {};
38
+ (Array.isArray(data.operation.parameters) ? data.operation.parameters : []).forEach((param) => {
39
+ if (!param) return;
40
+ if (param.in === "header" && param.required) {
41
+ const example = param.schema ? generateSchemaExample(param.schema, { mode: "write" }) : void 0;
42
+ if (example !== void 0 && param.name) headersObject[param.name] = typeof example !== "string" ? stringifyOpenAPI(example) : example;
43
+ } else if (param.in === "query" && param.required) {
44
+ const example = param.schema ? generateSchemaExample(param.schema, { mode: "write" }) : void 0;
45
+ if (example !== void 0 && param.name) searchParams.append(param.name, String(Array.isArray(example) ? example[0] : example));
46
+ }
47
+ });
48
+ const requestBody = !checkIsReference(data.operation.requestBody) ? data.operation.requestBody : void 0;
49
+ const defaultServerUrl = getDefaultServerURL(data.servers);
50
+ let serverUrlPath = defaultServerUrl ? parseHostAndPath(defaultServerUrl).path : "";
51
+ serverUrlPath = serverUrlPath === "/" ? "" : serverUrlPath;
52
+ const serverUrlOrigin = (data.servers[0] ? resolveURLWithPrefillCodePlaceholdersFromServer(data.servers[0], defaultServerUrl) : defaultServerUrl).replaceAll(serverUrlPath, "");
53
+ const path = serverUrlPath + data.path + (searchParams.size ? `?${searchParams.toString()}` : "");
54
+ const genericHeaders = {
55
+ ...getSecurityHeaders({
56
+ securityRequirement: data.operation.security,
57
+ securities: data.securities
58
+ }),
59
+ ...headersObject
60
+ };
61
+ const mediaTypeRendererFactories = Object.entries(requestBody?.content ?? {}).map(([mediaType, mediaTypeObject]) => {
62
+ return (generator) => {
63
+ const mediaTypeHeaders = {
64
+ ...genericHeaders,
65
+ "Content-Type": mediaType
66
+ };
67
+ return {
68
+ mediaType,
69
+ element: context.renderCodeBlock({
70
+ code: generator.generate({
71
+ url: {
72
+ origin: serverUrlOrigin,
73
+ path
74
+ },
75
+ method: data.method,
76
+ body: void 0,
77
+ headers: mediaTypeHeaders
78
+ }),
79
+ syntax: generator.syntax
80
+ }),
81
+ examples: generateMediaTypeExamples(mediaTypeObject, { mode: "write" }).map((example) => ({
82
+ example,
83
+ element: context.renderCodeBlock({
84
+ code: generator.generate({
85
+ url: {
86
+ origin: serverUrlOrigin,
87
+ path
88
+ },
89
+ method: data.method,
90
+ body: example.value,
91
+ headers: mediaTypeHeaders
92
+ }),
93
+ syntax: generator.syntax
94
+ })
95
+ }))
96
+ };
97
+ };
98
+ });
99
+ return codeSampleGenerators.map((generator) => {
100
+ if (mediaTypeRendererFactories.length > 0) {
101
+ const renderers = mediaTypeRendererFactories.map((generate) => generate(generator));
102
+ return {
103
+ key: `default-${generator.id}`,
104
+ label: generator.label,
105
+ body: <OpenAPIMediaTypeExamplesBody method={data.method} path={data.path} renderers={renderers} blockKey={context.blockKey} />,
106
+ footer: <OpenAPICodeSampleFooter renderers={renderers} data={data} context={context} />
107
+ };
108
+ }
109
+ return {
110
+ key: `default-${generator.id}`,
111
+ label: generator.label,
112
+ body: context.renderCodeBlock({
113
+ code: generator.generate({
114
+ url: {
115
+ origin: serverUrlOrigin,
116
+ path
117
+ },
118
+ method: data.method,
119
+ body: void 0,
120
+ headers: genericHeaders
121
+ }),
122
+ syntax: generator.syntax
123
+ }),
124
+ footer: <OpenAPICodeSampleFooter data={data} renderers={[]} context={context} />
125
+ };
126
+ });
127
+ }
128
+ function OpenAPICodeSampleFooter(props) {
129
+ const { data, context, renderers } = props;
130
+ const { method, path, securities, servers } = data;
131
+ const { specUrl } = context;
132
+ const hideTryItPanel = data["x-hideTryItPanel"] || data.operation["x-hideTryItPanel"];
133
+ const hasMultipleMediaTypes = renderers.length > 1 || renderers.some((renderer) => renderer.examples.length > 0);
134
+ if (hideTryItPanel && !hasMultipleMediaTypes) return null;
135
+ if (!validateHttpMethod(method) || !hasMultipleMediaTypes && servers.length === 0) return null;
136
+ return <div className="openapi-codesample-footer">
137
+ {hasMultipleMediaTypes ? <OpenAPIMediaTypeExamplesSelector method={data.method} path={data.path} renderers={renderers} selectIcon={context.icons.chevronDown} blockKey={context.blockKey} /> : <span />}
138
+ {!hideTryItPanel && servers.length > 0 && <ScalarApiButton context={getOpenAPIClientContext(context)} method={method} path={path} securities={securities} servers={servers} specUrl={specUrl} />}
139
+ </div>;
140
+ }
141
+ /**
142
+ * Get custom code samples for the operation.
143
+ */
144
+ function getCustomCodeSamples(props) {
145
+ const { data, context } = props;
146
+ let customCodeSamples = null;
147
+ CUSTOM_CODE_SAMPLES_KEYS.forEach((key) => {
148
+ const customSamples = data.operation[key];
149
+ if (customSamples && Array.isArray(customSamples)) customCodeSamples = customSamples.filter((sample) => {
150
+ return typeof sample.source === "string" && typeof sample.lang === "string";
151
+ }).map((sample, index) => ({
152
+ key: `custom-sample-${sample.lang}-${index}`,
153
+ label: sample.label || sample.lang,
154
+ body: context.renderCodeBlock({
155
+ code: sample.source,
156
+ syntax: sample.lang
157
+ }),
158
+ footer: <OpenAPICodeSampleFooter renderers={[]} data={data} context={context} />
159
+ }));
160
+ });
161
+ return customCodeSamples;
162
+ }
163
+ function getSecurityHeaders(args) {
164
+ const { securityRequirement, securities } = args;
165
+ const operationSecurityInfo = extractOperationSecurityInfo({
166
+ securityRequirement,
167
+ securities
168
+ });
169
+ if (operationSecurityInfo.length === 0) return {};
170
+ const selectedSecurity = operationSecurityInfo.at(0);
171
+ if (!selectedSecurity) return {};
172
+ const headers = {};
173
+ for (const security of selectedSecurity.schemes) switch (security.type) {
174
+ case "http": {
175
+ let scheme = security.scheme;
176
+ const format = resolvePrefillCodePlaceholderFromSecurityScheme({
177
+ security,
178
+ defaultPlaceholderValue: scheme?.includes("basic") ? "username:password" : "YOUR_SECRET_TOKEN"
179
+ });
180
+ if (scheme?.includes("bearer")) scheme = "Bearer";
181
+ else if (scheme?.includes("basic")) scheme = "Basic";
182
+ else if (scheme?.includes("token")) scheme = "Token";
183
+ headers.Authorization = `${scheme} ${format}`;
184
+ break;
185
+ }
186
+ case "apiKey": {
187
+ if (security.in !== "header") break;
188
+ const name = security.name ?? "Authorization";
189
+ headers[name] = resolvePrefillCodePlaceholderFromSecurityScheme({
190
+ security,
191
+ defaultPlaceholderValue: "YOUR_API_KEY"
192
+ });
193
+ break;
194
+ }
195
+ case "oauth2":
196
+ headers.Authorization = `Bearer ${resolvePrefillCodePlaceholderFromSecurityScheme({
197
+ security,
198
+ defaultPlaceholderValue: "YOUR_OAUTH2_TOKEN"
199
+ })}`;
200
+ break;
201
+ default: break;
202
+ }
203
+ return headers;
204
+ }
205
+ function validateHttpMethod(method) {
206
+ return [
207
+ "get",
208
+ "post",
209
+ "put",
210
+ "delete",
211
+ "patch",
212
+ "head",
213
+ "options",
214
+ "trace"
215
+ ].includes(method);
216
+ }
217
+
218
+ //#endregion
219
+ export { OpenAPICodeSample };
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+
4
+ import { createStateKey } from "./utils.js";
5
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
6
+
7
+ //#region src/OpenAPICodeSampleInteractive.tsx
8
+ function OpenAPIMediaTypeExamplesSelector(props) {
9
+ const { method, path, renderers, selectIcon, blockKey } = props;
10
+ if (!renderers[0]) throw new Error("No renderers provided");
11
+ const stateKey = createStateKey("request-body-media-type", blockKey);
12
+ const state = useSelectState(stateKey, renderers[0].mediaType);
13
+ const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
14
+ return <div className="openapi-codesample-selectors">
15
+ <MediaTypeSelector selectIcon={selectIcon} stateKey={stateKey} renderers={renderers} />
16
+ <ExamplesSelector selectIcon={selectIcon} method={method} path={path} renderer={selected} />
17
+ </div>;
18
+ }
19
+ function MediaTypeSelector(props) {
20
+ const { renderers, stateKey, selectIcon } = props;
21
+ if (renderers.length < 2) return null;
22
+ const items = renderers.map((renderer) => ({
23
+ key: renderer.mediaType,
24
+ label: renderer.mediaType
25
+ }));
26
+ return <OpenAPISelect items={items} icon={selectIcon} stateKey={stateKey} placement="bottom start">
27
+ {items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
28
+ {item.label}
29
+ </OpenAPISelectItem>)}
30
+ </OpenAPISelect>;
31
+ }
32
+ function ExamplesSelector(props) {
33
+ const { method, path, renderer, selectIcon } = props;
34
+ if (renderer.examples.length < 2) return null;
35
+ const items = renderer.examples.map((example, index) => ({
36
+ key: index,
37
+ label: example.example.summary || `Example ${index + 1}`
38
+ }));
39
+ return <OpenAPISelect items={items} icon={selectIcon} stateKey={`media-type-sample-${renderer.mediaType}-${method}-${path}`} placement="bottom start">
40
+ {items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
41
+ {item.label}
42
+ </OpenAPISelectItem>)}
43
+ </OpenAPISelect>;
44
+ }
45
+ function OpenAPIMediaTypeExamplesBody(props) {
46
+ const { renderers, method, path, blockKey } = props;
47
+ if (!renderers[0]) throw new Error("No renderers provided");
48
+ const mediaTypeState = useSelectState(createStateKey("request-body-media-type", blockKey), renderers[0].mediaType);
49
+ const selected = renderers.find((r) => r.mediaType === mediaTypeState.key) ?? renderers[0];
50
+ if (selected.examples.length === 0) return selected.element;
51
+ return <ExamplesBody method={method} path={path} renderer={selected} />;
52
+ }
53
+ function ExamplesBody(props) {
54
+ const { method, path, renderer } = props;
55
+ const exampleState = useSelectState(`media-type-sample-${renderer.mediaType}-${method}-${path}`, renderer.mediaType);
56
+ const example = renderer.examples[Number(exampleState.key)] ?? renderer.examples[0];
57
+ if (!example) throw new Error(`No example found for key ${exampleState.key}`);
58
+ return example.element;
59
+ }
60
+
61
+ //#endregion
62
+ export { OpenAPIMediaTypeExamplesBody, OpenAPIMediaTypeExamplesSelector };
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+
4
+ import { StaticSection } from "./StaticSection.js";
5
+ import { getOrCreateStoreByKey } from "./getOrCreateStoreByKey.js";
6
+ import { OpenAPISelect, OpenAPISelectItem } from "./OpenAPISelect.js";
7
+ import { OpenAPIPath } from "./OpenAPIPath.js";
8
+ import { useCallback } from "react";
9
+ import { useStore } from "zustand";
10
+
11
+ //#region src/OpenAPICodeSampleSelector.tsx
12
+ function useCodeSampleState(initialKey = "default") {
13
+ const store = useStore(getOrCreateStoreByKey("codesample", initialKey));
14
+ return {
15
+ key: store.key,
16
+ setKey: useCallback((key) => store.setKey(key), [store.setKey])
17
+ };
18
+ }
19
+ function OpenAPICodeSampleHeader(props) {
20
+ const { data, items, selectIcon, context } = props;
21
+ return <>
22
+ <OpenAPIPath context={context} canCopy={false} withServer={false} data={data} />
23
+ {items.length > 1 ? <OpenAPISelect icon={selectIcon} items={items} stateKey="codesample" placement="bottom end">
24
+ {items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
25
+ {item.label}
26
+ </OpenAPISelectItem>)}
27
+ </OpenAPISelect> : items[0] ? <span className="openapi-codesample-label">{items[0].label}</span> : null}
28
+ </>;
29
+ }
30
+ function OpenAPICodeSampleBody(props) {
31
+ const { items, data, selectIcon, context } = props;
32
+ if (!items[0]) throw new Error("No items provided");
33
+ const state = useCodeSampleState(items[0]?.key);
34
+ const selected = items.find((item) => item.key === state.key) || items[0];
35
+ if (!selected) return null;
36
+ return <StaticSection header={<OpenAPICodeSampleHeader context={context} selectIcon={selectIcon} data={data} items={items} />} className="openapi-codesample">
37
+ <div id={selected.key} className="openapi-codesample-panel">
38
+ {selected.body ? selected.body : null}
39
+ {selected.footer ? selected.footer : null}
40
+ </div>
41
+ </StaticSection>;
42
+ }
43
+
44
+ //#endregion
45
+ export { OpenAPICodeSampleBody };
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+
4
+ import { t } from "./translate.js";
5
+ import { OpenAPITooltip } from "./OpenAPITooltip.js";
6
+ import clsx from "classnames";
7
+ import { useState } from "react";
8
+ import { Button } from "react-aria-components";
9
+
10
+ //#region src/OpenAPICopyButton.tsx
11
+ function OpenAPICopyButton(props) {
12
+ const { value, label, children, onPress, className, context, withTooltip = true } = props;
13
+ const [copied, setCopied] = useState(false);
14
+ const [isOpen, setIsOpen] = useState(false);
15
+ const handleCopy = () => {
16
+ if (!value) return;
17
+ navigator.clipboard.writeText(value).then(() => {
18
+ setIsOpen(true);
19
+ setCopied(true);
20
+ setTimeout(() => {
21
+ setCopied(false);
22
+ setIsOpen(false);
23
+ }, 2e3);
24
+ });
25
+ };
26
+ return <OpenAPITooltip isDisabled={!withTooltip} isOpen={isOpen} onOpenChange={setIsOpen}>
27
+ <Button type="button" preventFocusOnPress onPress={(e) => {
28
+ handleCopy();
29
+ onPress?.(e);
30
+ }} className={clsx("openapi-copy-button", className)} {...props}>
31
+ {children}
32
+ </Button>
33
+
34
+ <OpenAPITooltip.Content isOpen={isOpen} onOpenChange={setIsOpen}>
35
+ {copied ? <>
36
+ {context.icons.check}
37
+ {t(context.translation, "copied")}
38
+ </> : label || t(context.translation, "copy_to_clipboard")}
39
+ </OpenAPITooltip.Content>
40
+ </OpenAPITooltip>;
41
+ }
42
+
43
+ //#endregion
44
+ export { OpenAPICopyButton };
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+
4
+ import clsx from "classnames";
5
+ import { useState } from "react";
6
+ import { Button, Disclosure, DisclosurePanel } from "react-aria-components";
7
+
8
+ //#region src/OpenAPIDisclosure.tsx
9
+ /**
10
+ * Display an interactive OpenAPI disclosure.
11
+ */
12
+ function OpenAPIDisclosure(props) {
13
+ const { icon, header, label, children, className, defaultExpanded = false } = props;
14
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
15
+ return <Disclosure className={clsx("openapi-disclosure", className)} isExpanded={isExpanded} onExpandedChange={setIsExpanded}>
16
+ <Button slot="trigger" className="openapi-disclosure-trigger" style={({ isFocusVisible }) => ({ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500) / 0.4)" : "none" })}>
17
+ {header}
18
+ <div className="openapi-disclosure-trigger-label">
19
+ {label ? <span>{typeof label === "function" ? label(isExpanded) : label}</span> : null}
20
+ {icon}
21
+ </div>
22
+ </Button>
23
+ {isExpanded ? <DisclosurePanel className="openapi-disclosure-panel">{children}</DisclosurePanel> : null}
24
+ </Disclosure>;
25
+ }
26
+
27
+ //#endregion
28
+ export { OpenAPIDisclosure };
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+
3
+
4
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
5
+ import { getOrCreateDisclosureStoreByKey } from "./getOrCreateDisclosureStoreByKey.js";
6
+ import clsx from "classnames";
7
+ import { createContext, useContext, useRef } from "react";
8
+ import { useStore } from "zustand";
9
+ import { mergeProps, useButton, useDisclosure, useFocusRing, useId as useId$1 } from "react-aria";
10
+ import { useDisclosureGroupState, useDisclosureState } from "react-stately";
11
+
12
+ //#region src/OpenAPIDisclosureGroup.tsx
13
+ const DisclosureGroupStateContext = createContext(null);
14
+ function useDisclosureGroupStore(stateKey = "disclosure-group", initialKeys) {
15
+ return useStore(getOrCreateDisclosureStoreByKey(stateKey, initialKeys));
16
+ }
17
+ /**
18
+ * Display an interactive OpenAPI disclosure group.
19
+ */
20
+ function OpenAPIDisclosureGroup(props) {
21
+ const { icon, groups, selectStateKey, stateKey, selectIcon, className, expandedKeys, defaultExpandedKeys, onExpandedChange } = props;
22
+ const { expandedKeys: storeExpandedKeys, setExpandedKeys } = useDisclosureGroupStore(stateKey, expandedKeys || defaultExpandedKeys ? new Set(expandedKeys || defaultExpandedKeys) : void 0);
23
+ const state = useDisclosureGroupState({
24
+ ...props,
25
+ expandedKeys: storeExpandedKeys,
26
+ onExpandedChange: (keys) => {
27
+ setExpandedKeys(keys);
28
+ onExpandedChange?.(keys);
29
+ }
30
+ });
31
+ return <DisclosureGroupStateContext.Provider value={state}>
32
+ {groups.map((group) => <DisclosureItem className={className} selectStateKey={selectStateKey} selectIcon={selectIcon} icon={icon} key={group.key} group={group} />)}
33
+ </DisclosureGroupStateContext.Provider>;
34
+ }
35
+ function DisclosureItem(props) {
36
+ const { icon, group, selectStateKey, selectIcon, className } = props;
37
+ const defaultId = useId$1();
38
+ const id = group.key || defaultId;
39
+ const groupState = useContext(DisclosureGroupStateContext);
40
+ const isExpanded = groupState?.expandedKeys.has(id) || false;
41
+ const state = useDisclosureState({
42
+ isExpanded,
43
+ onExpandedChange() {
44
+ if (groupState) groupState.toggleKey(id);
45
+ }
46
+ });
47
+ const panelRef = useRef(null);
48
+ const triggerRef = useRef(null);
49
+ const isDisabled = groupState?.isDisabled || !group.tabs?.length || false;
50
+ const { buttonProps: triggerProps, panelProps } = useDisclosure({
51
+ ...props,
52
+ isExpanded,
53
+ isDisabled
54
+ }, state, panelRef);
55
+ const { buttonProps } = useButton(triggerProps, triggerRef);
56
+ const { isFocusVisible, focusProps } = useFocusRing();
57
+ const store = useSelectState(selectStateKey, group.tabs?.[0]?.key || "");
58
+ const selectedTab = group.tabs?.find((tab) => tab.key === store.key) || group.tabs?.[0];
59
+ return <div className={clsx("openapi-disclosure-group", className)} aria-expanded={state.isExpanded}>
60
+ <div slot="trigger" ref={triggerRef} {...mergeProps(buttonProps, focusProps)} aria-disabled={isDisabled} style={{ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500)/0.4)" : "none" }} className="openapi-disclosure-group-trigger">
61
+ <div className="openapi-disclosure-group-icon">
62
+ {icon || <svg viewBox="0 0 24 24" className="openapi-disclosure-group-icon">
63
+ <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
64
+ </svg>}
65
+ </div>
66
+
67
+ <div className="openapi-disclosure-group-label">
68
+ {group.label}
69
+
70
+ {group.tabs ? <div className="openapi-disclosure-group-mediatype" onClick={(e) => e.stopPropagation()}>
71
+ {group.tabs?.length > 1 ? <OpenAPISelect icon={selectIcon} stateKey={selectStateKey} onChange={() => {
72
+ state.expand();
73
+ }} items={group.tabs} placement="bottom end">
74
+ {group.tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
75
+ {tab.label}
76
+ </OpenAPISelectItem>)}
77
+ </OpenAPISelect> : group.tabs[0]?.label ? <span>{group.tabs[0].label}</span> : null}
78
+ </div> : null}
79
+ </div>
80
+ </div>
81
+
82
+ {state.isExpanded && selectedTab && <div className="openapi-disclosure-group-panel" ref={panelRef} {...panelProps}>
83
+ {selectedTab.body}
84
+ </div>}
85
+ </div>;
86
+ }
87
+
88
+ //#endregion
89
+ export { OpenAPIDisclosureGroup };
@@ -0,0 +1,41 @@
1
+ import { json2xml } from "./json2xml.js";
2
+ import { stringifyOpenAPI } from "./stringifyOpenAPI.js";
3
+ import { t } from "./translate.js";
4
+ import yaml from "js-yaml";
5
+
6
+ //#region src/OpenAPIExample.tsx
7
+ /**
8
+ * Display an example.
9
+ */
10
+ function OpenAPIExample(props) {
11
+ const { example, context, syntax } = props;
12
+ const code = stringifyExample({
13
+ example,
14
+ syntax
15
+ });
16
+ if (code === null) return <OpenAPIEmptyExample context={context} />;
17
+ return context.renderCodeBlock({
18
+ code,
19
+ syntax
20
+ });
21
+ }
22
+ function stringifyExample(args) {
23
+ const { example, syntax } = args;
24
+ if (!example.value) return null;
25
+ if (typeof example.value === "string") return example.value;
26
+ if (syntax === "xml") return json2xml(example.value);
27
+ if (syntax === "yaml") return yaml.dump(example.value).replace(/'/g, "").replace(/\\n/g, "\n");
28
+ return stringifyOpenAPI(example.value, null, 2);
29
+ }
30
+ /**
31
+ * Empty response example.
32
+ */
33
+ function OpenAPIEmptyExample(props) {
34
+ const { context } = props;
35
+ return <pre className="openapi-example-empty">
36
+ <p>{t(context.translation, "no_content")}</p>
37
+ </pre>;
38
+ }
39
+
40
+ //#endregion
41
+ export { OpenAPIEmptyExample, OpenAPIExample };