@gitbook/react-openapi 1.5.2 → 1.5.4

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 (39) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/InteractiveSection.js +2 -2
  3. package/dist/OpenAPICodeSample.js +8 -5
  4. package/dist/OpenAPICodeSampleInteractive.js +1 -5
  5. package/dist/OpenAPICopyButton.js +12 -7
  6. package/dist/OpenAPIDisclosure.js +4 -6
  7. package/dist/OpenAPIDisclosureGroup.js +21 -7
  8. package/dist/OpenAPIPath.js +12 -38
  9. package/dist/OpenAPIPathItem.js +22 -0
  10. package/dist/OpenAPIPathMultipleServers.js +43 -0
  11. package/dist/OpenAPIRequiredScopes.js +67 -0
  12. package/dist/OpenAPISchema.js +187 -79
  13. package/dist/OpenAPISecurities.js +17 -43
  14. package/dist/OpenAPISelect.js +6 -6
  15. package/dist/OpenAPISpec.js +1 -1
  16. package/dist/OpenAPITooltip.js +23 -0
  17. package/dist/ScalarApiButton.js +5 -2
  18. package/dist/code-samples.js +33 -3
  19. package/dist/context.d.ts +3 -0
  20. package/dist/formatPath.js +25 -0
  21. package/dist/generateSchemaExample.js +20 -3
  22. package/dist/getOrCreateDisclosureStoreByKey.js +31 -0
  23. package/dist/resolveOpenAPIOperation.js +5 -2
  24. package/dist/translate.js +2 -2
  25. package/dist/translations/de.js +2 -0
  26. package/dist/translations/en.d.ts +2 -0
  27. package/dist/translations/en.js +2 -0
  28. package/dist/translations/es.js +2 -0
  29. package/dist/translations/fr.js +2 -0
  30. package/dist/translations/index.d.ts +18 -0
  31. package/dist/translations/ja.js +2 -0
  32. package/dist/translations/nl.js +2 -0
  33. package/dist/translations/no.js +2 -0
  34. package/dist/translations/pt-br.js +2 -0
  35. package/dist/translations/zh.js +2 -0
  36. package/dist/types.d.ts +1 -0
  37. package/dist/util/tryit-prefill.js +4 -1
  38. package/dist/utils.js +8 -6
  39. package/package.json +23 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @gitbook/react-openapi
2
2
 
3
+ ## 1.5.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 4766092: Fix missing properties in allOf/oneOf
8
+ - 8761cee: Enhance discriminator handling in OpenAPISchema
9
+ - 461e15f: Add x-gitbook-prefix and x-gitbook-token-placeholder for OpenAPI security scheme
10
+ - 3e40b4d: Fix OpenAPI basic auth placeholder
11
+ - 87d68ea: Fix OpenAPI oneOf/allOf merge
12
+ - 344842f: Improve OpenAPI circular references
13
+ - Updated dependencies [461e15f]
14
+ - @gitbook/openapi-parser@3.0.6
15
+
16
+ ## 1.5.3
17
+
18
+ ### Patch Changes
19
+
20
+ - b4a021a: Add heredoc support for cURL JSON body
21
+ - a512c90: Re-arrange OpenAPI Scopes for OAuth2
22
+ - df1966d: Bump Scalar
23
+ - b45feaf: Disable OpenAPI "Try it" when no servers are defined
24
+ - 10995e0: Use NPM Trusted publishing for publishing the package.
25
+ - f9f8011: Add alt text support to card covers
26
+ - 8ce7322: Add OpenAPI servers selection
27
+ - 2c3066e: Improve OAuth2 scopes handling in OpenAPI
28
+ - Updated dependencies [df1966d]
29
+ - Updated dependencies [10995e0]
30
+ - Updated dependencies [10995e0]
31
+ - @gitbook/openapi-parser@3.0.5
32
+ - @gitbook/expr@1.2.4
33
+
3
34
  ## 1.5.2
4
35
 
5
36
  ### Patch Changes
@@ -3,10 +3,10 @@
3
3
 
4
4
  import { Section, SectionBody, SectionHeader, SectionHeaderContent } from "./StaticSection.js";
5
5
  import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
