diff --git a/.changeset/chilly-radios-tap.md b/.changeset/chilly-radios-tap.md new file mode 100644 index 00000000000..2957d34a3d5 --- /dev/null +++ b/.changeset/chilly-radios-tap.md @@ -0,0 +1,5 @@ +--- +'@graphiql/react': minor +--- + +BREAKING: The `onHasCompletion` export has been removed as it is only meant to be used internally. diff --git a/custom-words.txt b/custom-words.txt index 6039da2bc17..d70c152c6b9 100644 --- a/custom-words.txt +++ b/custom-words.txt @@ -196,6 +196,7 @@ paas // these pop up when writing "GraphQL___" qlapi qlid +qlide // other architecting diff --git a/packages/graphiql-react/README.md b/packages/graphiql-react/README.md index 0cca0a5f013..1d22c5fd9c3 100644 --- a/packages/graphiql-react/README.md +++ b/packages/graphiql-react/README.md @@ -1,7 +1,94 @@ [Changelog](https://github.com/graphql/graphiql/blob/main/packages/graphiql-react/CHANGELOG.md) | [API Docs](https://graphiql-test.netlify.app/typedoc/modules/graphiql_react.html) | [NPM](https://www.npmjs.com/package/@graphiql/react) -A react SDK for building graphql developer experiences for the web +# `@graphiql/react` -Used by `graphiql@2` and beyond! +A React SDK for building integrated GraphQL developer experiences for the web. -(docs coming soon!) +## Purpose + +This package contains a set of building blocks that allow its users to build GraphQL IDEs with ease. It's the set of components that make up Graph*i*QL, the first and official GraphQL IDE, owned and maintained by the GraphQL Foundation. + +There are two kinds of building blocks that this package provides: Stateful context providers for state management and simple UI components. + +## Getting started + +All the state for your GraphQL IDE lives in multiple contexts. The easiest way to get started is by using the `GraphiQLProvider` component that renders all the individual providers. + +There is one required prop called `fetcher`. This is a function that performs GraphQL request against a given endpoint. You can easily create a fetcher using the method `createGraphiQLFetcher` from the `@graphiql/toolkit` package. + +```jsx +import { GraphiQLProvider } from '@graphiql/react'; +import { createGraphiQLFetcher } from '@graphiql/toolkit'; + +const fetcher = createGraphiQLFetcher({ + url: 'https://my.graphql.api/graphql', +}); + +function MyGraphQLIDE() { + return ( + +
Hello GraphQL
+
+ ); +} +``` + +Inside the provider you can now use any UI component provided by `@graphiql/react`. For example, you can render a query editor like this: + +```jsx +import { QueryEditor } from '@graphiql/react'; + +function MyGraphQLIDE() { + return ( + +
+ +
+
+ ); +} +``` + +The package also ships the necessary CSS that all its UI components need. You can import them from `@graphiql/react/dist/style.css`. + +**Note**: In order for these styles to apply, the UI components need to be rendered inside an element that has a class name `graphiql-container`. + +By default the UI components will try to use the [Roboto](https://fonts.google.com/specimen/Roboto) font for regular text and the [Fira Code](https://fonts.google.com/specimen/Fira+Code) font for mono-space text. If you want to use the default fonts you can load them using these files: + +- `@graphiql/react/font/roboto.css` +- `@graphiql/react/font/fira-code.css`. + +You can of course use any other method to load these fonts (for example loading them from Google Fonts). + +Further details on how to use `@graphiql/react` can be found in the reference implementation of a GraphQL IDE - Graph*i*QL - in the [`graphiql` package](https://github.com/graphql/graphiql/blob/main/packages/graphiql/src/components/GraphiQL.tsx). + +## Available contexts + +There are multiple contexts that own different parts of the state that make up a complete GraphQL IDE. For each context there is a provider component (`ContextProvider`) that makes sure the context is initialized and managed properly. These components contains all the logic related to state management. In addition, for each context there is also a hook (`useContext`) that allows you to consume its current value. + +Here is a list of all contexts that come with `@graphiql/react` + +- `StorageContext`: Provides a storage API that can be used to persist state in the browser (by default using `localStorage`) +- `EditorContext`: Manages all the editors and tabs +- `SchemaContext`: Fetches, validates and stores the GraphQL schema +- `ExecutionContext`: Executes GraphQL requests +- `HistoryContext`: Persists executed requests in storage +- `ExplorerContext`: Handles the state for the docs explorer + +All context properties are documented using JSDoc comments. If you're using an IDE like VSCode for development these descriptions will show up in auto-complete tooltips. All these descriptions can also be found in the [API Docs](https://graphiql-test.netlify.app/typedoc/modules/graphiql_react.html). + +## Theming + +All the components from `@graphiql/react` have been designed with customization in mind. We achieve this using CSS variables. + +All variables that are available for customization can be found in the [`root.css` file](https://github.com/graphql/graphiql/blob/main/packages/graphiql-react/src/style/root.css). + +### Colors + +Colors are defined using the [HSL format](https://en.wikipedia.org/wiki/HSL_and_HSV). All CSS variables for colors are defined as a list of the three values that make up HSL (hue, saturation and lightness). + +This approach allows `@graphiql/react` to use transparent colors by passing the value of the CSS variable in the `hsla` function. This enables us to provide truly reusable UI elements where good contrasts are preserved regardless of the elements background. + +## Development + +If you want to develop with `@graphiql/react` locally - in particular when working on the `graphiql` package - all you need to do is run `yarn dev` in the package folder in a separate terminal. This will build the package using Vite. When using it in combination with `yarn start-graphiql` (running in the repo root) this will give you auto-reloading when working on `graphiql` and `@graphiql/react` simultaneously. diff --git a/packages/graphiql-react/package.json b/packages/graphiql-react/package.json index 67a5e70b205..d38ce7e5dff 100644 --- a/packages/graphiql-react/package.json +++ b/packages/graphiql-react/package.json @@ -27,9 +27,8 @@ "types" ], "scripts": { - "dev": "vite", - "build": "tsc --emitDeclarationOnly && vite build", - "preview": "vite preview" + "dev": "concurrently 'tsc --emitDeclarationOnly --watch' 'vite build --watch'", + "build": "tsc --emitDeclarationOnly && vite build" }, "peerDependencies": { "graphql": "^15.5.0 || ^16.0.0", diff --git a/packages/graphiql-react/src/editor/components/header-editor.tsx b/packages/graphiql-react/src/editor/components/header-editor.tsx index d59b9922294..f401d1ad41f 100644 --- a/packages/graphiql-react/src/editor/components/header-editor.tsx +++ b/packages/graphiql-react/src/editor/components/header-editor.tsx @@ -7,7 +7,13 @@ import '../style/codemirror.css'; import '../style/fold.css'; import '../style/editor.css'; -type HeaderEditorProps = UseHeaderEditorArgs & { isHidden?: boolean }; +type HeaderEditorProps = UseHeaderEditorArgs & { + /** + * Visually hide the header editor. + * @default false + */ + isHidden?: boolean; +}; export function HeaderEditor({ isHidden, ...hookArgs }: HeaderEditorProps) { const { headerEditor } = useEditorContext({ diff --git a/packages/graphiql-react/src/editor/components/variable-editor.tsx b/packages/graphiql-react/src/editor/components/variable-editor.tsx index 5a9e5d81680..2d50b619d06 100644 --- a/packages/graphiql-react/src/editor/components/variable-editor.tsx +++ b/packages/graphiql-react/src/editor/components/variable-editor.tsx @@ -10,6 +10,10 @@ import '../style/hint.css'; import '../style/editor.css'; type VariableEditorProps = UseVariableEditorArgs & { + /** + * Visually hide the header editor. + * @default false + */ isHidden?: boolean; }; diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 67997af044f..29168763048 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -34,35 +34,109 @@ export type CodeMirrorEditorWithOperationFacts = CodeMirrorEditor & { variableToType: VariableToType | null; }; -export type EditorContextType = { - activeTabIndex: number; - tabs: TabState[]; +export type EditorContextType = TabsState & { + /** + * Add a new tab. + */ addTab(): void; + /** + * Switch to a different tab. + * @param index The index of the tab that should be switched to. + */ changeTab(index: number): void; + /** + * Close a tab. If the currently active tab is closed the tab before it will + * become active. If there is no tab before the closed one, the tab after it + * will become active. + * @param index The index of the tab that should be closed. + */ closeTab(index: number): void; + /** + * Update the state for the tab that is currently active. This will be + * reflected in the `tabs` object and the state will be persisted in storage + * (if available). + * @param partialTab A partial tab state object that will override the + * current values. The properties `id`, `hash` and `title` cannot be changed. + */ updateActiveTabValues( partialTab: Partial>, ): void; + /** + * The CodeMirror editor instance for the headers editor. + */ headerEditor: CodeMirrorEditor | null; + /** + * The CodeMirror editor instance for the query editor. This editor also + * stores the operation facts that are derived from the current editor + * contents. + */ queryEditor: CodeMirrorEditorWithOperationFacts | null; + /** + * The CodeMirror editor instance for the response editor. + */ responseEditor: CodeMirrorEditor | null; + /** + * The CodeMirror editor instance for the variables editor. + */ variableEditor: CodeMirrorEditor | null; + /** + * Set the CodeMirror editor instance for the headers editor. + */ setHeaderEditor(newEditor: CodeMirrorEditor): void; + /** + * Set the CodeMirror editor instance for the query editor. + */ setQueryEditor(newEditor: CodeMirrorEditorWithOperationFacts): void; + /** + * Set the CodeMirror editor instance for the response editor. + */ setResponseEditor(newEditor: CodeMirrorEditor): void; + /** + * Set the CodeMirror editor instance for the variables editor. + */ setVariableEditor(newEditor: CodeMirrorEditor): void; + /** + * Changes the operation name and invokes the `onEditOperationName` callback. + */ setOperationName(operationName: string): void; + /** + * The contents of the headers editor when initially rendering the provider + * component. + */ initialHeaders: string; + /** + * The contents of the query editor when initially rendering the provider + * component. + */ initialQuery: string; + /** + * The contents of the response editor when initially rendering the provider + * component. + */ initialResponse: string; + /** + * The contents of the variables editor when initially rendering the provider + * component. + */ initialVariables: string; + /** + * A map of fragment definitions using the fragment name as key which are + * made available to include in the query. + */ externalFragments: Map; + /** + * A list of custom validation rules that are run in addition to the rules + * provided by the GraphQL spec. + */ validationRules: ValidationRule[]; + /** + * If the contents of the headers editor are persisted in storage. + */ shouldPersistHeaders: boolean; }; diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index 835c37f5645..47d8b32cdf6 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -89,11 +89,9 @@ export function useChangeHandler( ]); } -export type OnClickReference = (reference: SchemaReference) => void; - export function useCompletion( editor: CodeMirrorEditor | null, - callback: OnClickReference | null, + callback: ((reference: SchemaReference) => void) | null, caller: Function, ) { const { schema } = useSchemaContext({ nonNull: true, caller }); @@ -150,13 +148,20 @@ export function useKeyMap( }, [editor, keys, callback]); } -export function useCopyQuery({ - caller, - onCopyQuery, -}: { +export type UseCopyQueryArgs = { + /** + * This is only meant to be used internally in `@graphiql/react`. + */ caller?: Function; + /** + * Invoked when the current contents of the query editor are copied to the + * clipboard. + * @param query The content that has been copied. + */ onCopyQuery?: (query: string) => void; -} = {}) { +}; + +export function useCopyQuery({ caller, onCopyQuery }: UseCopyQueryArgs = {}) { const { queryEditor } = useEditorContext({ nonNull: true, caller: caller || useCopyQuery, @@ -173,7 +178,14 @@ export function useCopyQuery({ }, [queryEditor, onCopyQuery]); } -export function useMergeQuery({ caller }: { caller?: Function } = {}) { +type UseMergeQueryArgs = { + /** + * This is only meant to be used internally in `@graphiql/react`. + */ + caller?: Function; +}; + +export function useMergeQuery({ caller }: UseMergeQueryArgs = {}) { const { queryEditor } = useEditorContext({ nonNull: true, caller: caller || useMergeQuery, @@ -190,11 +202,14 @@ export function useMergeQuery({ caller }: { caller?: Function } = {}) { }, [queryEditor, schema]); } -export function usePrettifyEditors({ - caller, -}: { +type UsePrettifyEditorsArgs = { + /** + * This is only meant to be used internally in `@graphiql/react`. + */ caller?: Function; -} = {}) { +}; + +export function usePrettifyEditors({ caller }: UsePrettifyEditorsArgs = {}) { const { queryEditor, headerEditor, variableEditor } = useEditorContext({ nonNull: true, caller: caller || usePrettifyEditors, @@ -244,10 +259,23 @@ export function usePrettifyEditors({ }, [queryEditor, variableEditor, headerEditor]); } +export type UseAutoCompleteLeafsArgs = { + /** + * A function to determine which field leafs are automatically added when + * trying to execute a query with missing selection sets. It will be called + * with the `GraphQLType` for which fields need to be added. + */ + getDefaultFieldNames?: GetDefaultFieldNamesFn; + /** + * This is only meant to be used internally in `@graphiql/react`. + */ + caller?: Function; +}; + export function useAutoCompleteLeafs({ getDefaultFieldNames, caller, -}: { getDefaultFieldNames?: GetDefaultFieldNamesFn; caller?: Function } = {}) { +}: UseAutoCompleteLeafsArgs = {}) { const { schema } = useSchemaContext({ nonNull: true, caller: caller || useAutoCompleteLeafs, diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index 7e89fdfd731..a62201b24a5 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -1,4 +1,3 @@ -export { onHasCompletion } from './completion'; export { HeaderEditor, ImagePreview, diff --git a/packages/graphiql-react/src/editor/query-editor.tsx b/packages/graphiql-react/src/editor/query-editor.tsx index fce732d5e7d..c6e2c48f8b8 100644 --- a/packages/graphiql-react/src/editor/query-editor.tsx +++ b/packages/graphiql-react/src/editor/query-editor.tsx @@ -32,9 +32,9 @@ import { useEditorContext, } from './context'; import { - OnClickReference, useCompletion, useCopyQuery, + UseCopyQueryArgs, useKeyMap, useMergeQuery, usePrettifyEditors, @@ -47,21 +47,21 @@ import { } from './types'; import { normalizeWhitespace } from './whitespace'; -export type UseQueryEditorArgs = WriteableEditorProps & { - onClickReference?: OnClickReference; - /** - * Invoked when the current contents of the query editor are copied to the - * clipboard. - * @param query The content that has been copied. - */ - onCopyQuery?(query: string): void; - /** - * Invoked when the contents of the query editor change. - * @param value The new contents of the editor. - * @param documentAST The editor contents parsed into a GraphQL document. - */ - onEdit?(value: string, documentAST?: DocumentNode): void; -}; +export type UseQueryEditorArgs = WriteableEditorProps & + Pick & { + /** + * Invoked when a reference to the GraphQL schema (type or field) is clicked + * as part of the editor or one of its tooltips. + * @param reference The reference that has been clicked. + */ + onClickReference?(reference: SchemaReference): void; + /** + * Invoked when the contents of the query editor change. + * @param value The new contents of the editor. + * @param documentAST The editor contents parsed into a GraphQL document. + */ + onEdit?(value: string, documentAST?: DocumentNode): void; + }; export function useQueryEditor( { @@ -100,7 +100,9 @@ export function useQueryEditor( const ref = useRef(null); const codeMirrorRef = useRef(); - const onClickReferenceRef = useRef(() => {}); + const onClickReferenceRef = useRef< + NonNullable + >(() => {}); useEffect(() => { onClickReferenceRef.current = reference => { if (!explorer) { diff --git a/packages/graphiql-react/src/editor/variable-editor.tsx b/packages/graphiql-react/src/editor/variable-editor.tsx index 4089e552f84..c8970b8460e 100644 --- a/packages/graphiql-react/src/editor/variable-editor.tsx +++ b/packages/graphiql-react/src/editor/variable-editor.tsx @@ -1,3 +1,4 @@ +import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; import { useEffect, useRef } from 'react'; import { useExecutionContext } from '../execution'; @@ -9,7 +10,6 @@ import { } from './common'; import { useEditorContext } from './context'; import { - OnClickReference, useChangeHandler, useCompletion, useKeyMap, @@ -20,7 +20,12 @@ import { import { CodeMirrorType, WriteableEditorProps } from './types'; export type UseVariableEditorArgs = WriteableEditorProps & { - onClickReference?: OnClickReference; + /** + * Invoked when a reference to the GraphQL schema (type or field) is clicked + * as part of the editor or one of its tooltips. + * @param reference The reference that has been clicked. + */ + onClickReference?(reference: SchemaReference): void; /** * Invoked when the contents of the variables editor change. * @param value The new contents of the editor. diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index b8acc4579cf..3a1fccef431 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -3,7 +3,6 @@ import { FetcherResultPayload, formatError, formatResult, - GetDefaultFieldNamesFn, isAsyncIterable, isObservable, Unsubscribable, @@ -14,20 +13,38 @@ import { ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import setValue from 'set-value'; import { useAutoCompleteLeafs, useEditorContext } from './editor'; +import { UseAutoCompleteLeafsArgs } from './editor/hooks'; import { useHistoryContext } from './history'; import { createContextHook, createNullableContext } from './utility/context'; export type ExecutionContextType = { + /** + * If there is currently a GraphQL request in-flight. For long-running + * requests like subscriptions this will be `true` until the request is + * stopped manually. + */ isFetching: boolean; + /** + * The operation name that will be sent with all GraphQL requests. + */ operationName: string | null; + /** + * Start a GraphQL requests based of the current editor contents. + */ run(): void; + /** + * Stop the GraphQL request that is currently in-flight. + */ stop(): void; }; export const ExecutionContext = createNullableContext('ExecutionContext'); -export type ExecutionContextProviderProps = { +export type ExecutionContextProviderProps = Pick< + UseAutoCompleteLeafsArgs, + 'getDefaultFieldNames' +> & { children: ReactNode; /** * A function which accepts GraphQL HTTP parameters and returns a `Promise`, @@ -40,12 +57,6 @@ export type ExecutionContextProviderProps = { * @see {@link https://graphiql-test.netlify.app/typedoc/modules/graphiql_toolkit.html#creategraphiqlfetcher-2|`createGraphiQLFetcher`} */ fetcher: Fetcher; - /** - * A function to determine which field leafs are automatically added when - * trying to execute a query with missing selection sets. It will be called - * with the `GraphQLType` for which fields need to be added. - */ - getDefaultFieldNames?: GetDefaultFieldNamesFn; /** * This prop sets the operation name that is passed with a GraphQL request. */ diff --git a/packages/graphiql-react/src/explorer/components/argument.tsx b/packages/graphiql-react/src/explorer/components/argument.tsx index 10824e991cc..f38d127501a 100644 --- a/packages/graphiql-react/src/explorer/components/argument.tsx +++ b/packages/graphiql-react/src/explorer/components/argument.tsx @@ -7,8 +7,21 @@ import './argument.css'; import { MarkdownContent } from '../../ui'; type ArgumentProps = { + /** + * The argument that should be rendered. + */ arg: GraphQLArgument; + /** + * Toggle if the default value for the argument is shown (if there is one) + * @default false + */ showDefaultValue?: boolean; + /** + * Toggle whether to render the whole argument including description and + * deprecation reason (`false`) or to just render the argument name, type, + * and default value in a single line (`true`). + * @default false + */ inline?: boolean; }; diff --git a/packages/graphiql-react/src/explorer/components/default-value.tsx b/packages/graphiql-react/src/explorer/components/default-value.tsx index 44ee3ff8cb7..4e3cb0e83be 100644 --- a/packages/graphiql-react/src/explorer/components/default-value.tsx +++ b/packages/graphiql-react/src/explorer/components/default-value.tsx @@ -12,6 +12,9 @@ const printDefault = (ast?: ValueNode | null): string => { }; type DefaultValueProps = { + /** + * The field or argument for which to render the default value. + */ field: ExplorerFieldDef; }; diff --git a/packages/graphiql-react/src/explorer/components/deprecation-reason.tsx b/packages/graphiql-react/src/explorer/components/deprecation-reason.tsx index 3ad2f5f99ef..84a9170638c 100644 --- a/packages/graphiql-react/src/explorer/components/deprecation-reason.tsx +++ b/packages/graphiql-react/src/explorer/components/deprecation-reason.tsx @@ -2,7 +2,12 @@ import { MarkdownContent } from '../../ui'; import './deprecation-reason.css'; -type DeprecationReasonProps = { children?: string | null }; +type DeprecationReasonProps = { + /** + * The deprecation reason as markdown string. + */ + children?: string | null; +}; export function DeprecationReason(props: DeprecationReasonProps) { return props.children ? ( diff --git a/packages/graphiql-react/src/explorer/components/directive.tsx b/packages/graphiql-react/src/explorer/components/directive.tsx index 1ff48a3d3f4..087d83c585a 100644 --- a/packages/graphiql-react/src/explorer/components/directive.tsx +++ b/packages/graphiql-react/src/explorer/components/directive.tsx @@ -3,6 +3,9 @@ import { DirectiveNode } from 'graphql'; import './directive.css'; type DirectiveProps = { + /** + * The directive that should be rendered. + */ directive: DirectiveNode; }; diff --git a/packages/graphiql-react/src/explorer/components/doc-explorer.tsx b/packages/graphiql-react/src/explorer/components/doc-explorer.tsx index a582d89b7cc..6c7227c449c 100644 --- a/packages/graphiql-react/src/explorer/components/doc-explorer.tsx +++ b/packages/graphiql-react/src/explorer/components/doc-explorer.tsx @@ -76,9 +76,7 @@ export function DocExplorer() { {prevName} )} -
- {navItem.title || navItem.name} -
+
{navItem.name}
diff --git a/packages/graphiql-react/src/explorer/components/field-documentation.tsx b/packages/graphiql-react/src/explorer/components/field-documentation.tsx index a3ddfcfd386..655e37b9d64 100644 --- a/packages/graphiql-react/src/explorer/components/field-documentation.tsx +++ b/packages/graphiql-react/src/explorer/components/field-documentation.tsx @@ -9,7 +9,12 @@ import { Directive } from './directive'; import { ExplorerSection } from './section'; import { TypeLink } from './type-link'; -type FieldDocumentationProps = { field: ExplorerFieldDef }; +type FieldDocumentationProps = { + /** + * The field or argument that should be rendered. + */ + field: ExplorerFieldDef; +}; export function FieldDocumentation(props: FieldDocumentationProps) { return ( diff --git a/packages/graphiql-react/src/explorer/components/field-link.tsx b/packages/graphiql-react/src/explorer/components/field-link.tsx index 6a3d5753b19..09245b90cc0 100644 --- a/packages/graphiql-react/src/explorer/components/field-link.tsx +++ b/packages/graphiql-react/src/explorer/components/field-link.tsx @@ -3,6 +3,9 @@ import { ExplorerFieldDef, useExplorerContext } from '../context'; import './field-link.css'; type FieldLinkProps = { + /** + * The field or argument that should be linked to. + */ field: ExplorerFieldDef; }; diff --git a/packages/graphiql-react/src/explorer/components/schema-documentation.tsx b/packages/graphiql-react/src/explorer/components/schema-documentation.tsx index 2c5ea55bd5c..481dc87cf67 100644 --- a/packages/graphiql-react/src/explorer/components/schema-documentation.tsx +++ b/packages/graphiql-react/src/explorer/components/schema-documentation.tsx @@ -6,7 +6,12 @@ import { TypeLink } from './type-link'; import './schema-documentation.css'; -type SchemaDocumentationProps = { schema: GraphQLSchema }; +type SchemaDocumentationProps = { + /** + * The schema that should be rendered. + */ + schema: GraphQLSchema; +}; export function SchemaDocumentation(props: SchemaDocumentationProps) { const queryType = props.schema.getQueryType(); diff --git a/packages/graphiql-react/src/explorer/components/section.tsx b/packages/graphiql-react/src/explorer/components/section.tsx index 9d98fa19c2e..4071889a2a1 100644 --- a/packages/graphiql-react/src/explorer/components/section.tsx +++ b/packages/graphiql-react/src/explorer/components/section.tsx @@ -17,6 +17,10 @@ import './section.css'; type ExplorerSectionProps = { children: ReactNode; + /** + * The title of the section, which will also determine the icon rendered next + * to the headline. + */ title: | 'Root Types' | 'Fields' diff --git a/packages/graphiql-react/src/explorer/components/type-documentation.tsx b/packages/graphiql-react/src/explorer/components/type-documentation.tsx index 03aff28649f..205ab71a3b5 100644 --- a/packages/graphiql-react/src/explorer/components/type-documentation.tsx +++ b/packages/graphiql-react/src/explorer/components/type-documentation.tsx @@ -22,7 +22,12 @@ import { TypeLink } from './type-link'; import './type-documentation.css'; -type TypeDocumentationProps = { type: GraphQLNamedType }; +type TypeDocumentationProps = { + /** + * The type that should be rendered. + */ + type: GraphQLNamedType; +}; export function TypeDocumentation(props: TypeDocumentationProps) { return isNamedType(props.type) ? ( diff --git a/packages/graphiql-react/src/explorer/components/type-link.tsx b/packages/graphiql-react/src/explorer/components/type-link.tsx index 78c75d2c4cf..a472fa7dbdc 100644 --- a/packages/graphiql-react/src/explorer/components/type-link.tsx +++ b/packages/graphiql-react/src/explorer/components/type-link.tsx @@ -6,6 +6,9 @@ import { renderType } from './utils'; import './type-link.css'; type TypeLinkProps = { + /** + * The type that should be linked to. + */ type: GraphQLType; }; diff --git a/packages/graphiql-react/src/explorer/context.tsx b/packages/graphiql-react/src/explorer/context.tsx index ba6f43723d9..e380594b70e 100644 --- a/packages/graphiql-react/src/explorer/context.tsx +++ b/packages/graphiql-react/src/explorer/context.tsx @@ -23,8 +23,14 @@ export type ExplorerFieldDef = | GraphQLArgument; export type ExplorerNavStackItem = { + /** + * The name of the item. + */ name: string; - title?: string; + /** + * The definition object of the item, this can be a named type, a field, an + * input field or an argument. + */ def?: GraphQLNamedType | ExplorerFieldDef; }; @@ -34,18 +40,39 @@ export type ExplorerNavStack = [ ...ExplorerNavStackItem[], ]; -const initialNavStackItem: ExplorerNavStackItem = { - name: 'Schema', - title: 'Docs', -}; +const initialNavStackItem: ExplorerNavStackItem = { name: 'Docs' }; export type ExplorerContextType = { + /** + * A stack of navigation items. The last item in the list is the current one. + * This list always contains at least one item. + */ explorerNavStack: ExplorerNavStack; + /** + * Hide the doc explorer. + */ hide(): void; + /** + * If the doc explorer should be shown. + */ isVisible: boolean; + /** + * Push an item to the navigation stack. + * @param item The item that should be pushed to the stack. + */ push(item: ExplorerNavStackItem): void; + /** + * Pop the last item from the navigation stack. + */ pop(): void; + /** + * Reset the navigation stack to its initial state, this will remove all but + * the initial stack item. + */ reset(): void; + /** + * Show the doc explorer. + */ show(): void; }; diff --git a/packages/graphiql-react/src/history/context.tsx b/packages/graphiql-react/src/history/context.tsx index 97cd4785d30..15200407fc6 100644 --- a/packages/graphiql-react/src/history/context.tsx +++ b/packages/graphiql-react/src/history/context.tsx @@ -5,12 +5,24 @@ import { useStorageContext } from '../storage'; import { createContextHook, createNullableContext } from '../utility/context'; export type HistoryContextType = { + /** + * Add an operation to the history. + * @param operation The operation that was executed, consisting of the query, + * variables, headers and the operation name. + */ addToHistory(operation: { query?: string; variables?: string; headers?: string; operationName?: string; }): void; + /** + * Change the custom label of an item from the history. + * @param args An object containing the label (`undefined` if it should be + * unset) and properties that identify the history item that the label should + * be applied to. (This can result in the label being applied to multiple + * history items.) + */ editLabel(args: { query?: string; variables?: string; @@ -19,11 +31,33 @@ export type HistoryContextType = { label?: string; favorite?: boolean; }): void; + /** + * Hide the history. + */ hide(): void; + /** + * If the history should be shown. + */ isVisible: boolean; + /** + * The list of history items. + */ items: readonly QueryStoreItem[]; + /** + * Show the history. + */ show(): void; + /** + * Toggle the visibility state of the history. + */ toggle(): void; + /** + * Toggle the favorite state of an item from the history. + * @param args An object containing the favorite state (`undefined` if it + * should be unset) and properties that identify the history item that the + * label should be applied to. (This can result in the label being applied + * to multiple history items.) + */ toggleFavorite(args: { query?: string; variables?: string; diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 80872755eda..bb8fe798df5 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -5,7 +5,6 @@ export { EditorContextProvider, HeaderEditor, ImagePreview, - onHasCompletion, QueryEditor, ResponseEditor, useAutoCompleteLeafs, diff --git a/packages/graphiql-react/src/schema.tsx b/packages/graphiql-react/src/schema.tsx index c082b932b1a..154d780c70b 100644 --- a/packages/graphiql-react/src/schema.tsx +++ b/packages/graphiql-react/src/schema.tsx @@ -30,10 +30,32 @@ import { createContextHook, createNullableContext } from './utility/context'; type MaybeGraphQLSchema = GraphQLSchema | null | undefined; export type SchemaContextType = { + /** + * Stores an error raised during introspecting or building the GraphQL schema + * from the introspection result. + */ fetchError: string | null; + /** + * Trigger building the GraphQL schema. This might trigger an introspection + * request if no schema is passed via props and if using a schema is not + * explicitly disabled by passing `null` as value for the `schema` prop. If + * there is a schema (either fetched using introspection or passed via props) + * it will be validated, unless this is explicitly skipped using the + * `dangerouslyAssumeSchemaIsValid` prop. + */ introspect(): void; + /** + * If there currently is an introspection request in-flight. + */ isFetching: boolean; + /** + * The current GraphQL schema. + */ schema: MaybeGraphQLSchema; + /** + * A list of errors from validating the current GraphQL schema. The schema is + * valid if and only if this list is empty. + */ validationErrors: readonly GraphQLError[]; }; diff --git a/packages/graphiql-react/src/utility/resize.tsx b/packages/graphiql-react/src/utility/resize.tsx index ca3d6f4ade8..83c17986bf0 100644 --- a/packages/graphiql-react/src/utility/resize.tsx +++ b/packages/graphiql-react/src/utility/resize.tsx @@ -13,12 +13,39 @@ import debounce from './debounce'; type ResizableElement = 'first' | 'second'; type UseDragResizeArgs = { + /** + * Set the default sizes for the two resizable halves by passing their ratio + * (first divided by second). + */ defaultSizeRelation?: number; + /** + * The direction in which the two halves should be resizable. + */ direction: 'horizontal' | 'vertical'; + /** + * Choose one of the two halves that should initially be hidden. + */ initiallyHidden?: ResizableElement; + /** + * Invoked when the visibility of one of the halves changes. + * @param hiddenElement The element that is now hidden after the change + * (`null` if both are visible). + */ onHiddenElementChange?(hiddenElement: ResizableElement | null): void; + /** + * The minimum width in pixels for the first half. If it is resized to a + * width smaller than this threshold, the half will be hidden. + */ sizeThresholdFirst?: number; + /** + * The minimum width in pixels for the second half. If it is resized to a + * width smaller than this threshold, the half will be hidden. + */ sizeThresholdSecond?: number; + /** + * A key for which the state of resizing is persisted in storage (if storage + * is available). + */ storageKey?: string; }; @@ -33,12 +60,13 @@ export function useDragResize({ }: UseDragResizeArgs) { const storage = useStorageContext(); - const store = useCallback( - debounce(500, (value: string) => { - if (storage && storageKey) { - storage.set(storageKey, value); - } - }), + const store = useMemo( + () => + debounce(500, (value: string) => { + if (storage && storageKey) { + storage.set(storageKey, value); + } + }), [storage, storageKey], ); diff --git a/packages/graphiql/__mocks__/@graphiql/react.tsx b/packages/graphiql/__mocks__/@graphiql/react.tsx index 5e06351c4f0..1dc8242fd0d 100644 --- a/packages/graphiql/__mocks__/@graphiql/react.tsx +++ b/packages/graphiql/__mocks__/@graphiql/react.tsx @@ -56,7 +56,6 @@ export { MarkdownContent, Menu, MergeIcon, - onHasCompletion, PenIcon, PlayIcon, PlusIcon, diff --git a/packages/graphiql/src/components/GraphiQL.tsx b/packages/graphiql/src/components/GraphiQL.tsx index 5afef4f20f8..a9bc62ce74c 100644 --- a/packages/graphiql/src/components/GraphiQL.tsx +++ b/packages/graphiql/src/components/GraphiQL.tsx @@ -73,6 +73,11 @@ if (majorVersion < 16) { } export type GraphiQLToolbarConfig = { + /** + * This content will be rendered after the built-in buttons of the toolbar. + * Note that this will not apply if you provide a completely custom toolbar + * (by passing `GraphiQL.Toolbar` as child to the `GraphiQL` component). + */ additionalContent?: React.ReactNode; }; @@ -188,7 +193,8 @@ export type GraphiQLInterfaceProps = WriteableEditorProps & */ isHeadersEditorEnabled?: boolean; /** - * Custom toolbar configuration + * An object that allows configuration of the toolbar next to the query + * editor. */ toolbar?: GraphiQLToolbarConfig; };