From 6a86d0b1b91cdb59029a1134c5c78916248f8087 Mon Sep 17 00:00:00 2001 From: Thomas Heyenbrock Date: Wed, 6 Jul 2022 10:22:47 +0200 Subject: [PATCH] [redesign] implement new toolbar design (#2524) * implement new toolbar design * add `caller` argument for better errors --- .changeset/real-waves-enjoy.md | 25 ++++ .../src/editor/style/editor.css | 1 + packages/graphiql-react/src/icons/copy.svg | 4 + packages/graphiql-react/src/icons/index.tsx | 10 ++ packages/graphiql-react/src/icons/merge.svg | 6 + packages/graphiql-react/src/icons/play.svg | 3 + .../graphiql-react/src/icons/prettify.svg | 7 ++ packages/graphiql-react/src/icons/stop.svg | 3 + packages/graphiql-react/src/index.ts | 1 + packages/graphiql-react/src/style/root.css | 5 + .../graphiql-react/src/toolbar/button.css | 13 ++ .../graphiql-react/src/toolbar/button.tsx | 33 +++++ .../graphiql-react/src/toolbar/execute.css | 30 +++++ .../src/toolbar/execute.tsx} | 42 ++++--- packages/graphiql-react/src/toolbar/index.ts | 2 + packages/graphiql-react/src/ui/dropdown.css | 30 +++++ packages/graphiql-react/src/ui/dropdown.tsx | 21 ++++ packages/graphiql-react/src/ui/index.ts | 1 + .../graphiql/__mocks__/@graphiql/react.tsx | 57 ++------- .../graphiql/cypress/integration/tabs.spec.ts | 4 +- packages/graphiql/cypress/support/commands.ts | 2 +- packages/graphiql/src/components/GraphiQL.tsx | 113 +++++++++--------- .../graphiql/src/components/ToolbarGroup.tsx | 21 ---- .../components/__tests__/GraphiQL.spec.tsx | 19 +-- packages/graphiql/src/css/app.css | 89 -------------- packages/graphiql/src/index.tsx | 1 - packages/graphiql/src/style.css | 33 ++++- 27 files changed, 329 insertions(+), 247 deletions(-) create mode 100644 .changeset/real-waves-enjoy.md create mode 100644 packages/graphiql-react/src/icons/copy.svg create mode 100644 packages/graphiql-react/src/icons/merge.svg create mode 100644 packages/graphiql-react/src/icons/play.svg create mode 100644 packages/graphiql-react/src/icons/prettify.svg create mode 100644 packages/graphiql-react/src/icons/stop.svg create mode 100644 packages/graphiql-react/src/toolbar/button.css create mode 100644 packages/graphiql-react/src/toolbar/button.tsx create mode 100644 packages/graphiql-react/src/toolbar/execute.css rename packages/{graphiql/src/components/ExecuteButton.tsx => graphiql-react/src/toolbar/execute.tsx} (80%) create mode 100644 packages/graphiql-react/src/toolbar/index.ts create mode 100644 packages/graphiql-react/src/ui/dropdown.css create mode 100644 packages/graphiql-react/src/ui/dropdown.tsx delete mode 100644 packages/graphiql/src/components/ToolbarGroup.tsx diff --git a/.changeset/real-waves-enjoy.md b/.changeset/real-waves-enjoy.md new file mode 100644 index 00000000000..f729f5fba94 --- /dev/null +++ b/.changeset/real-waves-enjoy.md @@ -0,0 +1,25 @@ +--- +'graphiql': major +--- + +BREAKING: The following static properties of the `GraphiQL` component have been removed: +- `GraphiQL.Button`: You can use the `ToolbarButton` component from `@graphiql/react` instead. +- `GraphiQL.ToolbarButton`: This exposed the same component as `GraphiQL.Button`. +- `GraphiQL.Group`: Grouping multiple buttons side-by-side is not provided out-of-the box anymore in the new GraphiQL UI. If you want to implement a similar feature in the new vertical toolbar you can do so by adding your own styles for your custom toolbar elements. Example: + ```jsx + import { GraphiQL } from "graphiql"; + function CustomGraphiQL() { + return ( + + + {/* Add custom styles for your buttons using the given class */} +
+ + + +
+
+
+ ); + } + ``` diff --git a/packages/graphiql-react/src/editor/style/editor.css b/packages/graphiql-react/src/editor/style/editor.css index abc20c7d9bc..c717d555971 100644 --- a/packages/graphiql-react/src/editor/style/editor.css +++ b/packages/graphiql-react/src/editor/style/editor.css @@ -1,6 +1,7 @@ .graphiql-editor { height: 100%; position: relative; + width: 100%; &.hidden { /* Just setting `display: none;` would break the editor gutters */ diff --git a/packages/graphiql-react/src/icons/copy.svg b/packages/graphiql-react/src/icons/copy.svg new file mode 100644 index 00000000000..c18b6e78a00 --- /dev/null +++ b/packages/graphiql-react/src/icons/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/graphiql-react/src/icons/index.tsx b/packages/graphiql-react/src/icons/index.tsx index 7ac6f12bb42..fad2253d041 100644 --- a/packages/graphiql-react/src/icons/index.tsx +++ b/packages/graphiql-react/src/icons/index.tsx @@ -1,11 +1,21 @@ import _ChevronDownIcon from './chevron-down.svg'; import _ChevronUpIcon from './chevron-up.svg'; +import _CopyIcon from './copy.svg'; +import _MergeIcon from './merge.svg'; +import _PlayIcon from './play.svg'; +import _PrettifyIcon from './prettify.svg'; +import _StopIcon from './stop.svg'; export const ChevronDownIcon = generateIcon( _ChevronDownIcon, 'chevron down icon', ); export const ChevronUpIcon = generateIcon(_ChevronUpIcon, 'chevron up icon'); +export const CopyIcon = generateIcon(_CopyIcon, 'copy icon'); +export const MergeIcon = generateIcon(_MergeIcon, 'merge icon'); +export const PlayIcon = generateIcon(_PlayIcon, 'play icon'); +export const PrettifyIcon = generateIcon(_PrettifyIcon, 'prettify icon'); +export const StopIcon = generateIcon(_StopIcon, 'stop icon'); function generateIcon(RawComponent: any, title: string) { const WithTitle = function IconComponent( diff --git a/packages/graphiql-react/src/icons/merge.svg b/packages/graphiql-react/src/icons/merge.svg new file mode 100644 index 00000000000..c4db221b4d8 --- /dev/null +++ b/packages/graphiql-react/src/icons/merge.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/graphiql-react/src/icons/play.svg b/packages/graphiql-react/src/icons/play.svg new file mode 100644 index 00000000000..9f194110940 --- /dev/null +++ b/packages/graphiql-react/src/icons/play.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/graphiql-react/src/icons/prettify.svg b/packages/graphiql-react/src/icons/prettify.svg new file mode 100644 index 00000000000..bcc1740d24d --- /dev/null +++ b/packages/graphiql-react/src/icons/prettify.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/graphiql-react/src/icons/stop.svg b/packages/graphiql-react/src/icons/stop.svg new file mode 100644 index 00000000000..02d9a7b321a --- /dev/null +++ b/packages/graphiql-react/src/icons/stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 68e0533c214..4582b6c697d 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -44,6 +44,7 @@ export { StorageContextProvider, useStorageContext, } from './storage'; +export * from './toolbar'; export * from './ui'; export { useDragResize } from './utility/resize'; diff --git a/packages/graphiql-react/src/style/root.css b/packages/graphiql-react/src/style/root.css index fbe4673e1b3..7b2ce696141 100644 --- a/packages/graphiql-react/src/style/root.css +++ b/packages/graphiql-react/src/style/root.css @@ -18,8 +18,10 @@ --color-neutral-0: #ffffff; --color-pink-background: rgb(214, 6, 144, 0.1); + --color-pink-dark: #ab0573; --color-orche-background: rgba(211, 127, 0, 0.07); --color-orche-background-dark: rgba(211, 127, 0, 0.12); + --color-red-background: rgba(248, 91, 48, 0.12); /* Font */ --font-family: 'Roboto', sans-serif; @@ -50,6 +52,9 @@ --box-shadow: 0px 6px 20px rgba(59, 76, 106, 0.13), 0px 1.34018px 4.46726px rgba(59, 76, 106, 0.0774939), 0px 0.399006px 1.33002px rgba(59, 76, 106, 0.0525061); + + /* Layout */ + --toolbar-width: 40px; } .graphiql-container, diff --git a/packages/graphiql-react/src/toolbar/button.css b/packages/graphiql-react/src/toolbar/button.css new file mode 100644 index 00000000000..83baae4c3c7 --- /dev/null +++ b/packages/graphiql-react/src/toolbar/button.css @@ -0,0 +1,13 @@ +button.graphiql-toolbar-button { + display: block; + height: var(--toolbar-width); + width: var(--toolbar-width); + + &.error { + background: var(--color-red-background); + } + + & > svg { + pointer-events: none; + } +} diff --git a/packages/graphiql-react/src/toolbar/button.tsx b/packages/graphiql-react/src/toolbar/button.tsx new file mode 100644 index 00000000000..02a859df8e6 --- /dev/null +++ b/packages/graphiql-react/src/toolbar/button.tsx @@ -0,0 +1,33 @@ +import { useState } from 'react'; + +import { UnStyledButton } from '../ui'; + +import './button.css'; + +export function ToolbarButton(props: JSX.IntrinsicElements['button']) { + const [error, setError] = useState(null); + return ( + { + try { + props.onClick?.(event); + setError(null); + } catch (err) { + setError( + err instanceof Error + ? err + : new Error(`Toolbar button click failed: ${err}`), + ); + } + }} + title={error ? error.message : props.title} + aria-invalid={error ? 'true' : props['aria-invalid']} + /> + ); +} diff --git a/packages/graphiql-react/src/toolbar/execute.css b/packages/graphiql-react/src/toolbar/execute.css new file mode 100644 index 00000000000..7c43395e52b --- /dev/null +++ b/packages/graphiql-react/src/toolbar/execute.css @@ -0,0 +1,30 @@ +.graphiql-execute-button-wrapper { + position: relative; +} + +.graphiql-execute-button { + background-color: var(--color-pink); + border: none; + border-radius: var(--border-radius-8); + cursor: pointer; + height: var(--toolbar-width); + padding: 0; + width: var(--toolbar-width); + + &:active { + background-color: var(--color-pink-dark); + } + + &:focus { + outline: var(--color-pink-dark) auto 1px; + } + + & > svg { + color: var(--color-neutral-0); + display: block; + height: var(--px-16); + margin: auto; + pointer-events: none; + width: var(--px-16); + } +} diff --git a/packages/graphiql/src/components/ExecuteButton.tsx b/packages/graphiql-react/src/toolbar/execute.tsx similarity index 80% rename from packages/graphiql/src/components/ExecuteButton.tsx rename to packages/graphiql-react/src/toolbar/execute.tsx index 68cec262b30..596866e1d7a 100644 --- a/packages/graphiql/src/components/ExecuteButton.tsx +++ b/packages/graphiql-react/src/toolbar/execute.tsx @@ -1,17 +1,21 @@ -/** - * 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 { useEditorContext, useExecutionContext } from '@graphiql/react'; import { OperationDefinitionNode } from 'graphql'; -import React, { useState } from 'react'; +import { useState } from 'react'; + +import { useEditorContext } from '../editor'; +import { useExecutionContext } from '../execution'; +import { PlayIcon, StopIcon } from '../icons'; +import { Dropdown } from '../ui'; + +import './execute.css'; export function ExecuteButton() { - const { queryEditor } = useEditorContext({ nonNull: true }); + const { queryEditor } = useEditorContext({ + nonNull: true, + caller: ExecuteButton, + }); const { isFetching, run, stop, subscription } = useExecutionContext({ nonNull: true, + caller: ExecuteButton, }); const [optionsOpen, setOptionsOpen] = useState(false); const [highlight, setHighlight] = useState( @@ -23,10 +27,10 @@ export function ExecuteButton() { const hasOptions = operations.length > 1; return ( -
+
{hasOptions && optionsOpen ? ( -
    + {operations.map((operation, i) => { const opName = operation.name ? operation.name.value : ``; return ( -
  • setHighlight(operation)} @@ -100,10 +98,10 @@ export function ExecuteButton() { run(operation.name?.value); }}> {opName} -
  • + ); })} -
+ ) : null}
); diff --git a/packages/graphiql-react/src/toolbar/index.ts b/packages/graphiql-react/src/toolbar/index.ts new file mode 100644 index 00000000000..a1e27139637 --- /dev/null +++ b/packages/graphiql-react/src/toolbar/index.ts @@ -0,0 +1,2 @@ +export * from './button'; +export * from './execute'; diff --git a/packages/graphiql-react/src/ui/dropdown.css b/packages/graphiql-react/src/ui/dropdown.css new file mode 100644 index 00000000000..400bb09bd79 --- /dev/null +++ b/packages/graphiql-react/src/ui/dropdown.css @@ -0,0 +1,30 @@ +.graphiql-dropdown { + background-color: var(--color-neutral-0); + box-shadow: var(--box-shadow); + border-radius: var(--border-radius-8); + margin: 0; + max-width: 250px; + min-width: 100px; + padding: 0; + position: absolute; + z-index: 1; +} + +.graphiql-dropdown-item { + border-radius: var(--border-radius-4); + cursor: pointer; + margin: var(--px-4); + overflow: hidden; + padding: var(--px-6) var(--px-8); + text-overflow: ellipsis; + white-space: nowrap; +} + +.graphiql-dropdown-item:active, +.graphiql-dropdown-item:hover { + background-color: var(--color-neutral-7); +} + +.graphiql-dropdown-item:not(:first-child) { + margin-top: 0; +} diff --git a/packages/graphiql-react/src/ui/dropdown.tsx b/packages/graphiql-react/src/ui/dropdown.tsx new file mode 100644 index 00000000000..043eb7a5d26 --- /dev/null +++ b/packages/graphiql-react/src/ui/dropdown.tsx @@ -0,0 +1,21 @@ +import './dropdown.css'; + +export function Dropdown(props: JSX.IntrinsicElements['ul']) { + return ( +
    + ); +} + +Dropdown.Item = DropdownItem; + +function DropdownItem(props: JSX.IntrinsicElements['li']) { + return ( +
  • + ); +} diff --git a/packages/graphiql-react/src/ui/index.ts b/packages/graphiql-react/src/ui/index.ts index eaf5eea7f1c..7ef3616b216 100644 --- a/packages/graphiql-react/src/ui/index.ts +++ b/packages/graphiql-react/src/ui/index.ts @@ -1 +1,2 @@ export * from './button'; +export * from './dropdown'; diff --git a/packages/graphiql/__mocks__/@graphiql/react.tsx b/packages/graphiql/__mocks__/@graphiql/react.tsx index 06c75ee95fa..775af04683e 100644 --- a/packages/graphiql/__mocks__/@graphiql/react.tsx +++ b/packages/graphiql/__mocks__/@graphiql/react.tsx @@ -1,33 +1,5 @@ import { - ChevronDownIcon, - ChevronUpIcon, - EditorContext, - EditorContextProvider, - ExecutionContext, - ExecutionContextProvider, - ExplorerContext, - ExplorerContextProvider, - HistoryContext, - HistoryContextProvider, - ImagePreview, - onHasCompletion, - SchemaContext, - SchemaContextProvider, - StorageContext, - StorageContextProvider, - UnStyledButton, - useAutoCompleteLeafs, - useCopyQuery, - useDragResize, useEditorContext, - useExecutionContext, - useExplorerContext, - useHistoryContext, - useMergeQuery, - usePrettifyEditors, - useSchemaContext, - useSelectHistoryItem, - useStorageContext, HeaderEditor as _HeaderEditor, QueryEditor as _QueryEditor, ResponseEditor as _ResponseEditor, @@ -37,30 +9,16 @@ import { useResponseEditor as _useResponseEditor, useVariableEditor as _useVariableEditor, } from '@graphiql/react'; -import type { - EditorContextType, - ExecutionContextType, - ExplorerContextType, - ExplorerFieldDef, - ExplorerNavStack, - ExplorerNavStackItem, - HistoryContextType, - ResponseTooltipType, - SchemaContextType, - StorageContextType, - TabsState, - UseHeaderEditorArgs, - UseResponseEditorArgs, - UseQueryEditorArgs, - UseVariableEditorArgs, -} from '@graphiql/react'; import React, { useEffect, useRef, useState } from 'react'; export { ChevronDownIcon, ChevronUpIcon, + CopyIcon, + Dropdown, EditorContext, EditorContextProvider, + ExecuteButton, ExecutionContext, ExecutionContextProvider, ExplorerContext, @@ -69,10 +27,15 @@ export { HistoryContextProvider, ImagePreview, onHasCompletion, + MergeIcon, + PlayIcon, + PrettifyIcon, SchemaContext, SchemaContextProvider, + StopIcon, StorageContext, StorageContextProvider, + ToolbarButton, UnStyledButton, useAutoCompleteLeafs, useCopyQuery, @@ -86,7 +49,7 @@ export { useSchemaContext, useSelectHistoryItem, useStorageContext, -}; +} from '@graphiql/react'; export type { EditorContextType, @@ -104,7 +67,7 @@ export type { UseResponseEditorArgs, UseQueryEditorArgs, UseVariableEditorArgs, -}; +} from '@graphiql/react'; type Name = 'query' | 'variable' | 'header' | 'response'; diff --git a/packages/graphiql/cypress/integration/tabs.spec.ts b/packages/graphiql/cypress/integration/tabs.spec.ts index 848af058ffb..5112c8e05ce 100644 --- a/packages/graphiql/cypress/integration/tabs.spec.ts +++ b/packages/graphiql/cypress/integration/tabs.spec.ts @@ -10,7 +10,7 @@ describe('Tabs', () => { cy.get('#session-tab-0').should('have.text', ''); // Run the query - cy.get('.execute-button').click().wait(500); + cy.clickExecuteQuery().wait(500); // Open a new tab cy.get('.tab-add').click(); @@ -33,7 +33,7 @@ describe('Tabs', () => { .type('{"someHeader":"someValue"', { force: true }); // Run the query - cy.get('.execute-button').click().wait(500); + cy.clickExecuteQuery().wait(500); // Switch back to the first tab cy.get('#session-tab-0').click(); diff --git a/packages/graphiql/cypress/support/commands.ts b/packages/graphiql/cypress/support/commands.ts index b6671d59ae4..184e9546edc 100644 --- a/packages/graphiql/cypress/support/commands.ts +++ b/packages/graphiql/cypress/support/commands.ts @@ -48,7 +48,7 @@ Cypress.Commands.add('getCy', cyName => { }); Cypress.Commands.add('clickExecuteQuery', () => { - return cy.get('.execute-button').click(); + return cy.get('.graphiql-execute-button').click(); }); Cypress.Commands.add('clickPrettify', () => { diff --git a/packages/graphiql/src/components/GraphiQL.tsx b/packages/graphiql/src/components/GraphiQL.tsx index 7bb1c6f3eb2..9650be6bc8d 100644 --- a/packages/graphiql/src/components/GraphiQL.tsx +++ b/packages/graphiql/src/components/GraphiQL.tsx @@ -24,16 +24,21 @@ import { import { ChevronDownIcon, ChevronUpIcon, + CopyIcon, EditorContextProvider, + ExecuteButton, ExecutionContextProvider, ExecutionContextType, ExplorerContextProvider, HeaderEditor, HistoryContextProvider, + MergeIcon, + PrettifyIcon, QueryEditor, ResponseEditor, SchemaContextProvider, StorageContextProvider, + ToolbarButton, UnStyledButton, useAutoCompleteLeafs, useCopyQuery, @@ -59,9 +64,7 @@ import type { KeyMap, } from '@graphiql/react'; -import { ExecuteButton } from './ExecuteButton'; -import { ToolbarButton } from './ToolbarButton'; -import { ToolbarGroup } from './ToolbarGroup'; +import { ToolbarButton as LegacyToolbarButton } from './ToolbarButton'; import { ToolbarMenu, ToolbarMenuItem } from './ToolbarMenu'; import { DocExplorer } from './DocExplorer'; import { QueryHistory } from './QueryHistory'; @@ -421,13 +424,6 @@ export class GraphiQL extends React.Component { static HeaderEditor = HeaderEditor; static ResultViewer = ResponseEditor; - // Add a button to the Toolbar. - static Button = ToolbarButton; - static ToolbarButton = ToolbarButton; // Don't break existing API. - - // Add a group of buttons to the Toolbar - static Group = ToolbarGroup; - // Add a menu of items to the Toolbar. static Menu = ToolbarMenu; static MenuItem = ToolbarMenuItem; @@ -644,46 +640,35 @@ class GraphiQLWithContext extends React.Component< const toolbar = find(children, child => isChildComponentType(child, GraphiQL.Toolbar), ) || ( - + <> { this.props.prettify(); }} title="Prettify Query (Shift-Ctrl-P)" - label="Prettify" - /> + aria-label="Prettify"> + + { this.props.merge(); }} title="Merge Query (Shift-Ctrl-M)" - label="Merge" - /> + aria-label="Merge"> + + { this.props.copy(); }} title="Copy Query (Shift-Ctrl-C)" - label="Copy" - /> - this.props.historyContext?.toggle()} - title={ - this.props.historyContext?.isVisible - ? 'Hide History' - : 'Show History' - } - label="History" - /> - this.props.schemaContext.introspect()} - title="Fetch GraphQL schema using introspection (Shift-Ctrl-R)" - label="Introspect" - /> + aria-label="Copy"> + + {this.props.toolbar?.additionalContent ? this.props.toolbar.additionalContent : null} - + ); const footer = find(children, child => @@ -707,8 +692,20 @@ class GraphiQLWithContext extends React.Component< {this.props.beforeTopBarContent}
    {logo} - - {toolbar} + this.props.historyContext?.toggle()} + title={ + this.props.historyContext?.isVisible + ? 'Hide History' + : 'Show History' + } + label="History" + /> + this.props.schemaContext.introspect()} + title="Fetch GraphQL schema using introspection (Shift-Ctrl-R)" + label="Introspect" + />
    {this.props.explorerContext && !this.props.explorerContext.isVisible && ( @@ -768,21 +765,32 @@ class GraphiQLWithContext extends React.Component<
    - { - 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} - /> +
    + { + 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} + /> +
    + + {toolbar} +
    +
@@ -967,11 +975,8 @@ GraphiQLLogo.displayName = 'GraphiQLLogo'; // Configure the UI by providing this Component as a child of GraphiQL. function GraphiQLToolbar(props: PropsWithChildren) { - return ( -
- {props.children} -
- ); + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{props.children}; } GraphiQLToolbar.displayName = 'GraphiQLToolbar'; diff --git a/packages/graphiql/src/components/ToolbarGroup.tsx b/packages/graphiql/src/components/ToolbarGroup.tsx deleted file mode 100644 index 40fcde9d7a0..00000000000 --- a/packages/graphiql/src/components/ToolbarGroup.tsx +++ /dev/null @@ -1,21 +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, { ReactNode } from 'react'; - -type ToolbarGroupProps = { - children: ReactNode; -}; - -/** - * ToolbarGroup - * - * A group of associated controls. - */ -export function ToolbarGroup({ children }: ToolbarGroupProps) { - return
{children}
; -} diff --git a/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx b/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx index 2b8b254f7bf..82bba6cc2e4 100644 --- a/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx +++ b/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx @@ -6,8 +6,9 @@ * 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'; +import { ToolbarButton } from '@graphiql/react'; import { render, fireEvent } from '@testing-library/react'; +import React from 'react'; import { GraphiQL, Fetcher } from '../GraphiQL'; import { mockQuery1, @@ -423,20 +424,22 @@ describe('GraphiQL', () => { const { container } = render( - + , ); expect( - container.querySelectorAll('[role="toolbar"] .toolbar-button'), + container.querySelectorAll( + '[role="toolbar"] .graphiql-toolbar-button', + ), ).toHaveLength(1); }); it('can be overridden using a named component', () => { const WrappedToolbar = wrap( - + , ); WrappedToolbar.displayName = 'GraphiQLToolbar'; @@ -449,7 +452,9 @@ describe('GraphiQL', () => { expect(container.querySelector('.test-wrapper')).toBeInTheDocument(); expect( - container.querySelectorAll('[role="toolbar"] button'), + container.querySelectorAll( + '[role="toolbar"] .graphiql-toolbar-button', + ), ).toHaveLength(1); }); }); @@ -459,7 +464,7 @@ describe('GraphiQL', () => { const { container } = render( - + , ); @@ -472,7 +477,7 @@ describe('GraphiQL', () => { it('can be overridden using a named component', () => { const WrappedFooter = wrap( - + , ); WrappedFooter.displayName = 'GraphiQLFooter'; diff --git a/packages/graphiql/src/css/app.css b/packages/graphiql/src/css/app.css index 6c415c3485d..d0fd9ff8815 100644 --- a/packages/graphiql/src/css/app.css +++ b/packages/graphiql/src/css/app.css @@ -127,92 +127,11 @@ position: relative; } -.graphiql-container .toolbar-button { - background: #fdfdfd; - background: linear-gradient(#f9f9f9, #ececec); - border: 0; - border-radius: 3px; - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2), - 0 1px 0 rgba(255, 255, 255, 0.7), inset 0 1px #fff; - color: #555; - cursor: pointer; - display: inline-block; - margin: 0 5px; - padding: 3px 11px 5px; - text-decoration: none; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 150px; -} - -.graphiql-container .toolbar-button:active { - background: linear-gradient(#ececec, #d5d5d5); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7), - inset 0 0 0 1px rgba(0, 0, 0, 0.1), inset 0 1px 1px 1px rgba(0, 0, 0, 0.12), - inset 0 0 5px rgba(0, 0, 0, 0.1); -} - -.graphiql-container .toolbar-button.error { - background: linear-gradient(#fdf3f3, #e6d6d7); - color: #b00; -} - -.graphiql-container .toolbar-button-group { - margin: 0 5px; - white-space: nowrap; -} - -.graphiql-container .toolbar-button-group > * { - margin: 0; -} - -.graphiql-container .toolbar-button-group > *:not(:last-child) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.graphiql-container .toolbar-button-group > *:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - margin-left: -1px; -} - -.graphiql-container .execute-button-wrap { - height: 34px; - margin: 0 14px 0 28px; - position: relative; -} - -.graphiql-container .execute-button { - background: linear-gradient(#fdfdfd, #d2d3d6); - border-radius: 17px; - border: 1px solid rgba(0, 0, 0, 0.25); - box-shadow: 0 1px 0 #fff; - cursor: pointer; - fill: #444; - height: 34px; - margin: 0; - padding: 0; - width: 34px; -} - -.graphiql-container .toolbar-button > svg, -.graphiql-container .execute-button svg { - pointer-events: none; -} - -.graphiql-container .execute-button:active { - background: linear-gradient(#e6e6e6, #c3c3c3); - box-shadow: 0 1px 0 #fff, inset 0 0 2px rgba(0, 0, 0, 0.2), - inset 0 0 6px rgba(0, 0, 0, 0.1); -} - .graphiql-container .toolbar-menu, .graphiql-container .toolbar-select { position: relative; } -.graphiql-container .execute-options, .graphiql-container .toolbar-menu-items, .graphiql-container .toolbar-select-options { background: #fff; @@ -223,12 +142,6 @@ z-index: 100; } -.graphiql-container .execute-options { - min-width: 100px; - top: 37px; - left: -1px; -} - .graphiql-container .toolbar-menu-items { left: 1px; margin-top: -1px; @@ -252,7 +165,6 @@ visibility: visible; } -.graphiql-container .execute-options > li, .graphiql-container .toolbar-menu-items > li, .graphiql-container .toolbar-select-options > li { cursor: pointer; @@ -264,7 +176,6 @@ white-space: nowrap; } -.graphiql-container .execute-options > li.selected, .graphiql-container .toolbar-menu-items > li.hover, .graphiql-container .toolbar-menu-items > li:active, .graphiql-container .toolbar-menu-items > li:hover, diff --git a/packages/graphiql/src/index.tsx b/packages/graphiql/src/index.tsx index cf3b206d7c1..b559e0b5a30 100644 --- a/packages/graphiql/src/index.tsx +++ b/packages/graphiql/src/index.tsx @@ -41,7 +41,6 @@ export { DocExplorer } from './components/DocExplorer'; */ export { ToolbarMenu, ToolbarMenuItem } from './components/ToolbarMenu'; export { ToolbarButton } from './components/ToolbarButton'; -export { ToolbarGroup } from './components/ToolbarGroup'; export { ToolbarSelect, ToolbarSelectOption } from './components/ToolbarSelect'; /** diff --git a/packages/graphiql/src/style.css b/packages/graphiql/src/style.css index 6ab34d809ac..839027ad6b8 100644 --- a/packages/graphiql/src/style.css +++ b/packages/graphiql/src/style.css @@ -18,23 +18,50 @@ flex-direction: column; } -/* The query editor */ +/* The query editor and the toolbar */ .graphiql-container .graphiql-query-editor { border-bottom: 1px solid var(--color-neutral-15); + display: flex; flex: 1; padding: var(--px-16); } +/* The query editor */ +.graphiql-container .graphiql-query-editor-wrapper { + display: flex; + flex: 1; +} + +/* The vertical toolbar next to the query editor */ +.graphiql-container .graphiql-toolbar { + margin-left: var(--px-16); + width: var(--toolbar-width); +} +.graphiql-container .graphiql-toolbar > * + * { + margin-top: var(--px-8); +} + +/* The toolbar icons */ +.graphiql-toolbar-icon { + color: var(--color-neutral-40); + display: block; + height: calc(var(--toolbar-width) - (var(--px-8) * 2)); + padding: var(--px-8); + width: calc(var(--toolbar-width) - (var(--px-8) * 2)); +} + /* The tab bar for editor tools */ .graphiql-container .graphiql-editor-tools { align-items: center; - color: var(--color-neutral-60); cursor: row-resize; display: flex; justify-content: space-between; padding: var(--px-8); } -.graphiql-container .graphiql-editor-tools .active { +.graphiql-container .graphiql-editor-tools button { + color: var(--color-neutral-60); +} +.graphiql-container .graphiql-editor-tools button.active { color: var(--color-neutral-100); }