6
- import { useDisclosureState } from "./node_modules/react-stately/dist/import.js";
7
6
  import clsx from "classnames";
8
7
  import { useRef } from "react";
9
8
  import { mergeProps, useButton, useDisclosure, useFocusRing } from "react-aria";
9
+ import { useDisclosureState } from "react-stately";
10
10
 
11
11
  //#region src/InteractiveSection.tsx
12
12
  /**
@@ -37,7 +37,7 @@ function InteractiveSection(props) {
37
37
  <div className={clsx("openapi-section-header-controls", `${className}-header-controls`)} onClick={(event) => {
38
38
  event.stopPropagation();
39
39
  }}>
40
- {tabs.length > 0 ? <OpenAPISelect stateKey={stateKey} items={tabs} onSelectionChange={() => {
40
+ {tabs.length > 0 ? <OpenAPISelect stateKey={stateKey} items={tabs} onChange={() => {
41
41
  state.expand();
42
42
  }} icon={selectIcon} placement="bottom end">
43
43
  {tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
@@ -132,10 +132,10 @@ function OpenAPICodeSampleFooter(props) {
132
132
  const hideTryItPanel = data["x-hideTryItPanel"] || data.operation["x-hideTryItPanel"];
133
133
  const hasMultipleMediaTypes = renderers.length > 1 || renderers.some((renderer) => renderer.examples.length > 0);
134
134
  if (hideTryItPanel && !hasMultipleMediaTypes) return null;
135
- if (!validateHttpMethod(method)) return null;
135
+ if (!validateHttpMethod(method) || !hasMultipleMediaTypes && servers.length === 0) return null;
136
136
  return <div className="openapi-codesample-footer">
137
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} />}
138
+ {!hideTryItPanel && servers.length > 0 && <ScalarApiButton context={getOpenAPIClientContext(context)} method={method} path={path} securities={securities} servers={servers} specUrl={specUrl} />}
139
139
  </div>;
140
140
  }
141
141
  /**
@@ -175,25 +175,28 @@ function getSecurityHeaders(args) {
175
175
  let scheme = security.scheme;
176
176
  const format = resolvePrefillCodePlaceholderFromSecurityScheme({
177
177
  security,
178
- defaultPlaceholderValue: scheme?.includes("basic") ? "username:password" : "YOUR_SECRET_TOKEN"
178
+ defaultPlaceholderValue: scheme?.toLowerCase()?.includes("basic") ? "username:password" : "YOUR_SECRET_TOKEN"
179
179
  });
180
180
  if (scheme?.includes("bearer")) scheme = "Bearer";
181
181
  else if (scheme?.includes("basic")) scheme = "Basic";
182
182
  else if (scheme?.includes("token")) scheme = "Token";
183
+ else scheme = scheme ?? "";
183
184
  headers.Authorization = `${scheme} ${format}`;
184
185
  break;
185
186
  }
186
187
  case "apiKey": {
187
188
  if (security.in !== "header") break;
188
189
  const name = security.name ?? "Authorization";
189
- headers[name] = resolvePrefillCodePlaceholderFromSecurityScheme({
190
+ const placeholder = resolvePrefillCodePlaceholderFromSecurityScheme({
190
191
  security,
191
192
  defaultPlaceholderValue: "YOUR_API_KEY"
192
193
  });
194
+ const prefix = security["x-gitbook-prefix"];
195
+ headers[name] = prefix ? `${prefix} ${placeholder}` : placeholder;
193
196
  break;
194
197
  }
195
198
  case "oauth2":
196
- headers.Authorization = `Bearer ${resolvePrefillCodePlaceholderFromSecurityScheme({
199
+ headers.Authorization = `${security["x-gitbook-prefix"] ?? "Bearer"} ${resolvePrefillCodePlaceholderFromSecurityScheme({
197
200
  security,
198
201
  defaultPlaceholderValue: "YOUR_OAUTH2_TOKEN"
199
202
  })}`;
@@ -3,7 +3,6 @@
3
3
 
4
4
  import { createStateKey } from "./utils.js";
5
5
  import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
6
- import clsx from "classnames";
7
6
 
8
7
  //#region src/OpenAPICodeSampleInteractive.tsx
9
8
  function OpenAPIMediaTypeExamplesSelector(props) {
@@ -24,10 +23,7 @@ function MediaTypeSelector(props) {
24
23
  key: renderer.mediaType,
25
24
  label: renderer.mediaType
26
25
  }));
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">
26
+ return <OpenAPISelect items={items} icon={selectIcon} stateKey={stateKey} placement="bottom start">
31
27
  {items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
32
28
  {item.label}
33
29
  </OpenAPISelectItem>)}
@@ -2,8 +2,10 @@
2
2
 
3
3
 
4
4
  import { t } from "./translate.js";
5
+ import { OpenAPITooltip } from "./OpenAPITooltip.js";
6
+ import clsx from "classnames";
5
7
  import { useState } from "react";
6
- import { Button, Tooltip, TooltipTrigger } from "react-aria-components";
8
+ import { Button } from "react-aria-components";
7
9
 
8
10
  //#region src/OpenAPICopyButton.tsx
9
11
  function OpenAPICopyButton(props) {
@@ -21,18 +23,21 @@ function OpenAPICopyButton(props) {
21
23
  }, 2e3);
22
24
  });
23
25
  };
24
- return <TooltipTrigger isOpen={isOpen} onOpenChange={setIsOpen} isDisabled={!withTooltip} closeDelay={200} delay={200}>
26
+ return <OpenAPITooltip isDisabled={!withTooltip} isOpen={isOpen} onOpenChange={setIsOpen}>
25
27
  <Button type="button" preventFocusOnPress onPress={(e) => {
26
28
  handleCopy();
27
29
  onPress?.(e);
28
- }} className={`openapi-copy-button ${className}`} {...props}>
30
+ }} className={clsx("openapi-copy-button", className)} {...props}>
29
31
  {children}
30
32
  </Button>
31
33
 
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>;
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>;
36
41
  }
37
42
 
38
43
  //#endregion
@@ -10,19 +10,17 @@ import { Button, Disclosure, DisclosurePanel } from "react-aria-components";
10
10
  * Display an interactive OpenAPI disclosure.
11
11
  */
