diff --git a/.changeset/tender-years-destroy.md b/.changeset/tender-years-destroy.md new file mode 100644 index 00000000000..2a739d665fb --- /dev/null +++ b/.changeset/tender-years-destroy.md @@ -0,0 +1,16 @@ +--- +'@graphiql/plugin-doc-explorer': minor +'@graphiql/plugin-history': minor +'@graphiql/react': minor +'graphiql': minor +--- + +- deprecate `useExplorerContext`, `useHistoryContext`, `usePrettifyEditors`, `useCopyQuery`, `useMergeQuery`, `useExecutionContext`, `usePluginContext`, `useSchemaContext`, `useStorageContext` hooks +- fix response editor overflow on `` +- export `GraphiQLProps` type +- allow `children: ReactNode` for `` +- change `ToolbarMenu` component: + - The `label` and `className` props were removed + - The `button` prop should now be a button element +- document `useGraphiQL` and `useGraphiQLActions` hooks in `@graphiql/react` README.md +- rename `useThemeStore` to `useTheme` diff --git a/docs/migration/graphiql-5.0.0.md b/docs/migration/graphiql-5.0.0.md index f2841495fb4..57fef2ce033 100644 --- a/docs/migration/graphiql-5.0.0.md +++ b/docs/migration/graphiql-5.0.0.md @@ -18,3 +18,25 @@ - `keyMap`. To use Vim or Emacs keybindings in Monaco, you can use community plugins. Monaco Vim: https://github.com/brijeshb42/monaco-vim. Monaco Emacs: https://github.com/aioutecism/monaco-emacs - `readOnly` - `validationRules`. Use custom GraphQL worker, see https://github.com/graphql/graphiql/tree/main/packages/monaco-graphql#custom-webworker-for-passing-non-static-config-to-worker.' + +## `@graphiql/react` + +The `ToolbarMenu` component has changed. + +- The `label` and `className` props were removed +- The `button` prop should now be a button element + + ```jsx + + + ``` diff --git a/packages/graphiql-plugin-doc-explorer/src/deprecated.ts b/packages/graphiql-plugin-doc-explorer/src/deprecated.ts new file mode 100644 index 00000000000..78425a4c9a7 --- /dev/null +++ b/packages/graphiql-plugin-doc-explorer/src/deprecated.ts @@ -0,0 +1,13 @@ +import { useDocExplorer, useDocExplorerActions } from './context'; + +/** + * @deprecated Use `useDocExplorerActions` and `useDocExplorer` hooks instead. + */ +export function useExplorerContext() { + const actions = useDocExplorerActions(); + const explorerNavStack = useDocExplorer(); + return { + ...actions, + explorerNavStack, + }; +} diff --git a/packages/graphiql-plugin-doc-explorer/src/index.ts b/packages/graphiql-plugin-doc-explorer/src/index.ts index 811d0603fb8..46ddae148d7 100644 --- a/packages/graphiql-plugin-doc-explorer/src/index.ts +++ b/packages/graphiql-plugin-doc-explorer/src/index.ts @@ -12,3 +12,4 @@ export type { DocExplorerNavStack, DocExplorerNavStackItem, } from './context'; +export * from './deprecated'; diff --git a/packages/graphiql-plugin-history/src/deprecated.ts b/packages/graphiql-plugin-history/src/deprecated.ts new file mode 100644 index 00000000000..b7c6c9e574f --- /dev/null +++ b/packages/graphiql-plugin-history/src/deprecated.ts @@ -0,0 +1,10 @@ +import { useHistory, useHistoryActions } from './context'; + +/** + * @deprecated Use `useHistoryActions` and `useHistory` hooks instead. + */ +export function useHistoryContext() { + const actions = useHistoryActions(); + const items = useHistory(); + return { ...actions, items }; +} diff --git a/packages/graphiql-plugin-history/src/index.ts b/packages/graphiql-plugin-history/src/index.ts index 1ea1e91b7b3..3c14c6a7154 100644 --- a/packages/graphiql-plugin-history/src/index.ts +++ b/packages/graphiql-plugin-history/src/index.ts @@ -12,3 +12,4 @@ export const HISTORY_PLUGIN: GraphiQLPlugin = { export { History }; export { HistoryStore, useHistory, useHistoryActions } from './context'; +export * from './deprecated'; diff --git a/packages/graphiql-react/README.md b/packages/graphiql-react/README.md index 586a7b38a96..51e0eeec5a2 100644 --- a/packages/graphiql-react/README.md +++ b/packages/graphiql-react/README.md @@ -74,31 +74,54 @@ 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 +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 stores +## Available Stores -There are multiple stores that own different parts of the state that make up a -complete GraphQL IDE. For each store there is a component -(`Store`) that makes sure the store is initialized and managed -properly. These components contains all the logic related to state management. +GraphiQL uses a set of state management stores, each responsible for a specific part of the IDE's +behavior. These stores contain all logic related to state management and can be accessed via custom +React hooks. -In addition, for each store, there is also a hook that -allows you to consume its current value: +### Core Hooks -- `useStorage`: Provides a storage API that can be used to persist state in - the browser (by default using `localStorage`) -- `useEditorStore`: Manages all the editors and tabs -- `useSchemaStore`: Fetches, validates and stores the GraphQL schema -- `useExecutionStore`: Executes GraphQL requests +- **`useStorage`**: Provides a storage API that can be used to persist state in the browser (by default using `localStorage`). +- **`useTheme`**: Manages the current theme and provides a method to update it. +- **`useGraphiQL`**: Access the current state. +- **`useGraphiQLActions`**: Trigger actions that mutate the state. This hook **never** rerenders. -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 +The `useGraphiQLActions` hook **exposes all actions** across store slices. +The `useGraphiQL` hook **provides access to the following store slices**: + +| Store Slice | Responsibilities | +| ----------- | -------------------------------------------------------------------------------- | +| `editor` | Manages **query**, **variables**, **headers**, and **response** editors and tabs | +| `execution` | Handles the execution of GraphQL requests | +| `plugin` | Manages plugins and the currently active plugin | +| `schema` | Fetches, validates, and stores the GraphQL schema | + +### Usage Example + +```js +import { useGraphiQL, useGraphiQLActions, useTheme } from '@graphiql/react'; + +// Get an action to fetch the schema +const { introspect } = useGraphiQLActions(); + +// Get the current theme and a method to change it +const { theme, setTheme } = useTheme(); + +// Or use a selector to access specific parts of the state +const schema = useGraphiQL(state => state.schema); +const currentTheme = useTheme(state => state.theme); +``` + +All store properties are documented using TSDoc 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). diff --git a/packages/graphiql-react/src/components/toolbar-menu/index.css b/packages/graphiql-react/src/components/toolbar-menu/index.css deleted file mode 100644 index d49a31bc41d..00000000000 --- a/packages/graphiql-react/src/components/toolbar-menu/index.css +++ /dev/null @@ -1,5 +0,0 @@ -button.graphiql-toolbar-menu { - display: block; - height: var(--toolbar-width); - width: var(--toolbar-width); -} diff --git a/packages/graphiql-react/src/components/toolbar-menu/index.tsx b/packages/graphiql-react/src/components/toolbar-menu/index.tsx index 1f7e6fec48c..4f9cf4ceede 100644 --- a/packages/graphiql-react/src/components/toolbar-menu/index.tsx +++ b/packages/graphiql-react/src/components/toolbar-menu/index.tsx @@ -1,34 +1,19 @@ import type { FC, ReactNode } from 'react'; -import { cn } from '../../utility'; -import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; +import { Trigger, type DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; import { DropdownMenu } from '../dropdown-menu'; -import { Tooltip } from '../tooltip'; -import './index.css'; -interface ToolbarMenuProps { +interface ToolbarMenuProps extends DropdownMenuProps { button: ReactNode; - label: string; } -const ToolbarMenuRoot: FC< - ToolbarMenuProps & { - children: ReactNode; - className?: string; - } & DropdownMenuProps -> = ({ button, children, label, ...props }) => { +const ToolbarMenuRoot: FC = ({ + button, + children, + ...props +}) => { return ( - - - {button} - - + {button} {children} ); diff --git a/packages/graphiql-react/src/deprecated.ts b/packages/graphiql-react/src/deprecated.ts new file mode 100644 index 00000000000..9faa27b9ac7 --- /dev/null +++ b/packages/graphiql-react/src/deprecated.ts @@ -0,0 +1,76 @@ +/* eslint-disable unicorn/prefer-export-from */ +import { useGraphiQL, useGraphiQLActions } from './components'; +import { pick } from './utility'; +import { useStorage } from './stores'; + +/** + * @deprecated Use `const { prettifyEditors } = useGraphiQLActions()` instead. + */ +export function usePrettifyEditors() { + const { prettifyEditors } = useGraphiQLActions(); + return prettifyEditors; +} + +/** + * @deprecated Use `const { copyQuery } = useGraphiQLActions()` instead. + */ +export function useCopyQuery() { + const { copyQuery } = useGraphiQLActions(); + return copyQuery; +} + +/** + * @deprecated Use `const { mergeQuery } = useGraphiQLActions()` instead. + */ +export function useMergeQuery() { + const { mergeQuery } = useGraphiQLActions(); + return mergeQuery; +} + +/** + * @deprecated Use `useGraphiQLActions` and `useGraphiQL` hooks instead. + */ +export function useExecutionContext() { + const { run, stop } = useGraphiQLActions(); + const values = useGraphiQL(state => ({ + isFetching: state.isIntrospecting, + isSubscribed: Boolean(state.subscription), + operationName: state.operationName, + })); + return { + run, + stop, + ...values, + }; +} + +/** + * @deprecated Use `useGraphiQLActions` and `useGraphiQL` hooks instead. + */ +export function usePluginContext() { + const { setVisiblePlugin } = useGraphiQLActions(); + const values = useGraphiQL(pick('plugins', 'visiblePlugin')); + return { + setVisiblePlugin, + ...values, + }; +} + +/** + * @deprecated Use `useGraphiQLActions` and `useGraphiQL` hooks instead. + */ +export function useSchemaContext() { + const { introspect } = useGraphiQLActions(); + const values = useGraphiQL( + pick('fetchError', 'isFetching', 'schema', 'validationErrors'), + ); + return { + introspect, + ...values, + }; +} + +/** + * @deprecated Use `const storage = useStorage()` instead. + */ +export const useStorageContext = useStorage; diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index dd3a7f039b3..bf030ad5b7f 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -1,6 +1,6 @@ import './style/root.css'; -export { useStorage, useThemeStore, type Theme } from './stores'; +export { useStorage, useTheme, type Theme } from './stores'; export * from './utility'; export type { TabsState } from './utility/tabs'; @@ -10,3 +10,4 @@ export * from './components'; export type { EditorProps, SchemaReference, SlicesWithActions } from './types'; export type { GraphiQLPlugin } from './stores/plugin'; export { KEY_MAP, formatShortcutForOS, isMacOs } from './constants'; +export * from './deprecated'; diff --git a/packages/graphiql-react/src/stores/index.ts b/packages/graphiql-react/src/stores/index.ts index 2f4a7c1c96d..aa3401e9cf2 100644 --- a/packages/graphiql-react/src/stores/index.ts +++ b/packages/graphiql-react/src/stores/index.ts @@ -23,4 +23,4 @@ export { type SchemaProps, } from './schema'; export { storageStore, useStorage } from './storage'; -export { themeStore, useThemeStore, type Theme } from './theme'; +export { themeStore, useTheme, type Theme } from './theme'; diff --git a/packages/graphiql-react/src/stores/storage.ts b/packages/graphiql-react/src/stores/storage.ts index 09ef0999f62..f4eafebe245 100644 --- a/packages/graphiql-react/src/stores/storage.ts +++ b/packages/graphiql-react/src/stores/storage.ts @@ -24,7 +24,7 @@ export const storageStore = createStore(() => ({ })); export const StorageStore: FC = ({ storage, children }) => { - const isMounted = useStorageStore(store => Boolean(store.storage)); + const isMounted = useStorageStore(state => Boolean(state.storage)); useEffect(() => { storageStore.setState({ storage: new StorageAPI(storage) }); @@ -40,4 +40,4 @@ export const StorageStore: FC = ({ storage, children }) => { const useStorageStore = createBoundedUseStore(storageStore); -export const useStorage = () => useStorageStore(store => store.storage); +export const useStorage = () => useStorageStore(state => state.storage); diff --git a/packages/graphiql-react/src/stores/theme.ts b/packages/graphiql-react/src/stores/theme.ts index 893b22cf2ec..eb90eb669b1 100644 --- a/packages/graphiql-react/src/stores/theme.ts +++ b/packages/graphiql-react/src/stores/theme.ts @@ -58,7 +58,7 @@ export const ThemeStore: FC = ({ defaultTheme = null, editorTheme = EDITOR_THEME, }) => { - const theme = useThemeStore(store => store.theme); + const theme = useTheme(state => state.theme); useEffect(() => { const { storage } = storageStore.getState(); @@ -104,4 +104,4 @@ function getSystemTheme() { return systemTheme; } -export const useThemeStore = createBoundedUseStore(themeStore); +export const useTheme = createBoundedUseStore(themeStore); diff --git a/packages/graphiql-react/src/style/codemirror.css b/packages/graphiql-react/src/style/codemirror.css index ac6add716b8..ad674f80748 100644 --- a/packages/graphiql-react/src/style/codemirror.css +++ b/packages/graphiql-react/src/style/codemirror.css @@ -1,10 +1,3 @@ -/* Set default background color */ -.graphiql-container .CodeMirror, -.graphiql-container .CodeMirror-gutters { - background: none; - background-color: var(--editor-background, hsl(var(--color-base))); -} - /* No padding around line numbers */ .graphiql-container .CodeMirror-linenumber { padding: 0; diff --git a/packages/graphiql-react/src/style/editor.css b/packages/graphiql-react/src/style/editor.css index 38a4760efbe..1a3df981ff1 100644 --- a/packages/graphiql-react/src/style/editor.css +++ b/packages/graphiql-react/src/style/editor.css @@ -24,11 +24,6 @@ } } -.graphiql-response .monaco-editor { - /* Add some padding so it doesn’t touch the tabs */ - padding-top: var(--px-16); -} - /* Make hover contents be dynamic */ .monaco-hover, .monaco-hover-content { diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index fdc63d33966..2f98cd197c3 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -26,11 +26,14 @@ "files": [ "dist", "!dist/e2e.*", - "!dist/workers/*" + "!dist/workers/*", + "!dist/index.umd.*", + "!dist/cdn.*" ], "exports": { "./package.json": "./package.json", "./style.css": "./dist/style.css", + "./graphiql.css": "./dist/style.css", ".": "./dist/index.js", "./setup-workers/*": { "types": "./dist/setup-workers/*.d.ts", diff --git a/packages/graphiql/src/index.ts b/packages/graphiql/src/index.ts index 1b1706a46a5..1e97ea3d857 100644 --- a/packages/graphiql/src/index.ts +++ b/packages/graphiql/src/index.ts @@ -8,6 +8,6 @@ */ import './style.css'; -export { GraphiQL } from './GraphiQL'; +export { GraphiQL, type GraphiQLProps } from './GraphiQL'; export { HISTORY_PLUGIN } from '@graphiql/plugin-history'; diff --git a/packages/graphiql/src/style.css b/packages/graphiql/src/style.css index bf9c3be22a1..d12583a6905 100644 --- a/packages/graphiql/src/style.css +++ b/packages/graphiql/src/style.css @@ -201,7 +201,8 @@ button.graphiql-tab-add { /* The response view */ .graphiql-container .graphiql-response { - --editor-background: transparent; + /* Add some padding so it doesn’t touch the tabs */ + padding-top: var(--px-16); display: flex; width: 100%; flex-direction: column; diff --git a/packages/graphiql/src/ui/sidebar.tsx b/packages/graphiql/src/ui/sidebar.tsx index 33eadab1522..17ff1bfa8a6 100644 --- a/packages/graphiql/src/ui/sidebar.tsx +++ b/packages/graphiql/src/ui/sidebar.tsx @@ -15,7 +15,7 @@ import { useGraphiQL, useGraphiQLActions, useStorage, - useThemeStore, + useTheme, VisuallyHidden, } from '@graphiql/react'; import { ShortKeys } from './short-keys'; @@ -55,8 +55,8 @@ export const Sidebar: FC = ({ const forcedTheme = $forcedTheme && THEMES.includes($forcedTheme) ? $forcedTheme : undefined; - const storageContext = useStorage(); - const { theme, setTheme } = useThemeStore(); + const storage = useStorage(); + const { theme, setTheme } = useTheme(); const { setShouldPersistHeaders, introspect, setVisiblePlugin } = useGraphiQLActions(); const { shouldPersistHeaders, isIntrospecting, visiblePlugin, plugins } = @@ -99,7 +99,7 @@ export const Sidebar: FC = ({ function handleClearData() { try { - storageContext.clear(); + storage.clear(); setClearStorageStatus('success'); } catch { setClearStorageStatus('error'); diff --git a/packages/graphiql/src/ui/toolbar.tsx b/packages/graphiql/src/ui/toolbar.tsx index 83ad1d917fb..20ebbc169cd 100644 --- a/packages/graphiql/src/ui/toolbar.tsx +++ b/packages/graphiql/src/ui/toolbar.tsx @@ -1,4 +1,4 @@ -import type { FC, ReactNode } from 'react'; +import type { FC, ReactElement, ReactNode } from 'react'; import { CopyIcon, KEY_MAP, @@ -24,14 +24,15 @@ const DefaultToolbarRenderProps: FC<{ * Configure the UI by providing this Component as a child of GraphiQL. */ export const GraphiQLToolbar: FC<{ - children?: typeof DefaultToolbarRenderProps; + children?: typeof DefaultToolbarRenderProps | ReactNode; }> = ({ children = DefaultToolbarRenderProps }) => { - if (typeof children !== 'function') { - throw new TypeError( - 'The `GraphiQL.Toolbar` component requires a render prop function as its child.', - ); - } + const isRenderProp = typeof children === 'function'; const { copyQuery, prettifyEditors, mergeQuery } = useGraphiQLActions(); + + if (!isRenderProp) { + return children as ReactElement; + } + const prettify = (