Skip to content

Commit 4e3fbaa

Browse files
[redesign] layout for editor components (#2515)
* add icons to `@graphiql/react` * add `UnstyledButton` ui component * implement new design for editors layout * rename `Unstyled` to `UnStyled` for spell checking * remove unused class and whitespace in `className` * make tiny change for deploy preview 🤞 * this will do it, i think? Co-authored-by: Rikki Schulte <[email protected]>
1 parent 868291f commit 4e3fbaa

30 files changed

+498
-250
lines changed

.changeset/long-gorillas-act.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphiql/react': minor
3+
---
4+
5+
Add `UnStyledButton` UI component

.changeset/nice-garlics-help.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphiql/react': minor
3+
---
4+
5+
Add some neat svg icons to `@graphiql/react`

packages/graphiql-react/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"react": "^17.0.2",
5555
"react-dom": "^17.0.2",
5656
"typescript": "^4.6.3",
57-
"vite": "^2.9.5"
57+
"vite": "^2.9.5",
58+
"vite-plugin-react-svg": "^0.2.0"
5859
}
5960
}

packages/graphiql-react/src/editor/components/header-editor.tsx

+3-12
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,11 @@ import { useHeaderEditor, UseHeaderEditorArgs } from '../header-editor';
33
import '../style/codemirror.css';
44
import '../style/fold.css';
55

6-
type HeaderEditorProps = UseHeaderEditorArgs & { active?: boolean };
6+
type HeaderEditorProps = UseHeaderEditorArgs & { isHidden?: boolean };
77

