security.key === selectedKey);
+
+ if (!selectedSecurity) {
+ return null;
+ }
+
+ const scopes = selectedSecurity.schemes.flatMap((scheme) => {
+ if (scheme.type === 'oauth2') {
+ return Object.entries(scheme.flows ?? {}).flatMap(([_, flow]) =>
+ Object.entries(flow.scopes ?? {})
+ );
+ }
+
+ return scheme.scopes ?? [];
+ });
+
+ if (!scopes.length) {
+ return null;
+ }
+
+ return (
+
+ {context.icons.lock}
+ {t(context.translation, 'required_scopes')}
+
+ ),
+ tabs: [
+ {
+ key: 'scopes',
+ label: '',
+ body:
,
+ },
+ ],
+ },
+ ]}
+ />
+ );
+}
+
+function OpenAPISchemaScopes(props: {
+ scopes: OpenAPISecurityScope[];
+ context: OpenAPIClientContext;
+}) {
+ const { scopes, context } = props;
+
+ return (
+
+
+ {t(context.translation, 'required_scopes_description')}
+
+
+ {scopes.map((scope) => (
+
+ ))}
+
+
+ );
+}
+
+/**
+ * Display a scope item. Either a key-value pair or a single string.
+ */
+function OpenAPIScopeItem(props: {
+ scope: OpenAPISecurityScope;
+ context: OpenAPIClientContext;
+}) {
+ const { scope, context } = props;
+
+ return (
+
+
+ {scope[1] ? : {scope[1]} : null}
+
+ );
+}
+
+/**
+ * Displays the scope name within a copyable button.
+ */
+function OpenAPIScopeItemKey(props: {
+ name: string;
+ context: OpenAPIClientContext;
+}) {
+ const { name, context } = props;
+
+ return (
+
+ {name}
+
+ );
+}
diff --git a/packages/react-openapi/src/OpenAPISecurities.tsx b/packages/react-openapi/src/OpenAPISecurities.tsx
index f4808e69c6..d1d00a71e0 100644
--- a/packages/react-openapi/src/OpenAPISecurities.tsx
+++ b/packages/react-openapi/src/OpenAPISecurities.tsx
@@ -3,10 +3,11 @@ import { Fragment } from 'react';
import { InteractiveSection } from './InteractiveSection';
import { Markdown } from './Markdown';
import { OpenAPICopyButton } from './OpenAPICopyButton';
+import { OpenAPIRequiredScopes } from './OpenAPIRequiredScopes';
import { OpenAPISchemaName } from './OpenAPISchemaName';
import type { OpenAPIClientContext } from './context';
import { t } from './translate';
-import type { OpenAPICustomSecurityScheme, OpenAPISecurityScope } from './types';
+import type { OpenAPICustomSecurityScheme } from './types';
import type { OpenAPIOperationData } from './types';
import { createStateKey, extractOperationSecurityInfo, resolveDescription } from './utils';
@@ -25,48 +26,44 @@ export function OpenAPISecurities(props: {
}
const tabsData = extractOperationSecurityInfo({ securityRequirement, securities });
+ const stateKey = createStateKey('securities', context.blockKey);
return (
-
({
- key,
- label,
- body: (
-
- {schemes.map((security, index) => {
- const description = resolveDescription(security);
- return (
-
- {getLabelForType(security, context)}
- {description ? (
-
- ) : null}
- {security.scopes?.length ? (
-
- ) : null}
-
- );
- })}
-
- ),
- }))}
- />
+ <>
+
+ ({
+ key,
+ label,
+ body: (
+
+ {schemes.map((security, index) => {
+ const description = resolveDescription(security);
+ return (
+
+ {getLabelForType(security, context)}
+ {description ? (
+
+ ) : null}
+
+ );
+ })}
+
+ ),
+ }))}
+ />
+ >
);
}
@@ -175,9 +172,6 @@ function OpenAPISchemaOAuth2Item(props: {
return null;
}
- // If the security scheme has scopes, we don't need to display the scopes from the flow
- const scopes = !security.scopes?.length && flow.scopes ? Object.entries(flow.scopes) : [];
-
return (
) : null}
- {scopes.length ? : null}
);
}
-
-/**
- * Render a list of available scopes.
- */
-function OpenAPISchemaScopes(props: {
- scopes: OpenAPISecurityScope[];
- context: OpenAPIClientContext;
-}) {
- const { scopes, context } = props;
-
- return (
-
- );
-}
-
-/**
- * Display a scope item. Either a key-value pair or a single string.
- */
-function OpenAPIScopeItem(props: {
- scope: OpenAPISecurityScope;
- context: OpenAPIClientContext;
-}) {
- const { scope, context } = props;
-
- return (
-
- );
-}
-
-/**
- * Displays the scope name within a copyable button.
- */
-function OpenAPIScopeItemKey(props: {
- name: string;
- context: OpenAPIClientContext;
-}) {
- const { name, context } = props;
-
- return (
-
- );
-}
diff --git a/packages/react-openapi/src/context.ts b/packages/react-openapi/src/context.ts
index 76a6293ab6..9283489fc4 100644
--- a/packages/react-openapi/src/context.ts
+++ b/packages/react-openapi/src/context.ts
@@ -15,6 +15,7 @@ export interface OpenAPIClientContext {
plus: React.ReactNode;
copy: React.ReactNode;
check: React.ReactNode;
+ lock: React.ReactNode;
};
/**
diff --git a/packages/react-openapi/src/getOrCreateDisclosureStoreByKey.ts b/packages/react-openapi/src/getOrCreateDisclosureStoreByKey.ts
new file mode 100644
index 0000000000..f2c6603a6e
--- /dev/null
+++ b/packages/react-openapi/src/getOrCreateDisclosureStoreByKey.ts
@@ -0,0 +1,51 @@
+import type { Key } from 'react-stately';
+import { createStore } from 'zustand';
+
+type DisclosureState = {
+ expandedKeys: Set