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} tabs={samples}
overlay={ overlay={
data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : ( 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 * as React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { ApiClientModalProvider } from '@scalar/api-client-react';
import { OpenAPIOperationData } from './fetchOpenAPIOperation'; import { OpenAPIOperationData } from './fetchOpenAPIOperation';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
@@ -28,45 +27,40 @@ export function OpenAPIOperation(props: {
}; };
return ( return (
<ApiClientModalProvider <div className={classNames('openapi-operation', className)}>
configuration={{ spec: { url: context.specUrl } }} <div className="openapi-intro">
initialRequest={{ path: data.path, method: data.method }} <h2 className="openapi-summary" id={context.id}>
> {operation.summary}
<div className={classNames('openapi-operation', className)}> </h2>
<div className="openapi-intro"> {operation.description ? (
<h2 className="openapi-summary" id={context.id}> <Markdown className="openapi-description" source={operation.description} />
{operation.summary} ) : null}
</h2> <div className="openapi-target">
{operation.description ? ( <span
<Markdown className="openapi-description" source={operation.description} /> className={classNames(
) : null} 'openapi-method',
<div className="openapi-target"> `openapi-method-${method.toLowerCase()}`,
<span )}
className={classNames( >
'openapi-method', {method.toUpperCase()}
`openapi-method-${method.toLowerCase()}`, </span>
)} <span className="openapi-url">
> <OpenAPIServerURL servers={servers} />
{method.toUpperCase()} {path}
</span> </span>
<span className="openapi-url">
<OpenAPIServerURL servers={servers} />
{path}
</span>
</div>
</div> </div>
<div className={classNames('openapi-columns')}> </div>
<div className={classNames('openapi-column-spec')}> <div className={classNames('openapi-columns')}>
<OpenAPISpec data={data} context={clientContext} /> <div className={classNames('openapi-column-spec')}>
</div> <OpenAPISpec data={data} context={clientContext} />
<div className={classNames('openapi-column-preview')}> </div>
<div className={classNames('openapi-column-preview-body')}> <div className={classNames('openapi-column-preview')}>
<OpenAPICodeSample {...props} /> <div className={classNames('openapi-column-preview-body')}>
<OpenAPIResponseExample {...props} /> <OpenAPICodeSample {...props} />
</div> <OpenAPIResponseExample {...props} />
</div> </div>
</div> </div>
</div> </div>
</ApiClientModalProvider> </div>
); );
} }
+79 -7
View File
@@ -1,23 +1,33 @@
'use client'; 'use client';
import { useApiClientModal } from '@scalar/api-client-react'; import { ApiClientModalProvider, useApiClientModal } from '@scalar/api-client-react';
import React from 'react'; import React, { useImperativeHandle, useRef } from 'react';
import { createPortal } from 'react-dom';
import { useOpenAPIOperationContext } from './OpenAPIOperationContext'; import { useOpenAPIOperationContext } from './OpenAPIOperationContext';
import { useEventCallback } from 'usehooks-ts';
/** /**
* Button which launches the Scalar API Client * Button which launches the Scalar API Client
*/ */
export function ScalarApiButton({ method, path }: { method: string; path: string }) { export function ScalarApiButton({
const client = useApiClientModal(); method,
const { onOpenClient } = useOpenAPIOperationContext(); path,
specUrl,
}: {
method: string;
path: string;
specUrl: string;
}) {
const [isOpen, setIsOpen] = React.useState(false);
const controllerRef = useRef<ScalarModalControllerRef>(null);
return ( return (
<div className="scalar scalar-activate"> <div className="scalar scalar-activate">
<button <button
className="scalar-activate-button" className="scalar-activate-button"
onClick={() => { onClick={() => {
client?.open({ method, path }); controllerRef.current?.openClient?.();
onOpenClient({ method, path }); setIsOpen(true);
}} }}
> >
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="12" fill="none"> <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> </svg>
Test it Test it
</button> </button>
{isOpen &&
createPortal(
<ScalarModal
controllerRef={controllerRef}
method={method}
path={path}
specUrl={specUrl}
/>,
document.body,
)}
</div> </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;
}