12
12
  function OpenAPIDisclosure(props) {
13
- const { icon, header, label, children, className } = props;
14
- const [isExpanded, setIsExpanded] = useState(false);
13
+ const { icon, header, label, children, className, defaultExpanded = false } = props;
14
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
15
15
  return <Disclosure className={clsx("openapi-disclosure", className)} isExpanded={isExpanded} onExpandedChange={setIsExpanded}>
16
16
  <Button slot="trigger" className="openapi-disclosure-trigger" style={({ isFocusVisible }) => ({ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500) / 0.4)" : "none" })}>
17
17
  {header}
18
18
  <div className="openapi-disclosure-trigger-label">
19
- <span>{typeof label === "function" ? label(isExpanded) : label}</span>
19
+ {label ? <span>{typeof label === "function" ? label(isExpanded) : label}</span> : null}
20
20
  {icon}
21
21
  </div>
22
22
  </Button>
23
- <DisclosurePanel className="openapi-disclosure-panel">
24
- {isExpanded ? children : null}
25
- </DisclosurePanel>
23
+ {isExpanded ? <DisclosurePanel className="openapi-disclosure-panel">{children}</DisclosurePanel> : null}
26
24
  </Disclosure>;
27
25
  }
28
26
 
@@ -2,24 +2,38 @@
2
2
 
3
3
 
4
4
  import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
5
- import { useDisclosureGroupState, useDisclosureState } from "./node_modules/react-stately/dist/import.js";
5
+ import { getOrCreateDisclosureStoreByKey } from "./getOrCreateDisclosureStoreByKey.js";
6
+ import clsx from "classnames";
6
7
  import { createContext, useContext, useRef } from "react";
8
+ import { useStore } from "zustand";
7
9
  import { mergeProps, useButton, useDisclosure, useFocusRing, useId as useId$1 } from "react-aria";
10
+ import { useDisclosureGroupState, useDisclosureState } from "react-stately";
8
11
 
9
12
  //#region src/OpenAPIDisclosureGroup.tsx
10
13
  const DisclosureGroupStateContext = createContext(null);
14
+ function useDisclosureGroupStore(stateKey = "disclosure-group", initialKeys) {
15
+ return useStore(getOrCreateDisclosureStoreByKey(stateKey, initialKeys));
16
+ }
11
17
  /**
12
18
  * Display an interactive OpenAPI disclosure group.
13
19
  */
14
20
  function OpenAPIDisclosureGroup(props) {
15
- const { icon, groups, selectStateKey, selectIcon } = props;
16
- const state = useDisclosureGroupState(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
+ });
17
31
  return <DisclosureGroupStateContext.Provider value={state}>
18
- {groups.map((group) => <DisclosureItem selectStateKey={selectStateKey} selectIcon={selectIcon} icon={icon} key={group.key} group={group} />)}
32
+ {groups.map((group) => <DisclosureItem className={className} selectStateKey={selectStateKey} selectIcon={selectIcon} icon={icon} key={group.key} group={group} />)}
19
33
  </DisclosureGroupStateContext.Provider>;
20
34
  }
