Load Scalar only when the button is pressed (#2790)

This commit is contained in:
Greg Bergé
2025-01-30 15:01:59 +01:00
committed by GitHub
parent 8cfa67c1dd
commit dff08ae3e3
4 changed files with 120 additions and 45 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Improve performances by loading Scalar API Client only when the button is clicked
@@ -123,7 +123,11 @@ export function OpenAPICodeSample(props: {
tabs={samples}
overlay={
data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
<ScalarApiButton method={data.method} path={data.path} />
<ScalarApiButton
method={data.method}
path={data.path}
specUrl={context.specUrl}
/>
)
}
/>
+31 -37
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import classNames from 'classnames';
import { ApiClientModalProvider } from '@scalar/api-client-react';
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
import { Markdown } from './Markdown';
@@ -28,45 +27,40 @@ export function OpenAPIOperation(props: {
};
return (
<ApiClientModalProvider
configuration={{ spec: { url: context.specUrl } }}
initialRequest={{ path: data.path, method: data.method }}
>
<div className={classNames('openapi-operation', className)}>
<div className="openapi-intro">
<h2 className="openapi-summary" id={context.id}>
{operation.summary}
</h2>
{operation.description ? (
<Markdown className="openapi-description" source={operation.description} />
) : null}
<div className="openapi-target">
<span
className={classNames(
'openapi-method',
`openapi-method-${method.toLowerCase()}`,
)}
>
{method.toUpperCase()}
</span>
<span className="openapi-url">
<OpenAPIServerURL servers={servers} />
{path}
</span>
</div>
<div className={classNames('openapi-operation', className)}>
<div className="openapi-intro">
<h2 className="openapi-summary" id={context.id}>
{operation.summary}
</h2>
{operation.description ? (
<Markdown className="openapi-description" source={operation.description} />
) : null}
<div className="openapi-target">
<span
className={classNames(
'openapi-method',
`openapi-method-${method.toLowerCase()}`,
)}
>
{method.toUpperCase()}
</span>
<span className="openapi-url">
<OpenAPIServerURL servers={servers} />
{path}
</span>
</div>
<div className={classNames('openapi-columns')}>
<div className={classNames('openapi-column-spec')}>
<OpenAPISpec data={data} context={clientContext} />
</div>
<div className={classNames('openapi-column-preview')}>
<div className={classNames('openapi-column-preview-body')}>
<OpenAPICodeSample {...props} />
<OpenAPIResponseExample {...props} />
</div>
</div>
<div className={classNames('openapi-columns')}>
<div className={classNames('openapi-column-spec')}>
<OpenAPISpec data={data} context={clientContext} />
</div>
<div className={classNames('openapi-column-preview')}>
<div className={classNames('openapi-column-preview-body')}>
<OpenAPICodeSample {...props} />
<OpenAPIResponseExample {...props} />
</div>
</div>
</div>
</ApiClientModalProvider>
</div>
);
}
+79 -7
View File
@@ -1,23 +1,33 @@
'use client';
import { useApiClientModal } from '@scalar/api-client-react';
import React from 'react';
import { ApiClientModalProvider, useApiClientModal } from '@scalar/api-client-react';
import React, { useImperativeHandle, useRef } from 'react';
import { createPortal } from 'react-dom';
import { useOpenAPIOperationContext } from './OpenAPIOperationContext';
import { useEventCallback } from 'usehooks-ts';
/**
* Button which launches the Scalar API Client
*/
export function ScalarApiButton({ method, path }: { method: string; path: string }) {
const client = useApiClientModal();
const { onOpenClient } = useOpenAPIOperationContext();
export function ScalarApiButton({
method,
path,
specUrl,
}: {
method: string;
path: string;
specUrl: string;
}) {
const [isOpen, setIsOpen] = React.useState(false);
const controllerRef = useRef<ScalarModalControllerRef>(null);
return (
<div className="scalar scalar-activate">
<button
className="scalar-activate-button"
onClick={() => {
client?.open({ method, path });
onOpenClient({ method, path });
controllerRef.current?.openClient?.();
setIsOpen(true);
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="12" fill="none">
@@ -29,6 +39,68 @@ export function ScalarApiButton({ method, path }: { method: string; path: string
</svg>
Test it
</button>
{isOpen &&
createPortal(
<ScalarModal
controllerRef={controllerRef}
method={method}
path={path}
specUrl={specUrl}
/>,
document.body,
)}
</div>
);
}
function ScalarModal(props: {
method: string;
path: string;
specUrl: string;
controllerRef: React.Ref<ScalarModalControllerRef>;
}) {
return (
<ApiClientModalProvider
configuration={{ spec: { url: props.specUrl } }}
initialRequest={{ path: props.path, method: props.method }}
>
<ScalarModalController
method={props.method}
path={props.path}
controllerRef={props.controllerRef}
/>
</ApiClientModalProvider>
);
}
type ScalarModalControllerRef = {
openClient: (() => void) | undefined;
};
function ScalarModalController(props: {
method: string;
path: string;
controllerRef: React.Ref<ScalarModalControllerRef>;
}) {
const client = useApiClientModal();
const openClient = client?.open;
useImperativeHandle(
props.controllerRef,
() => ({ openClient: openClient ? () => openClient() : undefined }),
[openClient],
);
// Open the client when the component is mounted.
const { onOpenClient } = useOpenAPIOperationContext();
const trackOpening = useEventCallback(() => {
onOpenClient({ method: props.method, path: props.path });
});
React.useEffect(() => {
if (openClient) {
openClient();
trackOpening();
}
}, [openClient]);
return null;
}