8-
export function HeaderEditor({ active, ...hookArgs }: HeaderEditorProps) {
8+
export function HeaderEditor({ isHidden, ...hookArgs }: HeaderEditorProps) {
99
const ref = useHeaderEditor(hookArgs);
1010
return (
11-
<div
12-
className="codemirrorWrap"
13-
// This horrible hack is necessary because a simple display none toggle
14-
// causes one of the editors' gutters to break otherwise.
15-
style={{
16-
position: active ? 'relative' : 'absolute',
17-
visibility: active ? 'visible' : 'hidden',
18-
}}
19-
ref={ref}
20-
/>
11+
<div className={`graphiql-editor${isHidden ? ' hidden' : ''}`} ref={ref} />
2112
);
2213
}

packages/graphiql-react/src/editor/components/query-editor.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,5 @@ import '../style/auto-insertion.css';
1010

1111
export function QueryEditor(props: UseQueryEditorArgs) {
1212
const ref = useQueryEditor(props);
13-
return (
14-
<section className="query-editor" aria-label="Query Editor" ref={ref} />
15-
);
13+
return <div className="graphiql-editor" ref={ref} />;
1614
}

packages/graphiql-react/src/editor/components/variable-editor.tsx

+3-12
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,12 @@ import '../style/lint.css';
66
import '../style/hint.css';
77

88
type VariableEditorProps = UseVariableEditorArgs & {
9-
active?: boolean;
9+
isHidden?: boolean;
1010
};
1111

12-
export function VariableEditor({ active, ...hookArgs }: VariableEditorProps) {
12+
export function VariableEditor({ isHidden, ...hookArgs }: VariableEditorProps) {
1313
const ref = useVariableEditor(hookArgs);
1414
return (
15-
<div
16-
className="codemirrorWrap"
17-
// This horrible hack is necessary because a simple display none toggle
18-
// causes one of the editors' gutters to break otherwise.
19-
style={{
20-
position: active ? 'relative' : 'absolute',
21-
visibility: active ? 'visible' : 'hidden',
22-
}}
23-
ref={ref}
24-
/>
15+
<div className={`graphiql-editor${isHidden ? ' hidden' : ''}`} ref={ref} />
2516
);
2617
}

packages/graphiql-react/src/editor/style/codemirror.css

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
*/
3535

3636
.cm-s-graphiql {
37+
/* Default to punctuation */
38+
color: var(--color-neutral-60);
39+
3740
/* OperationType, `fragment`, `on` */
3841
& .cm-keyword {
3942
color: var(--color-pink);
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import _ChevronDownIcon from './chevron-down.svg';
2+
import _ChevronUpIcon from './chevron-up.svg';
3+
4+
export const ChevronDownIcon = generateIcon(
5+
_ChevronDownIcon,
6+
'chevron down icon',
7+
);
8+
export const ChevronUpIcon = generateIcon(_ChevronUpIcon, 'chevron up icon');
9+
10+
function generateIcon(RawComponent: any, title: string) {
11+
const WithTitle = function IconComponent(
12+
props: JSX.IntrinsicElements['svg'],
13+
) {
14+
return <RawComponent {...props} title={title} />;
15+
};
16+
Object.defineProperty(WithTitle, 'name', { value: RawComponent.name });
17+
return WithTitle;
18+
}

packages/graphiql-react/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export {
3333
useHistoryContext,
3434
useSelectHistoryItem,
3535
} from './history';
36+
export * from './icons';
3637
export {
3738
SchemaContext,
3839
SchemaContextProvider,
@@ -43,6 +44,7 @@ export {
4344
StorageContextProvider,
4445
useStorageContext,
4546
} from './storage';
47+
export * from './ui';
4648
export { useDragResize } from './utility/resize';
4749

4850
export type {

packages/graphiql-react/src/style/root.css

+2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
--px-16: 16px;
4242

4343
/* Border radius */
44+
--border-radius-2: 2px;
4445
--border-radius-4: 4px;
4546
--border-radius-8: 8px;
47+
--border-radius-12: 12px;
4648

4749
/* Box shadow */
4850
--box-shadow: 0px 6px 20px rgba(59, 76, 106, 0.13),
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
button.graphiql-un-styled {
2+
all: unset;
3+
border-radius: var(--border-radius-4);
4+
cursor: pointer;
5+
6+
&:active {
7+
background-color: var(--color-neutral-7);
8+
}
9+
10+
&:focus {
11+
outline: var(--color-neutral-15) auto 1px;
12+
}
13+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import './button.css';
2+
3+
export function UnStyledButton(props: JSX.IntrinsicElements['button']) {
4+
return (
5+
<button
6+
{...props}
7+
className={`graphiql-un-styled ${props.className || ''}`.trim()}
8+
/>
9+
);
10+
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './button';
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
// / <reference types="vite/client" />
1+
/// <reference types="vite/client" />

packages/graphiql-react/vite.config.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import { defineConfig } from 'vite';
22
import react from '@vitejs/plugin-react';
3+
import reactSvgPlugin from 'vite-plugin-react-svg';
34

45
export default defineConfig({
5-
plugins: [react()],
6+
plugins: [
7+
react(),
8+
reactSvgPlugin({
9+
defaultExport: 'component',
10+
expandProps: 'end',
11+
titleProp: true,
12+
}),
13+
],
614
esbuild: {
715
// We use function names for generating readable error messages, so we want
816
// them to be preserved when building and minifying.

packages/graphiql/__mocks__/@graphiql/react.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
ChevronDownIcon,
3+
ChevronUpIcon,
24
EditorContext,
35
EditorContextProvider,
46
ExecutionContext,
@@ -13,6 +15,7 @@ import {
1315
SchemaContextProvider,
1416
StorageContext,
1517
StorageContextProvider,
18+
UnStyledButton,
1619
useAutoCompleteLeafs,
1720
useCopyQuery,
1821
useDragResize,
@@ -54,6 +57,8 @@ import type {
5457
import React, { useEffect, useRef, useState } from 'react';
5558

5659
export {
60+
ChevronDownIcon,
61+
ChevronUpIcon,
5762
EditorContext,
5863
EditorContextProvider,
5964
ExecutionContext,
@@ -68,6 +73,7 @@ export {
6873
SchemaContextProvider,
6974
StorageContext,
7075
StorageContextProvider,
76+
UnStyledButton,
7177
useAutoCompleteLeafs,
7278
useCopyQuery,
7379
useDragResize,

packages/graphiql/cypress/integration/init.spec.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ describe('GraphiQL On Initialization', () => {
3333
'.graphiql-container',
3434
'.topBarWrap',
3535
'.editorWrap',
36-
'.queryWrap',
37-
'.resultWrap',
38-
'.variable-editor',
36+
'.graphiql-editors',
37+
'.graphiql-response',
38+
'.graphiql-editor-tool',
3939
];
4040
cy.visit(`/`);
41-
cy.get('.query-editor').contains('# Welcome to GraphiQL');
41+
cy.get('.graphiql-query-editor').contains('# Welcome to GraphiQL');
4242
containers.forEach(cSelector => cy.get(cSelector).should('be.visible'));
4343
});
4444

packages/graphiql/cypress/integration/tabs.spec.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ describe('Tabs', () => {
44
cy.get('#session-tab-0').should('have.text', '<untitled>');
55

66
// Enter a query without operation name
7-
cy.get('.query-editor textarea').type('{id', { force: true }).wait(500);
7+
cy.get('.graphiql-query-editor textarea')
8+
.type('{id', { force: true })
9+
.wait(500);
810
cy.get('#session-tab-0').should('have.text', '<untitled>');
911

1012
// Run the query
@@ -14,19 +16,19 @@ describe('Tabs', () => {
1416
cy.get('.tab-add').click();
1517

1618
// Enter a query
17-
cy.get('.query-editor textarea')
19+
cy.get('.graphiql-query-editor textarea')
1820
.type('query Foo {image', { force: true })
1921
.wait(500);
2022
cy.get('#session-tab-1').should('have.text', 'Foo');
2123

2224
// Enter variables
23-
cy.get('.variable-editor textarea')
25+
cy.get('.graphiql-editor-tool textarea')
2426
.eq(0)
2527
.type('{"someVar":42', { force: true });
2628

2729
// Enter headers
28-
cy.contains('Request Headers').click();
29-
cy.get('.variable-editor textarea')
30+
cy.contains('Headers').click();
31+
cy.get('.graphiql-editor-tool textarea')
3032
.eq(1)
3133
.type('{"someHeader":"someValue"', { force: true });
3234

packages/graphiql/cypress/support/commands.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ Cypress.Commands.add('visitWithOp', ({ query, variables, variablesString }) => {
6868
Cypress.Commands.add(
6969
'assertHasValues',
7070
({ query, variables, variablesString, headersString, response }: Op) => {
71-
cy.get('.query-editor').should(element => {
71+
cy.get('.graphiql-query-editor').should(element => {
7272
expect(normalize(element.get(0).innerText)).to.equal(
7373
codeWithLineNumbers(query),
7474
);
7575
});
7676
if (typeof variables !== 'undefined') {
77-
cy.contains('Query Variables').click();
78-
cy.get('.variable-editor .codemirrorWrap')
77+
cy.contains('Variables').click();
78+
cy.get('.graphiql-editor-tool .graphiql-editor')
7979
.eq(0)
8080
.should(element => {
8181
expect(normalize(element.get(0).innerText)).to.equal(
@@ -84,8 +84,8 @@ Cypress.Commands.add(
8484
});
8585
}
8686
if (typeof variablesString !== 'undefined') {
87-
cy.contains('Query Variables').click();
88-
cy.get('.variable-editor .codemirrorWrap')
87+
cy.contains('Variables').click();
88+
cy.get('.graphiql-editor-tool .graphiql-editor')
8989
.eq(0)
9090
.should(element => {
9191
expect(normalize(element.get(0).innerText)).to.equal(
@@ -94,8 +94,8 @@ Cypress.Commands.add(
9494
});
9595
}
9696
if (typeof headersString !== 'undefined') {
97-
cy.contains('Request Headers').click();
98-
cy.get('.variable-editor .codemirrorWrap')
97+
cy.contains('Headers').click();
98+
cy.get('.graphiql-editor-tool .graphiql-editor')
9999
.eq(1)
100100
.should(element => {
101101
expect(normalize(element.get(0).innerText)).to.equal(

packages/graphiql/src/cdn.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import { createGraphiQLFetcher } from '@graphiql/toolkit';
1111
import '@graphiql/react/font/roboto.css';
1212
import '@graphiql/react/font/fira-code.css';
1313
import '@graphiql/react/dist/style.css';
14+
import './style.css';
15+
16+
// Legacy styles
1417
import './css/app.css';
15-
import './css/loading.css';
1618
import './css/doc-explorer.css';
1719
import './css/history.css';
1820

packages/graphiql/src/components/DocExplorer.tsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,7 @@ export function DocExplorer(props: DocExplorerProps) {
6060
);
6161
} else if (isFetching) {
6262
// Schema is undefined when it is being loaded via introspection.
63-
content = (
64-
<div className="spinner-container">
65-
<div className="spinner" />
66-
</div>
67-
);
63+
content = <div className="graphiql-spinner" />;
6864
} else if (!schema) {
6965
// Schema is null when it explicitly does not exist, typically due to
7066
// an error during introspection.

0 commit comments

Comments
 (0)