@gitbook/react-openapi 1.5.1 → 1.5.2

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 (81) hide show
  1. package/CHANGELOG.md +9 -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 +66 -0
  6. package/dist/OpenAPICodeSampleSelector.js +45 -0
  7. package/dist/OpenAPICopyButton.js +39 -0
  8. package/dist/OpenAPIDisclosure.js +30 -0
  9. package/dist/OpenAPIDisclosureGroup.js +75 -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 +51 -0
  17. package/dist/OpenAPIPrefillContextProvider.d.ts +26 -0
  18. package/dist/OpenAPIPrefillContextProvider.js +25 -0
  19. package/dist/OpenAPIRequestBody.js +28 -0
  20. package/dist/OpenAPIRequestBodyHeaderType.js +23 -0
  21. package/dist/OpenAPIResponse.js +39 -0
  22. package/dist/OpenAPIResponseExample.js +75 -0
  23. package/dist/OpenAPIResponseExampleContent.js +61 -0
  24. package/dist/OpenAPIResponses.js +61 -0
  25. package/dist/OpenAPISchema.js +373 -0
  26. package/dist/OpenAPISchemaName.js +45 -0
  27. package/dist/OpenAPISchemaServer.js +13 -0
  28. package/dist/OpenAPISecurities.js +124 -0
  29. package/dist/OpenAPISelect.js +45 -0
  30. package/dist/OpenAPISpec.js +73 -0
  31. package/dist/OpenAPIWebhook.d.ts +15 -0
  32. package/dist/OpenAPIWebhook.js +28 -0
  33. package/dist/OpenAPIWebhookExample.js +40 -0
  34. package/dist/ScalarApiButton.js +87 -0
  35. package/dist/StaticSection.js +37 -0
  36. package/dist/code-samples.js +275 -0
  37. package/dist/common/OpenAPIColumnSpec.js +23 -0
  38. package/dist/common/OpenAPIOperationDescription.js +18 -0
  39. package/dist/common/OpenAPIStability.js +17 -0
  40. package/dist/common/OpenAPISummary.js +27 -0
  41. package/dist/contentTypeChecks.js +34 -0
  42. package/dist/context.d.ts +71 -0
  43. package/dist/context.js +29 -0
  44. package/dist/decycle.js +41 -0
  45. package/dist/dereference.js +24 -0
  46. package/dist/generateSchemaExample.js +198 -0
  47. package/dist/getDisclosureLabel.js +17 -0
  48. package/dist/getOrCreateStoreByKey.js +22 -0
  49. package/dist/index.d.ts +11 -662
  50. package/dist/index.js +9 -3871
  51. package/dist/json2xml.js +12 -0
  52. package/dist/resolveOpenAPIOperation.d.ts +15 -0
  53. package/dist/resolveOpenAPIOperation.js +102 -0
  54. package/dist/resolveOpenAPIWebhook.d.ts +15 -0
  55. package/dist/resolveOpenAPIWebhook.js +52 -0
  56. package/dist/schemas/OpenAPISchemaItem.js +26 -0
  57. package/dist/schemas/OpenAPISchemas.d.ts +19 -0
  58. package/dist/schemas/OpenAPISchemas.js +57 -0
  59. package/dist/schemas/resolveOpenAPISchemas.d.ts +15 -0
  60. package/dist/schemas/resolveOpenAPISchemas.js +17 -0
  61. package/dist/stringifyOpenAPI.js +14 -0
  62. package/dist/translate.js +43 -0
  63. package/dist/translations/de.js +48 -0
  64. package/dist/translations/en.d.ts +47 -0
  65. package/dist/translations/en.js +48 -0
  66. package/dist/translations/es.js +48 -0
  67. package/dist/translations/fr.js +48 -0
  68. package/dist/translations/index.d.ts +408 -0
  69. package/dist/translations/index.js +31 -0
  70. package/dist/translations/ja.js +48 -0
  71. package/dist/translations/nl.js +48 -0
  72. package/dist/translations/no.js +48 -0
  73. package/dist/translations/pt-br.js +48 -0
  74. package/dist/translations/types.d.ts +7 -0
  75. package/dist/translations/zh.js +48 -0
  76. package/dist/types.d.ts +37 -0
  77. package/dist/util/example.js +84 -0
  78. package/dist/util/server.js +38 -0
  79. package/dist/util/tryit-prefill.js +143 -0
  80. package/dist/utils.js +163 -0
  81. package/package.json +10 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @gitbook/react-openapi