21
35
  function DisclosureItem(props) {
22
- const { icon, group, selectStateKey, selectIcon } = props;
36
+ const { icon, group, selectStateKey, selectIcon, className } = props;
23
37
  const defaultId = useId$1();
24
38
  const id = group.key || defaultId;
25
39
  const groupState = useContext(DisclosureGroupStateContext);
@@ -42,7 +56,7 @@ function DisclosureItem(props) {
42
56
  const { isFocusVisible, focusProps } = useFocusRing();
43
57
  const store = useSelectState(selectStateKey, group.tabs?.[0]?.key || "");
44
58
  const selectedTab = group.tabs?.find((tab) => tab.key === store.key) || group.tabs?.[0];
45
- return <div className="openapi-disclosure-group" aria-expanded={state.isExpanded}>
59
+ return <div className={clsx("openapi-disclosure-group", className)} aria-expanded={state.isExpanded}>
46
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">
47
61
  <div className="openapi-disclosure-group-icon">
48
62
  {icon || <svg viewBox="0 0 24 24" className="openapi-disclosure-group-icon">
@@ -54,7 +68,7 @@ function DisclosureItem(props) {
54
68
  {group.label}
55
69
 
56
70
  {group.tabs ? <div className="openapi-disclosure-group-mediatype" onClick={(e) => e.stopPropagation()}>
57
- {group.tabs?.length > 1 ? <OpenAPISelect icon={selectIcon} stateKey={selectStateKey} onSelectionChange={() => {
71
+ {group.tabs?.length > 1 ? <OpenAPISelect icon={selectIcon} stateKey={selectStateKey} onChange={() => {
58
72
  state.expand();
59
73
  }} items={group.tabs} placement="bottom end">
60
74
  {group.tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
@@ -1,50 +1,24 @@
1
- import { OpenAPICopyButton } from "./OpenAPICopyButton.js";
2
1
  import { getOpenAPIClientContext } from "./context.js";
2
+ import { OpenAPIPathItem } from "./OpenAPIPathItem.js";
3
+ import { formatPath } from "./formatPath.js";
3
4
  import { getDefaultServerURL } from "./util/server.js";
5
+ import { OpenAPIPathMultipleServers } from "./OpenAPIPathMultipleServers.js";
4
6
 
5
7
  //#region src/OpenAPIPath.tsx
6
8
  /**
7
9
  * Display the path of an operation.
8
10
  */
9
11
  function OpenAPIPath(props) {
10
- const { data, context, withServer = true, canCopy = true } = props;
11
- const { method, path, operation } = data;
12
- const server = getDefaultServerURL(data.servers);
12
+ const { data, withServer = true, context } = props;
13
+ const { path } = data;
14
+ const clientContext = getOpenAPIClientContext(context);
15
+ if (withServer && data.servers.length > 1) return <OpenAPIPathMultipleServers {...props} context={clientContext} />;
13
16
  const formattedPath = formatPath(path);
14
- const element = (() => {
15
- return <>
16
- {withServer ? <span className="openapi-path-server">{server}</span> : null}
17
- {formattedPath}
18
- </>;
19
- })();
20
- return <div className="openapi-path">
21
- <div className={`openapi-method openapi-method-${method}`}>{method}</div>
22
-
23
- <OpenAPICopyButton value={`${withServer ? server : ""}${path}`} className="openapi-path-title" data-deprecated={operation.deprecated} isDisabled={!canCopy} context={getOpenAPIClientContext(context)}>
24
- {element}
25
- </OpenAPICopyButton>
26
- </div>;
27
- }
28
- /**
29
- * Format the path by wrapping placeholders in <span> tags.
30
- */
31
- function formatPath(path) {
32
- const regex = /\{\s*(\w+)\s*\}|:\w+/g;
33
- const parts = [];
34
- let lastIndex = 0;
35
- path.replace(regex, (match, _, offset) => {
36
- if (offset > lastIndex) parts.push(path.slice(lastIndex, offset));
37
- parts.push(<span key={`offset-${offset}`} className="openapi-path-variable">
38
- {match}
39
- </span>);
40
- lastIndex = offset + match.length;
41
- return match;
42
- });
43
- if (lastIndex < path.length) parts.push(path.slice(lastIndex));
44
- return parts.map((part, index) => {
45
- if (typeof part === "string") return <span key={`part-${index}`}>{part}</span>;
46
- return part;
47
- });
17
+ const defaultServer = getDefaultServerURL(data.servers);
18
+ return <OpenAPIPathItem {...props} value={`${defaultServer}${path}`} context={clientContext}>
19
+ {withServer ? <span className="openapi-path-server">{defaultServer}</span> : null}
20
+ {formattedPath}
21
+ </OpenAPIPathItem>;
48
22
  }
49
23
 
50
24
  //#endregion
@@ -0,0 +1,22 @@
1
+ import { OpenAPICopyButton } from "./OpenAPICopyButton.js";
2
+
3
+ //#region src/OpenAPIPathItem.tsx
4
+ function OpenAPIPathItem(props) {
5
+ const { value, canCopy = true, context, children, data, copyType = "children" } = props;
6
+ const { operation, method } = data;
7
+ const title = <span className="openapi-path-title">{children}</span>;
8
+ return <div className="openapi-path">
9
+ <div className={`openapi-method openapi-method-${method}`}>{method}</div>
10
+ {canCopy && value ? copyType === "children" ? <OpenAPICopyButton value={value} data-deprecated={operation.deprecated} isDisabled={!canCopy} context={context} className="openapi-path-copy-button">
11
+ {title}
12
+ </OpenAPICopyButton> : <>
13
+ {title}
14
+ <OpenAPICopyButton value={value} data-deprecated={operation.deprecated} isDisabled={!canCopy} context={context} className="openapi-path-copy-button openapi-path-copy-button-icon">
15
+ {context.icons.copy}
16
+ </OpenAPICopyButton>
17
+ </> : title}
18
+ </div>;
19
+ }
20
+
21
+ //#endregion
22
+ export { OpenAPIPathItem };
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+
4
+ import { OpenAPITooltip } from "./OpenAPITooltip.js";
5
+ import { createStateKey } from "./utils.js";
6
+ import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
7
+ import { OpenAPIPathItem } from "./OpenAPIPathItem.js";
8
+ import { formatPath } from "./formatPath.js";
9
+ import { getDefaultServerURL } from "./util/server.js";
10
+ import { Text } from "react-aria-components";
11
+
12
+ //#region src/OpenAPIPathMultipleServers.tsx
13
+ const serversStateKey = createStateKey("servers");
14
+ /**
15
+ * Display the path of an operation.
16
+ */
17
+ function OpenAPIPathMultipleServers(props) {
18
+ const { data, withServer = true, context } = props;
19
+ const { path, servers } = data;
20
+ const defaultServer = getDefaultServerURL(servers);
21
+ const { key, setKey } = useSelectState(serversStateKey, defaultServer);
22
+ const formattedPath = formatPath(path);
23
+ const items = servers.filter((server) => !!server.url).map((server) => ({
24
+ key: server.url,
25
+ label: server.url,
26
+ description: server.description
27
+ }));
28
+ return <OpenAPIPathItem copyType="button" {...props} value={`${withServer ? key : ""}${path}`} context={context}>
29
+ {withServer ? <OpenAPITooltip>
30
+ <OpenAPISelect className="openapi-select openapi-select-unstyled" items={items} stateKey={serversStateKey} placement="bottom start" icon={context.icons.chevronDown} defaultValue={defaultServer} onChange={setKey}>
31
+ {items.map((item) => <OpenAPISelectItem textValue={item.label} key={item.key} id={item.key} value={item} className="openapi-select-item-column">
32
+ <Text slot="label">{item.label}</Text>
33
+ {item.description ? <Text slot="description">{item.description}</Text> : null}
34
+ </OpenAPISelectItem>)}
35
+ </OpenAPISelect>
36
+ <OpenAPITooltip.Content>Click to select a server</OpenAPITooltip.Content>
37
+ </OpenAPITooltip> : null}
38
+ {formattedPath}
39
+ </OpenAPIPathItem>;
40
+ }
41
+
42
+ //#endregion
43
+ export { OpenAPIPathMultipleServers };
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+
3
+
4
+ import { t } from "./translate.js";
5
+ import { OpenAPICopyButton } from "./OpenAPICopyButton.js";
6
+ import { useSelectState } from "./OpenAPISelect.js";
7
+ import { OpenAPIDisclosureGroup } from "./OpenAPIDisclosureGroup.js";
8
+
9
+ //#region src/OpenAPIRequiredScopes.tsx
10
+ /**
11
+ * Present securities authorization that can be used for this operation.
12
+ */
13
+ function OpenAPIRequiredScopes(props) {
14
+ const { securities, stateKey, context } = props;
15
+ const { key: selectedKey } = useSelectState(stateKey, securities[0]?.key);
16
+ const selectedSecurity = securities.find((security) => security.key === selectedKey);
17
+ if (!selectedSecurity) return null;
18
+ const scopes = selectedSecurity.schemes.flatMap((scheme) => {
19
+ return scheme.scopes ?? [];
20
+ });
21
+ if (!scopes.length) return null;
22
+ return <OpenAPIDisclosureGroup className="openapi-required-scopes" icon={context.icons.chevronRight} stateKey="required-scopes" defaultExpandedKeys={["required-scopes"]} groups={[{
23
+ key: "required-scopes",
24
+ label: <div className="openapi-required-scopes-header">
25
+ {context.icons.lock}
26
+ <span>{t(context.translation, "required_scopes")}</span>
27
+ </div>,
28
+ tabs: [{
29
+ key: "scopes",
30
+ label: "",
31
+ body: <OpenAPISchemaScopes scopes={scopes} context={context} />
32
+ }]
33
+ }]} />;
34
+ }
35
+ function OpenAPISchemaScopes(props) {
36
+ const { scopes, context, isOAuth2 } = props;
37
+ return <div className="openapi-securities-scopes openapi-markdown">
38
+ <div className="openapi-required-scopes-description">
39
+ {t(context.translation, isOAuth2 ? "available_scopes" : "required_scopes_description")}
40
+ </div>
41
+ <ul>
42
+ {scopes.map((scope) => <OpenAPIScopeItem key={scope[0]} scope={scope} context={context} />)}
43
+ </ul>
44
+ </div>;
45
+ }
46
+ /**
47
+ * Display a scope item. Either a key-value pair or a single string.
48
+ */
49
+ function OpenAPIScopeItem(props) {
50
+ const { scope, context } = props;
51
+ return <li>
52
+ <OpenAPIScopeItemKey name={scope[0]} context={context} />
53
+ {scope[1] ? <span>: {scope[1]}</span> : null}
54
+ </li>;
55
+ }
56
+ /**
57
+ * Displays the scope name within a copyable button.
58
+ */
59
+ function OpenAPIScopeItemKey(props) {
60
+ const { name, context } = props;
61
+ return <OpenAPICopyButton value={name} context={context} withTooltip>
62
+ <code>{name}</code>
63
+ </OpenAPICopyButton>;
64
+ }
65
+
66
+ //#endregion
67
+ export { OpenAPIRequiredScopes, OpenAPISchemaScopes };