diff --git a/.changeset/smooth-countries-shout.md b/.changeset/smooth-countries-shout.md
new file mode 100644
index 00000000000..87e4ced66ac
--- /dev/null
+++ b/.changeset/smooth-countries-shout.md
@@ -0,0 +1,5 @@
+---
+'@graphiql/react': minor
+---
+
+Add `Spinner` component to `@graphiql/react`
diff --git a/packages/graphiql-react/src/ui/index.ts b/packages/graphiql-react/src/ui/index.ts
index 7ef3616b216..0399c089591 100644
--- a/packages/graphiql-react/src/ui/index.ts
+++ b/packages/graphiql-react/src/ui/index.ts
@@ -1,2 +1,3 @@
export * from './button';
export * from './dropdown';
+export * from './spinner';
diff --git a/packages/graphiql-react/src/ui/spinner.css b/packages/graphiql-react/src/ui/spinner.css
new file mode 100644
index 00000000000..40cb82a23f4
--- /dev/null
+++ b/packages/graphiql-react/src/ui/spinner.css
@@ -0,0 +1,27 @@
+.graphiql-spinner {
+ height: 56px;
+ margin: auto;
+ margin-top: var(--px-16);
+ width: 56px;
+
+ &::after {
+ animation: rotation 0.6s infinite linear;
+ border: 4px solid transparent;
+ border-radius: 100%;
+ border-top: 4px solid var(--color-neutral-40);
+ content: '';
+ display: inline-block;
+ height: 46px;
+ vertical-align: middle;
+ width: 46px;
+ }
+}
+
+@keyframes rotation {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(359deg);
+ }
+}
diff --git a/packages/graphiql-react/src/ui/spinner.tsx b/packages/graphiql-react/src/ui/spinner.tsx
new file mode 100644
index 00000000000..a70fff94fe1
--- /dev/null
+++ b/packages/graphiql-react/src/ui/spinner.tsx
@@ -0,0 +1,5 @@
+import './spinner.css';
+
+export function Spinner() {
+ return
;
+}
diff --git a/packages/graphiql/__mocks__/@graphiql/react.tsx b/packages/graphiql/__mocks__/@graphiql/react.tsx
index 1e00c64ccb2..0f65bb6ac1a 100644
--- a/packages/graphiql/__mocks__/@graphiql/react.tsx
+++ b/packages/graphiql/__mocks__/@graphiql/react.tsx
@@ -37,6 +37,7 @@ export {
SchemaContext,
SchemaContextProvider,
SettingsIcon,
+ Spinner,
StopIcon,
StorageContext,
StorageContextProvider,
@@ -52,7 +53,6 @@ export {
useMergeQuery,
usePrettifyEditors,
useSchemaContext,
- useSelectHistoryItem,
useStorageContext,
} from '@graphiql/react';
@@ -92,15 +92,20 @@ function useMockedEditor(
onEdit?: (newValue: string) => void,
) {
const editorContext = useEditorContext({ nonNull: true });
- const [code, setCode] = useState(
- value ?? editorContext[NAME_TO_INITIAL_VALUE[name]],
- );
+ const [code, setCode] = useState(() => {
+ const initialValueProp = NAME_TO_INITIAL_VALUE[name];
+ return (
+ value ??
+ (initialValueProp ? editorContext[initialValueProp] : undefined) ??
+ ''
+ );
+ });
const ref = useRef(null);
const setEditor =
editorContext[`set${name.slice(0, 1).toUpperCase()}${name.slice(1)}Editor`];
- const getValueRef = useRef<() => string>();
+ const getValueRef = useRef<() => string>(() => code);
useEffect(() => {
getValueRef.current = () => code;
}, [code]);
@@ -179,28 +184,28 @@ function useMockedEditor(
return ref;
}
-export const useHeaderEditor: typeof _useHeaderEditor = function useHeaderEditor({
- onEdit,
-}) {
- return useMockedEditor('header', undefined, onEdit);
+export const useHeaderEditor: typeof _useHeaderEditor = function useHeaderEditor(
+ props,
+) {
+ return useMockedEditor('header', undefined, props?.onEdit);
};
-export const useQueryEditor: typeof _useQueryEditor = function useQueryEditor({
- onEdit,
-}) {
- return useMockedEditor('query', undefined, onEdit);
+export const useQueryEditor: typeof _useQueryEditor = function useQueryEditor(
+ props,
+) {
+ return useMockedEditor('query', undefined, props?.onEdit);
};
-export const useResponseEditor: typeof _useResponseEditor = function useResponseEditor({
- value,
-}) {
- return useMockedEditor('response', value);
+export const useResponseEditor: typeof _useResponseEditor = function useResponseEditor(
+ props,
+) {
+ return useMockedEditor('response', props?.value);
};
-export const useVariableEditor: typeof _useVariableEditor = function useVariableEditor({
- onEdit,
-}) {
- return useMockedEditor('variable', undefined, onEdit);
+export const useVariableEditor: typeof _useVariableEditor = function useVariableEditor(
+ props,
+) {
+ return useMockedEditor('variable', undefined, props?.onEdit);
};
export const HeaderEditor: typeof _HeaderEditor = function HeaderEditor(props) {
diff --git a/packages/graphiql/src/components/DocExplorer.tsx b/packages/graphiql/src/components/DocExplorer.tsx
index 86205f9b778..865f920a829 100644
--- a/packages/graphiql/src/components/DocExplorer.tsx
+++ b/packages/graphiql/src/components/DocExplorer.tsx
@@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
-import React, { ReactNode } from 'react';
+import { Spinner, useExplorerContext, useSchemaContext } from '@graphiql/react';
import { GraphQLSchema, isType } from 'graphql';
-import { useExplorerContext, useSchemaContext } from '@graphiql/react';
+import React, { ReactNode } from 'react';
import FieldDoc from './DocExplorer/FieldDoc';
import SchemaDoc from './DocExplorer/SchemaDoc';
@@ -60,7 +60,7 @@ export function DocExplorer(props: DocExplorerProps) {
);
} else if (isFetching) {
// Schema is undefined when it is being loaded via introspection.
- content = ;
+ content = ;
} else if (!schema) {
// Schema is null when it explicitly does not exist, typically due to
// an error during introspection.
diff --git a/packages/graphiql/src/components/GraphiQL.tsx b/packages/graphiql/src/components/GraphiQL.tsx
index dcbd7032c89..52eb919edb3 100644
--- a/packages/graphiql/src/components/GraphiQL.tsx
+++ b/packages/graphiql/src/components/GraphiQL.tsx
@@ -42,6 +42,7 @@ import {
ResponseEditor,
SchemaContextProvider,
SettingsIcon,
+ Spinner,
StorageContextProvider,
ToolbarButton,
UnStyledButton,
@@ -958,7 +959,7 @@ class GraphiQLWithContext extends React.Component<
{this.props.executionContext.isFetching ? (
-
+
) : null}