2
2
 
3
+ ## 1.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 6142d6b: Mark as sideEffects, fix all package bundles
8
+ - Updated dependencies [6142d6b]
9
+ - @gitbook/openapi-parser@3.0.4
10
+ - @gitbook/expr@1.2.3
11
+
3
12
  ## 1.5.1
4
13
 
5
14
  ### 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 { useDisclosureState } from "./node_modules/react-stately/dist/import.js";
7
+ import clsx from "classnames";
8
+ import { useRef } from "react";
9
+ import { mergeProps, useButton, useDisclosure, useFocusRing } from "react-aria";
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} onSelectionChange={() => {
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)) 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 && <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,66 @@
1
+ 'use client';
2
+
3
+
4
+ import { createStateKey } from "./utils.js";
5
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
6
+ import clsx from "classnames";
7
+
8
+ //#region src/OpenAPICodeSampleInteractive.tsx
9
+ function OpenAPIMediaTypeExamplesSelector(props) {
10
+ const { method, path, renderers, selectIcon, blockKey } = props;
11
+ if (!renderers[0]) throw new Error("No renderers provided");
12
+ const stateKey = createStateKey("request-body-media-type", blockKey);
13
+ const state = useSelectState(stateKey, renderers[0].mediaType);
14
+ const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
15
+ return <div className="openapi-codesample-selectors">
16
+ <MediaTypeSelector selectIcon={selectIcon} stateKey={stateKey} renderers={renderers} />
17
+ <ExamplesSelector selectIcon={selectIcon} method={method} path={path} renderer={selected} />
18
+ </div>;
19
+ }
20
+ function MediaTypeSelector(props) {
21
+ const { renderers, stateKey, selectIcon } = props;
22
+ if (renderers.length < 2) return null;
23
+ const items = renderers.map((renderer) => ({
24
+ key: renderer.mediaType,
25
+ label: renderer.mediaType
26
+ }));
27
+ return <OpenAPISelect className={clsx("openapi-select")} items={renderers.map((renderer) => ({
28
+ key: renderer.mediaType,
29
+ label: renderer.mediaType
30
+ }))} icon={selectIcon} stateKey={stateKey} placement="bottom start">
31
+ {items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
32
+ {item.label}
33
+ </OpenAPISelectItem>)}
34
+ </OpenAPISelect>;
35
+ }
36
+ function ExamplesSelector(props) {
37
+ const { method, path, renderer, selectIcon } = props;
38
+ if (renderer.examples.length < 2) return null;
39
+ const items = renderer.examples.map((example, index) => ({
40
+ key: index,
41
+ label: example.example.summary || `Example ${index + 1}`
42
+ }));
43
+ return <OpenAPISelect items={items} icon={selectIcon} stateKey={`media-type-sample-${renderer.mediaType}-${method}-${path}`} placement="bottom start">
44
+ {items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
45
+ {item.label}
46
+ </OpenAPISelectItem>)}
47
+ </OpenAPISelect>;
48
+ }
49
+ function OpenAPIMediaTypeExamplesBody(props) {
50
+ const { renderers, method, path, blockKey } = props;
51
+ if (!renderers[0]) throw new Error("No renderers provided");
52
+ const mediaTypeState = useSelectState(createStateKey("request-body-media-type", blockKey), renderers[0].mediaType);
53
+ const selected = renderers.find((r) => r.mediaType === mediaTypeState.key) ?? renderers[0];
54
+ if (selected.examples.length === 0) return selected.element;
55
+ return <ExamplesBody method={method} path={path} renderer={selected} />;
56
+ }
57
+ function ExamplesBody(props) {
58
+ const { method, path, renderer } = props;
59
+ const exampleState = useSelectState(`media-type-sample-${renderer.mediaType}-${method}-${path}`, renderer.mediaType);
60
+ const example = renderer.examples[Number(exampleState.key)] ?? renderer.examples[0];
61
+ if (!example) throw new Error(`No example found for key ${exampleState.key}`);
62
+ return example.element;
63
+ }
64
+
65
+ //#endregion
66
+ 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,39 @@
1
+ 'use client';
2
+
3
+
4
+ import { t } from "./translate.js";
5
+ import { useState } from "react";
6
+ import { Button, Tooltip, TooltipTrigger } from "react-aria-components";
7
+
8
+ //#region src/OpenAPICopyButton.tsx
9
+ function OpenAPICopyButton(props) {
10
+ const { value, label, children, onPress, className, context, withTooltip = true } = props;
11
+ const [copied, setCopied] = useState(false);
12
+ const [isOpen, setIsOpen] = useState(false);
13
+ const handleCopy = () => {
14
+ if (!value) return;
15
+ navigator.clipboard.writeText(value).then(() => {
16
+ setIsOpen(true);
17
+ setCopied(true);
18
+ setTimeout(() => {
19
+ setCopied(false);
20
+ setIsOpen(false);
21
+ }, 2e3);
22
+ });
23
+ };
24
+ return <TooltipTrigger isOpen={isOpen} onOpenChange={setIsOpen} isDisabled={!withTooltip} closeDelay={200} delay={200}>
25
+ <Button type="button" preventFocusOnPress onPress={(e) => {
26
+ handleCopy();
27
+ onPress?.(e);
28
+ }} className={`openapi-copy-button ${className}`} {...props}>
29
+ {children}
30
+ </Button>
31
+
32
+ <Tooltip isOpen={isOpen} onOpenChange={setIsOpen} placement="top" offset={4} className="openapi-tooltip">
33
+ {copied ? t(context.translation, "copied") : label || t(context.translation, "copy_to_clipboard")}
34
+ </Tooltip>
35
+ </TooltipTrigger>;
36
+ }
37
+
38
+ //#endregion
39
+ export { OpenAPICopyButton };
@@ -0,0 +1,30 @@
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 } = props;
14
+ const [isExpanded, setIsExpanded] = useState(false);
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
+ <span>{typeof label === "function" ? label(isExpanded) : label}</span>
20
+ {icon}
21
+ </div>
22
+ </Button>
23
+ <DisclosurePanel className="openapi-disclosure-panel">
24
+ {isExpanded ? children : null}
25
+ </DisclosurePanel>
26
+ </Disclosure>;
27
+ }
28
+
29
+ //#endregion
30
+ export { OpenAPIDisclosure };
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+
3
+
4
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
5
+ import { useDisclosureGroupState, useDisclosureState } from "./node_modules/react-stately/dist/import.js";
6
+ import { createContext, useContext, useRef } from "react";
7
+ import { mergeProps, useButton, useDisclosure, useFocusRing, useId as useId$1 } from "react-aria";
8
+
9
+ //#region src/OpenAPIDisclosureGroup.tsx
10
+ const DisclosureGroupStateContext = createContext(null);
11
+ /**
12
+ * Display an interactive OpenAPI disclosure group.
13
+ */
14
+ function OpenAPIDisclosureGroup(props) {
15
+ const { icon, groups, selectStateKey, selectIcon } = props;
16
+ const state = useDisclosureGroupState(props);
17
+ return <DisclosureGroupStateContext.Provider value={state}>
18
+ {groups.map((group) => <DisclosureItem selectStateKey={selectStateKey} selectIcon={selectIcon} icon={icon} key={group.key} group={group} />)}
19
+ </DisclosureGroupStateContext.Provider>;
20
+ }
21
+ function DisclosureItem(props) {
22
+ const { icon, group, selectStateKey, selectIcon } = props;
23
+ const defaultId = useId$1();
24
+ const id = group.key || defaultId;
25
+ const groupState = useContext(DisclosureGroupStateContext);
26
+ const isExpanded = groupState?.expandedKeys.has(id) || false;
27
+ const state = useDisclosureState({
28
+ isExpanded,
29
+ onExpandedChange() {
30
+ if (groupState) groupState.toggleKey(id);
31
+ }
32
+ });
33
+ const panelRef = useRef(null);
34
+ const triggerRef = useRef(null);
35
+ const isDisabled = groupState?.isDisabled || !group.tabs?.length || false;
36
+ const { buttonProps: triggerProps, panelProps } = useDisclosure({
37
+ ...props,
38
+ isExpanded,
39
+ isDisabled
40
+ }, state, panelRef);
41
+ const { buttonProps } = useButton(triggerProps, triggerRef);
42
+ const { isFocusVisible, focusProps } = useFocusRing();
43
+ const store = useSelectState(selectStateKey, group.tabs?.[0]?.key || "");
44
+ const selectedTab = group.tabs?.find((tab) => tab.key === store.key) || group.tabs?.[0];
45
+ return <div className="openapi-disclosure-group" aria-expanded={state.isExpanded}>
46
+ <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">
47
+ <div className="openapi-disclosure-group-icon">
48
+ {icon || <svg viewBox="0 0 24 24" className="openapi-disclosure-group-icon">
49
+ <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
50
+ </svg>}
51
+ </div>
52
+
53
+ <div className="openapi-disclosure-group-label">
54
+ {group.label}
55
+
56
+ {group.tabs ? <div className="openapi-disclosure-group-mediatype" onClick={(e) => e.stopPropagation()}>
57
+ {group.tabs?.length > 1 ? <OpenAPISelect icon={selectIcon} stateKey={selectStateKey} onSelectionChange={() => {
58
+ state.expand();
59
+ }} items={group.tabs} placement="bottom end">
60
+ {group.tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
61
+ {tab.label}
62
+ </OpenAPISelectItem>)}
63
+ </OpenAPISelect> : group.tabs[0]?.label ? <span>{group.tabs[0].label}</span> : null}
64
+ </div> : null}
65
+ </div>
66
+ </div>
67
+
68
+ {state.isExpanded && selectedTab && <div className="openapi-disclosure-group-panel" ref={panelRef} {...panelProps}>
69
+ {selectedTab.body}
70
+ </div>}
71
+ </div>;
72
+ }
73
+
74
+ //#endregion
75
+ 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 };
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+
4
+ import { OpenAPIEmptyExample } from "./OpenAPIExample.js";
5
+ import { StaticSection } from "./StaticSection.js";
6
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
7
+
8
+ //#region src/OpenAPIMediaType.tsx
9
+ /**
10
+ * Get the state of the response examples select.
11
+ */
12
+ function useMediaTypesState(stateKey, initialKey = "default") {
13
+ return useSelectState(stateKey, initialKey);
14
+ }
15
+ function useMediaTypeExamplesState(stateKey, initialKey = "default") {
16
+ return useSelectState(stateKey, initialKey);
17
+ }
18
+ function OpenAPIMediaTypeContent(props) {
19
+ const { stateKey, items, selectIcon, context } = props;
20
+ const state = useMediaTypesState(stateKey, items[0]?.key);
21
+ const examples = items.find((item) => item.key === state.key)?.examples ?? [];
22
+ if (!items.length && !examples.length) return null;
23
+ return <StaticSection footer={items.length > 1 || examples.length > 1 ? <OpenAPIMediaTypeFooter items={items} examples={examples} selectIcon={selectIcon} stateKey={stateKey} /> : null} className="openapi-response-media-types-examples">
24
+ <OpenAPIMediaTypeBody context={context} stateKey={stateKey} items={items} examples={examples} />
25
+ </StaticSection>;
26
+ }
27
+ function OpenAPIMediaTypeFooter(props) {
28
+ const { items, examples, stateKey, selectIcon } = props;
29
+ return <>
30
+ {items.length > 1 && <OpenAPISelect icon={selectIcon} items={items} stateKey={stateKey} placement="bottom start">
31
+ {items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
32
+ <span>{item.label}</span>
33
+ </OpenAPISelectItem>)}
34
+ </OpenAPISelect>}
35
+
36
+ {examples && examples.length > 1 ? <OpenAPISelect icon={selectIcon} items={examples} stateKey={`${stateKey}-examples`} placement="bottom start">
37
+ {examples.map((example) => <OpenAPISelectItem key={example.key} id={example.key} value={example}>
38
+ <span>{example.label}</span>
39
+ </OpenAPISelectItem>)}
40
+ </OpenAPISelect> : null}
41
+ </>;
42
+ }
43
+ function OpenAPIMediaTypeBody(props) {
44
+ const { stateKey, items, examples, context } = props;
45
+ const state = useMediaTypesState(stateKey, items[0]?.key);
46
+ const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
47
+ const exampleState = useMediaTypeExamplesState(`${stateKey}-examples`, selectedItem?.examples?.[0]?.key);
48
+ if (!selectedItem) return null;
49
+ if (examples) {
50
+ const selectedExample = examples.find((example) => example.key === exampleState.key) ?? examples[0];
51
+ if (!selectedExample) return <OpenAPIEmptyExample context={context} />;
52
+ return selectedExample.body;
53
+ }
54
+ return selectedItem.body;
55
+ }
56
+
57
+ //#endregion
58
+ export { OpenAPIMediaTypeContent };