Skip to content

Commit

Permalink
[redesign] implement new toolbar design (#2524)
Browse files Browse the repository at this point in the history
* implement new toolbar design

* add `caller` argument for better errors
  • Loading branch information
thomasheyenbrock committed Aug 9, 2022
1 parent aaf1213 commit 591019c
Show file tree
Hide file tree
Showing 27 changed files with 330 additions and 248 deletions.
25 changes: 25 additions & 0 deletions .changeset/real-waves-enjoy.md
Original file line number Diff line number Diff line change
@@ -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 (
<GraphiQL>
<GraphiQL.Toolbar>
{/* Add custom styles for your buttons using the given class */}
<div className="button-group">
<button>1</button>
<button>2</button>
<button>3</button>
</div>
</GraphiQL.Toolbar>
</GraphiQL>
);
}
```
1 change: 1 addition & 0 deletions packages/graphiql-react/src/editor/style/editor.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.graphiql-editor {
height: 100%;
position: relative;
width: 100%;

&.hidden {
/* Just setting `display: none;` would break the editor gutters */
Expand Down
4 changes: 4 additions & 0 deletions packages/graphiql-react/src/icons/copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions packages/graphiql-react/src/icons/index.tsx
Original file line number Diff line number Diff line change
@@ -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(
Expand Down
6 changes: 6 additions & 0 deletions packages/graphiql-react/src/icons/merge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/graphiql-react/src/icons/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions packages/graphiql-react/src/icons/prettify.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/graphiql-react/src/icons/stop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/graphiql-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export {
StorageContextProvider,
useStorageContext,
} from './storage';
export * from './toolbar';
export * from './ui';
export { useDragResize } from './utility/resize';

Expand Down
5 changes: 5 additions & 0 deletions packages/graphiql-react/src/style/root.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions packages/graphiql-react/src/toolbar/button.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
33 changes: 33 additions & 0 deletions packages/graphiql-react/src/toolbar/button.tsx
Original file line number Diff line number Diff line change
@@ -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<Error | null>(null);
return (
<UnStyledButton
{...props}
className={
'graphiql-toolbar-button' +
(error ? ' error' : '') +
(props.className ? ' ' + props.className : '')
}
onClick={event => {
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']}
/>
);
}
30 changes: 30 additions & 0 deletions packages/graphiql-react/src/toolbar/execute.css
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
/**
* 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, setOperationName } = useEditorContext({ nonNull: true });
const { queryEditor, setOperationName } = useEditorContext({
nonNull: true,
caller: ExecuteButton,
});
const {
isFetching,
operationName,
Expand All @@ -18,6 +21,7 @@ export function ExecuteButton() {
subscription,
} = useExecutionContext({
nonNull: true,
caller: ExecuteButton,
});
const [optionsOpen, setOptionsOpen] = useState(false);
const [highlight, setHighlight] = useState<OperationDefinitionNode | null>(
Expand All @@ -29,10 +33,10 @@ export function ExecuteButton() {
const hasOptions = operations.length > 1 && typeof operationName !== 'string';

return (
<div className="execute-button-wrap">
<div className="graphiql-execute-button-wrapper">
<button
type="button"
className="execute-button"
className="graphiql-execute-button"
onMouseDown={
// Allow mouse down if there is no running query, there are options
// for which operation to run, and the dropdown is currently closed.
Expand Down Expand Up @@ -81,43 +85,37 @@ export function ExecuteButton() {
: undefined
}
title="Execute Query (Ctrl-Enter)">
<svg width="34" height="34">
{isRunning ? (
<path d="M 10 10 L 23 10 L 23 23 L 10 23 z" />
) : (
<path d="M 11 9 L 24 16 L 11 23 z" />
)}
</svg>
{isRunning ? <StopIcon /> : <PlayIcon />}
</button>
{hasOptions && optionsOpen ? (
<ul className="execute-options">
<Dropdown>
{operations.map((operation, i) => {
const opName = operation.name
? operation.name.value
: `<Unnamed ${operation.operation}>`;
return (
<li
<Dropdown.Item
key={`${opName}-${i}`}
className={operation === highlight ? 'selected' : undefined}
onMouseOver={() => setHighlight(operation)}
onMouseOut={() => setHighlight(null)}
onMouseUp={() => {
setOptionsOpen(false);
const operationName = operation.name?.value;
const selectedOperationName = operation.name?.value;
if (
queryEditor &&
operationName &&
operationName !== queryEditor.operationName
selectedOperationName &&
selectedOperationName !== queryEditor.operationName
) {
setOperationName(operationName);
setOperationName(selectedOperationName);
}
run();
}}>
{opName}
</li>
</Dropdown.Item>
);
})}
</ul>
</Dropdown>
) : null}
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions packages/graphiql-react/src/toolbar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './button';
export * from './execute';
30 changes: 30 additions & 0 deletions packages/graphiql-react/src/ui/dropdown.css
Original file line number Diff line number Diff line change
@@ -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;
}
21 changes: 21 additions & 0 deletions packages/graphiql-react/src/ui/dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import './dropdown.css';

export function Dropdown(props: JSX.IntrinsicElements['ul']) {
return (
<ul
{...props}
className={`graphiql-dropdown ${props.className || ''}`.trim()}
/>
);
}

Dropdown.Item = DropdownItem;

function DropdownItem(props: JSX.IntrinsicElements['li']) {
return (
<li
{...props}
className={`graphiql-dropdown-item ${props.className || ''}`.trim()}
/>
);
}
1 change: 1 addition & 0 deletions packages/graphiql-react/src/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './button';
export * from './dropdown';
Loading

0 comments on commit 591019c

Please sign in to comment.