diff --git a/packages/graphiql-react/src/icons/docs.svg b/packages/graphiql-react/src/icons/docs.svg
new file mode 100644
index 00000000000..4c2bf68a40c
--- /dev/null
+++ b/packages/graphiql-react/src/icons/docs.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/graphiql-react/src/icons/history.svg b/packages/graphiql-react/src/icons/history.svg
new file mode 100644
index 00000000000..3a632094812
--- /dev/null
+++ b/packages/graphiql-react/src/icons/history.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/graphiql-react/src/icons/index.tsx b/packages/graphiql-react/src/icons/index.tsx
index fad2253d041..182b6c0f379 100644
--- a/packages/graphiql-react/src/icons/index.tsx
+++ b/packages/graphiql-react/src/icons/index.tsx
@@ -1,9 +1,14 @@
import _ChevronDownIcon from './chevron-down.svg';
import _ChevronUpIcon from './chevron-up.svg';
import _CopyIcon from './copy.svg';
+import _DocsIcon from './docs.svg';
+import _HistoryIcon from './history.svg';
+import _KeyboardShortcutIcon from './keyboard-shortcut.svg';
import _MergeIcon from './merge.svg';
import _PlayIcon from './play.svg';
import _PrettifyIcon from './prettify.svg';
+import _ReloadIcon from './reload.svg';
+import _SettingsIcon from './settings.svg';
import _StopIcon from './stop.svg';
export const ChevronDownIcon = generateIcon(
@@ -12,9 +17,17 @@ export const ChevronDownIcon = generateIcon(
);
export const ChevronUpIcon = generateIcon(_ChevronUpIcon, 'chevron up icon');
export const CopyIcon = generateIcon(_CopyIcon, 'copy icon');
+export const DocsIcon = generateIcon(_DocsIcon, 'docs icon');
+export const HistoryIcon = generateIcon(_HistoryIcon, 'history icon');
+export const KeyboardShortcutIcon = generateIcon(
+ _KeyboardShortcutIcon,
+ 'keyboard shortcut icon',
+);
export const MergeIcon = generateIcon(_MergeIcon, 'merge icon');
export const PlayIcon = generateIcon(_PlayIcon, 'play icon');
export const PrettifyIcon = generateIcon(_PrettifyIcon, 'prettify icon');
+export const ReloadIcon = generateIcon(_ReloadIcon, 'reload icon');
+export const SettingsIcon = generateIcon(_SettingsIcon, 'settings icon');
export const StopIcon = generateIcon(_StopIcon, 'stop icon');
function generateIcon(RawComponent: any, title: string) {
diff --git a/packages/graphiql-react/src/icons/keyboard-shortcut.svg b/packages/graphiql-react/src/icons/keyboard-shortcut.svg
new file mode 100644
index 00000000000..8d192e164d1
--- /dev/null
+++ b/packages/graphiql-react/src/icons/keyboard-shortcut.svg
@@ -0,0 +1,7 @@
+
diff --git a/packages/graphiql-react/src/icons/reload.svg b/packages/graphiql-react/src/icons/reload.svg
new file mode 100644
index 00000000000..b0963c11a7e
--- /dev/null
+++ b/packages/graphiql-react/src/icons/reload.svg
@@ -0,0 +1,6 @@
+
diff --git a/packages/graphiql-react/src/icons/settings.svg b/packages/graphiql-react/src/icons/settings.svg
new file mode 100644
index 00000000000..f7cf68be0a1
--- /dev/null
+++ b/packages/graphiql-react/src/icons/settings.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/graphiql-react/src/style/root.css b/packages/graphiql-react/src/style/root.css
index 7b2ce696141..64c79c9dc7d 100644
--- a/packages/graphiql-react/src/style/root.css
+++ b/packages/graphiql-react/src/style/root.css
@@ -54,6 +54,7 @@
0px 0.399006px 1.33002px rgba(59, 76, 106, 0.0525061);
/* Layout */
+ --sidebar-width: 44px;
--toolbar-width: 40px;
}
@@ -61,6 +62,7 @@
.CodeMirror-info,
.CodeMirror-lint-tooltip,
.graphiql-container button {
+ color: var(--color-neutral-100);
font-family: var(--font-family);
font-size: var(--font-size-body);
font-weight: var(----font-weight-regular);
diff --git a/packages/graphiql-react/src/utility/resize.tsx b/packages/graphiql-react/src/utility/resize.tsx
index 43466fdc27c..5fc6f0f087f 100644
--- a/packages/graphiql-react/src/utility/resize.tsx
+++ b/packages/graphiql-react/src/utility/resize.tsx
@@ -145,12 +145,8 @@ export function useDragResize({
if (firstRef.current && storage && storageKey) {
const storedValue = storage?.get(storageKey);
- if (
- storedValue &&
- storedValue !== HIDE_FIRST &&
- storedValue !== HIDE_SECOND
- ) {
- firstRef.current.style.flex = storedValue;
+ if (storedValue !== HIDE_FIRST && storedValue !== HIDE_SECOND) {
+ firstRef.current.style.flex = storedValue || defaultFlexRef.current;
}
}
},
diff --git a/packages/graphiql/__mocks__/@graphiql/react.tsx b/packages/graphiql/__mocks__/@graphiql/react.tsx
index 775af04683e..4cfafe1d0ed 100644
--- a/packages/graphiql/__mocks__/@graphiql/react.tsx
+++ b/packages/graphiql/__mocks__/@graphiql/react.tsx
@@ -15,6 +15,7 @@ export {
ChevronDownIcon,
ChevronUpIcon,
CopyIcon,
+ DocsIcon,
Dropdown,
EditorContext,
EditorContextProvider,
@@ -25,13 +26,17 @@ export {
ExplorerContextProvider,
HistoryContext,
HistoryContextProvider,
+ HistoryIcon,
ImagePreview,
+ KeyboardShortcutIcon,
onHasCompletion,
MergeIcon,
PlayIcon,
PrettifyIcon,
+ ReloadIcon,
SchemaContext,
SchemaContextProvider,
+ SettingsIcon,
StopIcon,
StorageContext,
StorageContextProvider,
diff --git a/packages/graphiql/cypress/integration/docs.spec.ts b/packages/graphiql/cypress/integration/docs.spec.ts
index ad009ddfe46..e6f608154ab 100644
--- a/packages/graphiql/cypress/integration/docs.spec.ts
+++ b/packages/graphiql/cypress/integration/docs.spec.ts
@@ -5,7 +5,7 @@ describe('GraphiQL DocExplorer - button', () => {
cy.visit(`/`);
});
it('Toggles doc pane on', () => {
- cy.get('.docExplorerShow').click();
+ cy.get('.graphiql-sidebar button').eq(0).click();
cy.get('.doc-explorer').should('be.visible');
});
@@ -19,7 +19,7 @@ describe('GraphiQL DocExplorer - button', () => {
describe('GraphiQL DocExplorer - search', () => {
before(() => {
cy.visit(`/`);
- cy.get('.docExplorerShow').click();
+ cy.get('.graphiql-sidebar button').eq(0).click();
});
it('Searches docs for values', () => {
@@ -66,7 +66,7 @@ describe('GraphiQL DocExplorer - search', () => {
it('Allows clearing the search', () => {
cy.visit(`/`);
- cy.get('.docExplorerShow').click();
+ cy.get('.graphiql-sidebar button').eq(0).click();
cy.get('label.search-box input').type('test');
cy.get('.doc-category-item').should('have.length', 7);
cy.get('.search-box-clear').click();
@@ -78,7 +78,7 @@ describe('GraphiQL DocExplorer - search', () => {
describe('GraphQL DocExplorer - deprecated fields', () => {
before(() => {
cy.visit(`/`);
- cy.get('.docExplorerShow').click();
+ cy.get('.graphiql-sidebar button').eq(0).click();
});
it('should show deprecated fields category title', () => {
cy.get('.doc-category>.doc-category-item').first().find('a').click();
diff --git a/packages/graphiql/src/components/GraphiQL.tsx b/packages/graphiql/src/components/GraphiQL.tsx
index 9650be6bc8d..b4822f85d2b 100644
--- a/packages/graphiql/src/components/GraphiQL.tsx
+++ b/packages/graphiql/src/components/GraphiQL.tsx
@@ -25,6 +25,7 @@ import {
ChevronDownIcon,
ChevronUpIcon,
CopyIcon,
+ DocsIcon,
EditorContextProvider,
ExecuteButton,
ExecutionContextProvider,
@@ -32,11 +33,15 @@ import {
ExplorerContextProvider,
HeaderEditor,
HistoryContextProvider,
+ HistoryIcon,
+ KeyboardShortcutIcon,
MergeIcon,
PrettifyIcon,
QueryEditor,
+ ReloadIcon,
ResponseEditor,
SchemaContextProvider,
+ SettingsIcon,
StorageContextProvider,
ToolbarButton,
UnStyledButton,
@@ -64,7 +69,6 @@ import type {
KeyMap,
} from '@graphiql/react';
-import { ToolbarButton as LegacyToolbarButton } from './ToolbarButton';
import { ToolbarMenu, ToolbarMenuItem } from './ToolbarMenu';
import { DocExplorer } from './DocExplorer';
import { QueryHistory } from './QueryHistory';
@@ -535,11 +539,11 @@ const GraphiQLConsumeContexts = forwardRef<
const prettify = usePrettifyEditors();
const docResize = useDragResize({
- defaultSizeRelation: 3,
+ defaultSizeRelation: 1 / 3,
direction: 'horizontal',
- initiallyHidden: explorerContext?.isVisible ? undefined : 'second',
+ initiallyHidden: explorerContext?.isVisible ? undefined : 'first',
onHiddenElementChange: resizableElement => {
- if (resizableElement === 'second') {
+ if (resizableElement === 'first') {
explorerContext?.hide();
} else {
explorerContext?.show();
@@ -679,110 +683,151 @@ class GraphiQLWithContext extends React.Component<
return (
-
- {this.props.historyContext?.isVisible && (
-
-
-
- )}
-
-
- {this.props.beforeTopBarContent}
-
- {logo}
-
this.props.historyContext?.toggle()}
- title={
- this.props.historyContext?.isVisible
- ? 'Hide History'
- : 'Show History'
+
+
+ {this.props.explorerContext ? (
+ {
+ if (this.props.explorerContext?.isVisible) {
+ this.props.explorerContext?.hide();
+ this.props.docResize.setHiddenElement('first');
+ } else {
+ this.props.explorerContext?.show();
+ this.props.docResize.setHiddenElement(null);
}
- label="History"
- />
- this.props.schemaContext.introspect()}
- title="Fetch GraphQL schema using introspection (Shift-Ctrl-R)"
- label="Introspect"
- />
-
- {this.props.explorerContext &&
- !this.props.explorerContext.isVisible && (
-
- )}
+ }}
+ title={
+ this.props.explorerContext.isVisible
+ ? 'Hide Documentation Explorer'
+ : 'Show Documentation Explorer'
+ }>
+
+
+ ) : null}
+ {this.props.historyContext ? (
+
this.props.historyContext?.toggle()}
+ title={
+ this.props.historyContext.isVisible
+ ? 'Hide History'
+ : 'Show History'
+ }>
+
+
+ ) : null}
+
+
+ this.props.schemaContext.introspect()}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ this.props.docResize.setHiddenElement('first')}
+ />
- {this.props.tabs ? (
-
- {this.props.editorContext.tabs.map((tab, index) => (
- 1}
- onSelect={() => {
- this.props.executionContext.stop();
- this.props.editorContext.changeTab(index);
- }}
- onClose={() => {
- if (this.props.editorContext.activeTabIndex === index) {
- this.props.executionContext.stop();
+
+
+ {this.props.explorerContext?.isVisible ? (
+
+ ) : null}
+
+
+ {this.props.historyContext?.isVisible && (
+
+
+
+ )}
+
+
+ {this.props.beforeTopBarContent}
+
{logo}
+
+ {this.props.tabs ? (
+
+ {this.props.editorContext.tabs.map((tab, index) => (
+ 1}
+ onSelect={() => {
+ this.props.executionContext.stop();
+ this.props.editorContext.changeTab(index);
+ }}
+ onClose={() => {
+ if (this.props.editorContext.activeTabIndex === index) {
+ this.props.executionContext.stop();
+ }
+ this.props.editorContext.closeTab(index);
+ }}
+ tabProps={{
+ 'aria-controls': 'graphiql-session',
+ id: `session-tab-${index}`,
+ }}
+ />
+ ))}
+ {
+ this.props.editorContext.addTab();
}}
/>
- ))}
- {
- this.props.editorContext.addTab();
- }}
- />
-
- ) : null}
-
-
-
-
-
-
-
{
- if (
- this.props.docResize.hiddenElement === 'second'
- ) {
- this.props.docResize.setHiddenElement(null);
- }
- }}
- onCopyQuery={this.props.onCopyQuery}
- onEdit={this.props.onEditQuery}
- onEditOperationName={this.props.onEditOperationName}
- readOnly={this.props.readOnly}
- validationRules={this.props.validationRules}
- />
+
+ ) : null}
+
+
+
+
+
+
+ {
+ if (
+ this.props.docResize.hiddenElement === 'first'
+ ) {
+ this.props.docResize.setHiddenElement(null);
+ }
+ }}
+ onCopyQuery={this.props.onCopyQuery}
+ onEdit={this.props.onEditQuery}
+ onEditOperationName={this.props.onEditOperationName}
+ readOnly={this.props.readOnly}
+ validationRules={this.props.validationRules}
+ />
+
{toolbar}
-
-
-
-
-
-
- {
- if (
- this.props.editorToolsResize.hiddenElement ===
- 'second'
- ) {
- this.props.editorToolsResize.setHiddenElement(
- null,
- );
- }
- this.setState({
- activeSecondaryEditor: 'variable',
- });
- }}>
- Variables
-
- {this.props.headerEditorEnabled ? (
+
+
+
+
+
- Headers
+ Variables
- ) : null}
-
-
{
- if (
- this.props.editorToolsResize.hiddenElement ===
- 'second'
- ) {
- this.props.editorToolsResize.setHiddenElement(null);
- } else {
+ {headerEditorEnabled ? (
+ {
+ if (
+ this.props.editorToolsResize.hiddenElement ===
+ 'second'
+ ) {
+ this.props.editorToolsResize.setHiddenElement(
+ null,
+ );
+ }
+ this.setState({
+ activeSecondaryEditor: 'header',
+ });
+ }}>
+ Headers
+
+ ) : null}
+
+
{
this.props.editorToolsResize.setHiddenElement(
- 'second',
+ this.props.editorToolsResize.hiddenElement ===
+ 'second'
+ ? null
+ : 'second',
);
- }
- }}>
- {this.props.editorToolsResize.hiddenElement ===
- 'second' ? (
-
- ) : (
-
- )}
-
+ }}>
+ {this.props.editorToolsResize.hiddenElement ===
+ 'second' ? (
+
+ ) : (
+
+ )}
+
+
-
-
-
-
- {headerEditorEnabled && (
-
+
+ {headerEditorEnabled && (
+
+ )}
+
+
-
-
-
-
- {this.props.executionContext.isFetching ? (
-
- ) : null}
-
- {footer}
+
+
+
+ {this.props.executionContext.isFetching ? (
+
+ ) : null}
+
+ {footer}
+
-
-
-
- this.props.docResize.setHiddenElement('second')}
- />
-
-
);
}
diff --git a/packages/graphiql/src/components/ToolbarButton.tsx b/packages/graphiql/src/components/ToolbarButton.tsx
deleted file mode 100644
index 45d32da5671..00000000000
--- a/packages/graphiql/src/components/ToolbarButton.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * Copyright (c) 2021 GraphQL Contributors.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import React from 'react';
-
-type ToolbarButtonProps = {
- onClick: () => void;
- title: string;
- label: string;
-};
-
-type ToolbarButtonState = {
- error: Error | null;
-};
-
-/**
- * ToolbarButton
- *
- * A button to use within the Toolbar.
- */
-export class ToolbarButton extends React.Component<
- ToolbarButtonProps,
- ToolbarButtonState
-> {
- constructor(props: ToolbarButtonProps) {
- super(props);
- this.state = { error: null };
- }
-
- render() {
- const { error } = this.state;
- return (
-
- );
- }
-
- handleClick = () => {
- try {
- this.props.onClick();
- this.setState({ error: null });
- } catch (error) {
- if (error instanceof Error) {
- this.setState({ error });
- return;
- }
- throw error;
- }
- };
-}
diff --git a/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx b/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx
index 82bba6cc2e4..d0959f015cb 100644
--- a/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx
+++ b/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx
@@ -542,29 +542,29 @@ describe('GraphiQL', () => {
.spyOn(Element.prototype, 'getBoundingClientRect')
.mockReturnValue({ left: 0, right: 1200 });
- const { container, getByLabelText } = render(
-
,
- );
+ const { container } = render(
);
- fireEvent.click(getByLabelText(/Open Documentation Explorer/i));
- const docExplorerResizer = container.querySelector(
- '.docExplorerResizer',
- ) as Element;
+ fireEvent.click(
+ container.querySelector('[title="Show Documentation Explorer"]'),
+ );
+ const dragBar = container.querySelectorAll(
+ '.graphiql-horizontal-drag-bar',
+ )[0];
- fireEvent.mouseDown(docExplorerResizer, {
+ fireEvent.mouseDown(dragBar, {
clientX: 3,
});
- fireEvent.mouseMove(docExplorerResizer, {
+ fireEvent.mouseMove(dragBar, {
buttons: 1,
clientX: 800,
});
- fireEvent.mouseUp(docExplorerResizer);
+ fireEvent.mouseUp(dragBar);
// 797 / (1200 - 797) = 1.977667493796526
expect(
- container.querySelector('.editorWrap').parentElement.style.flex,
+ container.querySelector('.docExplorerWrap').parentElement.style.flex,
).toBe('1.977667493796526');
clientWidthSpy.mockRestore();
diff --git a/packages/graphiql/src/css/app.css b/packages/graphiql/src/css/app.css
index d0fd9ff8815..fc351be8ab2 100644
--- a/packages/graphiql/src/css/app.css
+++ b/packages/graphiql/src/css/app.css
@@ -1,19 +1,3 @@
-.graphiql-container,
-.graphiql-container button,
-.graphiql-container input {
- color: var(--color-neutral-100);
- font-size: 14px;
-}
-
-.graphiql-container {
- display: flex;
- flex-direction: row;
- height: 100%;
- margin: 0;
- overflow: hidden;
- width: 100%;
-}
-
.graphiql-container .editorWrap {
display: flex;
flex-direction: column;
@@ -102,14 +86,6 @@
z-index: 5;
}
-.graphiql-container .docExplorerResizer {
- cursor: col-resize;
- height: 100%;
- position: absolute;
- width: 10px;
- z-index: 10;
-}
-
.graphiql-container .docExplorerHide {
cursor: pointer;
font-size: 18px;
diff --git a/packages/graphiql/src/index.tsx b/packages/graphiql/src/index.tsx
index b559e0b5a30..b46f214e1b4 100644
--- a/packages/graphiql/src/index.tsx
+++ b/packages/graphiql/src/index.tsx
@@ -40,7 +40,6 @@ export { DocExplorer } from './components/DocExplorer';
* Toolbar
*/
export { ToolbarMenu, ToolbarMenuItem } from './components/ToolbarMenu';
-export { ToolbarButton } from './components/ToolbarButton';
export { ToolbarSelect, ToolbarSelectOption } from './components/ToolbarSelect';
/**
diff --git a/packages/graphiql/src/style.css b/packages/graphiql/src/style.css
index 839027ad6b8..a12eb4ec5e0 100644
--- a/packages/graphiql/src/style.css
+++ b/packages/graphiql/src/style.css
@@ -1,3 +1,44 @@
+/* Everything */
+.graphiql-container {
+ display: flex;
+ height: 100%;
+ margin: 0;
+ overflow: hidden;
+ width: 100%;
+}
+
+/* The sidebar */
+.graphiql-container .graphiql-sidebar {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ padding: var(--px-8);
+ width: var(--sidebar-width);
+}
+.graphiql-container .graphiql-sidebar button {
+ color: var(--color-neutral-60);
+ height: var(--sidebar-width);
+ width: var(--sidebar-width);
+}
+.graphiql-container .graphiql-sidebar button.active {
+ color: var(--color-neutral-100);
+}
+.graphiql-container .graphiql-sidebar button:not(:first-child) {
+ margin-top: var(--px-4);
+}
+.graphiql-container .graphiql-sidebar button > svg {
+ height: calc(var(--sidebar-width) - (2 * var(--px-12)));
+ padding: var(--px-12);
+ pointer-events: none;
+ width: calc(var(--sidebar-width) - (2 * var(--px-12)));
+}
+
+/* The main content, i.e. everything except the sidebar */
+.graphiql-container .graphiql-main {
+ display: flex;
+ flex: 1;
+}
+
/* The whole session, i.e. editors and response */
.graphiql-container .graphiql-session {
background-color: var(--color-neutral-7);
@@ -151,3 +192,16 @@
padding: var(--px-12);
width: var(--px-12);
}
+
+/* Generic spin animation */
+.graphiql-spin {
+ animation: spin 1s linear 0s infinite;
+}
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}