From 55929acdb3d7c53618b364b5848a1046f1a6e1a2 Mon Sep 17 00:00:00 2001 From: Thomas Heyenbrock Date: Wed, 27 Jul 2022 13:42:03 +0200 Subject: [PATCH] [redesign] docs (#2588) * add icons * add `Button` component to `@graphiql/react` * add explorer section component * redesign `SchemaDocumentation` component * redesign `TypeDocumentation` component * redesign `FieldDocumentation` component * redesign `DocExplorer` component * extend changeset message --- .changeset/five-pillows-fail.md | 4 +- .changeset/red-zoos-divide.md | 1 + .../__tests__/doc-explorer.spec.tsx | 16 +- .../components/__tests__/example-schema.ts | 72 ---- .../__tests__/field-documentation.spec.tsx | 14 +- .../__tests__/type-documentation.spec.tsx | 133 +++++-- .../components/deprecation-reason.css | 12 + .../components/deprecation-reason.tsx | 16 + .../src/explorer/components/doc-explorer.css | 79 ++++ .../src/explorer/components/doc-explorer.tsx | 100 ++--- .../components/field-documentation.tsx | 154 ++++---- .../components/schema-documentation.css | 3 + .../components/schema-documentation.tsx | 46 ++- .../src/explorer/components/section.css | 22 ++ .../src/explorer/components/section.tsx | 63 ++++ .../components/type-documentation.css | 11 + .../components/type-documentation.tsx | 353 +++++++++--------- packages/graphiql-react/src/explorer/index.ts | 2 + .../graphiql-react/src/icons/argument.svg | 4 + .../graphiql-react/src/icons/chevron-left.svg | 3 + .../src/icons/deprecated-argument.svg | 5 + .../src/icons/deprecated-enum-value.svg | 5 + .../src/icons/deprecated-field.svg | 5 + .../graphiql-react/src/icons/directive.svg | 4 + .../graphiql-react/src/icons/enum-value.svg | 4 + packages/graphiql-react/src/icons/field.svg | 4 + .../graphiql-react/src/icons/implements.svg | 4 + packages/graphiql-react/src/icons/index.tsx | 34 ++ .../graphiql-react/src/icons/root-type.svg | 4 + packages/graphiql-react/src/icons/type.svg | 4 + packages/graphiql-react/src/index.ts | 2 + packages/graphiql-react/src/style/root.css | 2 + packages/graphiql-react/src/ui/button.css | 18 + packages/graphiql-react/src/ui/button.tsx | 9 + .../graphiql/__mocks__/@graphiql/react.tsx | 14 + .../graphiql/cypress/integration/docs.spec.ts | 72 ++-- packages/graphiql/src/cdn.ts | 1 - packages/graphiql/src/components/GraphiQL.tsx | 8 +- packages/graphiql/src/css/doc-explorer.css | 282 -------------- packages/graphiql/src/style.css | 1 + 40 files changed, 817 insertions(+), 773 deletions(-) delete mode 100644 packages/graphiql-react/src/explorer/components/__tests__/example-schema.ts create mode 100644 packages/graphiql-react/src/explorer/components/deprecation-reason.css create mode 100644 packages/graphiql-react/src/explorer/components/deprecation-reason.tsx create mode 100644 packages/graphiql-react/src/explorer/components/doc-explorer.css create mode 100644 packages/graphiql-react/src/explorer/components/schema-documentation.css create mode 100644 packages/graphiql-react/src/explorer/components/section.css create mode 100644 packages/graphiql-react/src/explorer/components/section.tsx create mode 100644 packages/graphiql-react/src/explorer/components/type-documentation.css create mode 100644 packages/graphiql-react/src/icons/argument.svg create mode 100644 packages/graphiql-react/src/icons/chevron-left.svg create mode 100644 packages/graphiql-react/src/icons/deprecated-argument.svg create mode 100644 packages/graphiql-react/src/icons/deprecated-enum-value.svg create mode 100644 packages/graphiql-react/src/icons/deprecated-field.svg create mode 100644 packages/graphiql-react/src/icons/directive.svg create mode 100644 packages/graphiql-react/src/icons/enum-value.svg create mode 100644 packages/graphiql-react/src/icons/field.svg create mode 100644 packages/graphiql-react/src/icons/implements.svg create mode 100644 packages/graphiql-react/src/icons/root-type.svg create mode 100644 packages/graphiql-react/src/icons/type.svg delete mode 100644 packages/graphiql/src/css/doc-explorer.css diff --git a/.changeset/five-pillows-fail.md b/.changeset/five-pillows-fail.md index 279cab609e3..9bb1677681e 100644 --- a/.changeset/five-pillows-fail.md +++ b/.changeset/five-pillows-fail.md @@ -3,8 +3,8 @@ --- Add new components: -- UI components (`Dropdown`, `Spinner`, `UnStyledButton` and lots of icon components) +- UI components (`Button`, `Dropdown`, `Spinner`, `UnStyledButton` and lots of icon components) - Editor components (`QueryEditor`, `VariableEditor`, `HeaderEditor` and `ResponseEditor`) - Toolbar components (`ExecuteButton` and `ToolbarButton`) -- Docs components (`Argument`, `DefaultValue`, `Directive`, `DocExplorer`, `FieldDocumentation`, `FieldLink`, `SchemaDocumentation`, `Search`, `TypeDocumentation` and `TypeLink`) +- Docs components (`Argument`, `DefaultValue`, `DeprecationReason`, `Directive`, `DocExplorer`, `ExplorerSection`, `FieldDocumentation`, `FieldLink`, `SchemaDocumentation`, `Search`, `TypeDocumentation` and `TypeLink`) - `History` component diff --git a/.changeset/red-zoos-divide.md b/.changeset/red-zoos-divide.md index 689ea91d859..aac443000ea 100644 --- a/.changeset/red-zoos-divide.md +++ b/.changeset/red-zoos-divide.md @@ -4,3 +4,4 @@ BREAKING: The following exports of the `graphiql` package have been removed: - `DocExplorer`: Now exported from `@graphiql/react` as `DocExplorer` + - The `schema` prop has been removed, the component now uses the schema provided by the `ExplorerContext` diff --git a/packages/graphiql-react/src/explorer/components/__tests__/doc-explorer.spec.tsx b/packages/graphiql-react/src/explorer/components/__tests__/doc-explorer.spec.tsx index 6308d0c810c..b4d73fe3947 100644 --- a/packages/graphiql-react/src/explorer/components/__tests__/doc-explorer.spec.tsx +++ b/packages/graphiql-react/src/explorer/components/__tests__/doc-explorer.spec.tsx @@ -1,24 +1,22 @@ import { render } from '@testing-library/react'; +import { GraphQLSchema } from 'graphql'; import { SchemaContext, SchemaContextType } from '../../../schema'; import { ExplorerContextProvider } from '../../context'; import { DocExplorer } from '../doc-explorer'; -import { ExampleSchema } from './example-schema'; const defaultSchemaContext: SchemaContextType = { fetchError: null, introspect() {}, isFetching: false, - schema: ExampleSchema, + schema: new GraphQLSchema({ description: 'GraphQL Schema for testing' }), validationErrors: [], }; -function DocExplorerWithContext( - props: React.ComponentProps, -) { +function DocExplorerWithContext() { return ( - + ); } @@ -45,9 +43,9 @@ describe('DocExplorer', () => { , ); - const error = container.querySelectorAll('.error-container'); + const error = container.querySelectorAll('.graphiql-doc-explorer-error'); expect(error).toHaveLength(1); - expect(error[0]).toHaveTextContent('No Schema Available'); + expect(error[0]).toHaveTextContent('No GraphQL schema available'); }); it('renders with schema', () => { const { container } = render( @@ -55,7 +53,7 @@ describe('DocExplorer', () => { , , ); - const error = container.querySelectorAll('.error-container'); + const error = container.querySelectorAll('.graphiql-doc-explorer-error'); expect(error).toHaveLength(0); expect( container.querySelector('.graphiql-markdown-description'), diff --git a/packages/graphiql-react/src/explorer/components/__tests__/example-schema.ts b/packages/graphiql-react/src/explorer/components/__tests__/example-schema.ts deleted file mode 100644 index 0154642af5e..00000000000 --- a/packages/graphiql-react/src/explorer/components/__tests__/example-schema.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - GraphQLObjectType, - GraphQLString, - GraphQLSchema, - GraphQLUnionType, - GraphQLInterfaceType, - GraphQLBoolean, - GraphQLEnumType, -} from 'graphql'; - -export const ExampleInterface = new GraphQLInterfaceType({ - name: 'exampleInterface', - fields: { - name: { type: GraphQLString }, - }, -}); - -export const ExampleEnum = new GraphQLEnumType({ - name: 'exampleEnum', - values: { - value1: { value: 'Value 1' }, - value2: { value: 'Value 2' }, - value3: { value: 'Value 3', deprecationReason: 'Only two are needed' }, - }, -}); - -export const ExampleUnionType1 = new GraphQLObjectType({ - name: 'Union_Type_1', - interfaces: [ExampleInterface], - fields: { - name: { type: GraphQLString }, - enum: { type: ExampleEnum }, - }, -}); - -export const ExampleUnionType2 = new GraphQLObjectType({ - name: 'Union_Type_2', - interfaces: [ExampleInterface], - fields: { - name: { type: GraphQLString }, - string: { type: GraphQLString }, - }, -}); - -export const ExampleUnion = new GraphQLUnionType({ - name: 'exampleUnion', - types: [ExampleUnionType1, ExampleUnionType2], -}); - -export const ExampleQuery = new GraphQLObjectType({ - name: 'Query', - description: 'Query description\n Second line', - fields: { - string: { type: GraphQLString }, - union: { type: ExampleUnion }, - fieldWithArgs: { - type: GraphQLString, - args: { - stringArg: { type: GraphQLString }, - }, - }, - deprecatedField: { - type: GraphQLBoolean, - deprecationReason: 'example deprecation reason', - }, - }, -}); - -export const ExampleSchema = new GraphQLSchema({ - query: ExampleQuery, - description: 'GraphQL Schema for testing', -}); diff --git a/packages/graphiql-react/src/explorer/components/__tests__/field-documentation.spec.tsx b/packages/graphiql-react/src/explorer/components/__tests__/field-documentation.spec.tsx index 89e4a4e84f3..c7fb31b2c77 100644 --- a/packages/graphiql-react/src/explorer/components/__tests__/field-documentation.spec.tsx +++ b/packages/graphiql-react/src/explorer/components/__tests__/field-documentation.spec.tsx @@ -4,8 +4,8 @@ import { render, } from '@testing-library/react'; import { GraphQLString, GraphQLObjectType, Kind } from 'graphql'; -import { ExplorerContext, ExplorerFieldDef } from '../../context'; +import { ExplorerContext, ExplorerFieldDef } from '../../context'; import { FieldDocumentation } from '../field-documentation'; import { mockExplorerContextValue } from './test-utils'; @@ -65,12 +65,12 @@ function FieldDocumentationWithContext(props: { field: ExplorerFieldDef }) { def: props.field, })} > - + ); } -describe('FieldDoc', () => { +describe('FieldDocumentation', () => { it('should render a simple string field', () => { const { container } = render( { ); expect( container.querySelector('.graphiql-markdown-description'), - ).toHaveTextContent('No Description'); + ).not.toBeInTheDocument(); expect( container.querySelector('.graphiql-doc-explorer-type-name'), ).toHaveTextContent('String'); @@ -96,7 +96,7 @@ describe('FieldDoc', () => { ); expect( container.querySelector('.graphiql-markdown-description'), - ).toHaveTextContent('No Description'); + ).not.toBeInTheDocument(); expect( container.querySelector('.graphiql-doc-explorer-type-name'), ).toHaveTextContent('String'); @@ -118,7 +118,7 @@ describe('FieldDoc', () => { }); it('should render a string field with arguments', () => { - const { container } = render( + const { container, getByText } = render( , @@ -140,7 +140,7 @@ describe('FieldDoc', () => { container.querySelectorAll('.graphiql-markdown-deprecation'), ).toHaveLength(0); // make sure deprecation is present - fireEvent.click(container.querySelector('.show-btn')); + fireEvent.click(getByText('Show Deprecated Arguments')); const deprecationDocs = container.querySelectorAll( '.graphiql-markdown-deprecation', ); diff --git a/packages/graphiql-react/src/explorer/components/__tests__/type-documentation.spec.tsx b/packages/graphiql-react/src/explorer/components/__tests__/type-documentation.spec.tsx index f69640cd445..1d1b4091651 100644 --- a/packages/graphiql-react/src/explorer/components/__tests__/type-documentation.spec.tsx +++ b/packages/graphiql-react/src/explorer/components/__tests__/type-documentation.spec.tsx @@ -3,17 +3,20 @@ import { fireEvent, render, } from '@testing-library/react'; -import { GraphQLNamedType } from 'graphql'; +import { + GraphQLBoolean, + GraphQLEnumType, + GraphQLInterfaceType, + GraphQLNamedType, + GraphQLObjectType, + GraphQLSchema, + GraphQLString, + GraphQLUnionType, +} from 'graphql'; import { SchemaContext } from '../../../schema'; import { ExplorerContext } from '../../context'; import { TypeDocumentation } from '../type-documentation'; -import { - ExampleEnum, - ExampleQuery, - ExampleSchema, - ExampleUnion, -} from './example-schema'; import { mockExplorerContextValue, unwrapType } from './test-utils'; function TypeDocumentationWithContext(props: { type: GraphQLNamedType }) { @@ -33,13 +36,13 @@ function TypeDocumentationWithContext(props: { type: GraphQLNamedType }) { def: props.type, })} > - + ); } -describe('TypeDoc', () => { +describe('TypeDocumentation', () => { it('renders a top-level query object type', () => { const { container } = render( , @@ -52,7 +55,7 @@ describe('TypeDoc', () => { normalizeWhitespace: false, }); - const cats = container.querySelectorAll('.doc-category-item'); + const cats = container.querySelectorAll('.graphiql-doc-explorer-item'); expect(cats[0]).toHaveTextContent('string: String'); expect(cats[1]).toHaveTextContent('union: exampleUnion'); expect(cats[2]).toHaveTextContent( @@ -61,15 +64,15 @@ describe('TypeDoc', () => { }); it('renders deprecated fields when you click to see them', () => { - const { container } = render( + const { container, getByText } = render( , ); - let cats = container.querySelectorAll('.doc-category-item'); + let cats = container.querySelectorAll('.graphiql-doc-explorer-item'); expect(cats).toHaveLength(3); - fireEvent.click(container.querySelector('.show-btn')!); + fireEvent.click(getByText('Show Deprecated Fields')); - cats = container.querySelectorAll('.doc-category-item'); + cats = container.querySelectorAll('.graphiql-doc-explorer-item'); expect(cats).toHaveLength(4); expect( container.querySelectorAll('.graphiql-doc-explorer-field-name')[3], @@ -83,19 +86,25 @@ describe('TypeDoc', () => { const { container } = render( , ); - expect(container.querySelector('.doc-category-title')).toHaveTextContent( - 'possible types', + const title = container.querySelector( + '.graphiql-doc-explorer-section-title', ); + title.removeChild(title.childNodes[0]); + expect(title).toHaveTextContent('Possible Types'); }); it('renders an Enum type', () => { const { container } = render( , ); - expect(container.querySelector('.doc-category-title')).toHaveTextContent( - 'values', + const title = container.querySelector( + '.graphiql-doc-explorer-section-title', + ); + title.removeChild(title.childNodes[0]); + expect(title).toHaveTextContent('Enum Values'); + const enums = container.querySelectorAll( + '.graphiql-doc-explorer-enum-value', ); - const enums = container.querySelectorAll('.enum-value'); expect(enums[0]).toHaveTextContent('value1'); expect(enums[1]).toHaveTextContent('value2'); }); @@ -104,18 +113,29 @@ describe('TypeDoc', () => { const { getByText, container } = render( , ); - const showBtn = getByText('Show deprecated values...'); + const showBtn = getByText('Show Deprecated Values'); expect(showBtn).toBeInTheDocument(); - const titles = container.querySelectorAll('.doc-category-title'); - expect(titles[0]).toHaveTextContent('values'); - expect(titles[1]).toHaveTextContent('deprecated values'); - let enums = container.querySelectorAll('.enum-value'); + + const title = container.querySelector( + '.graphiql-doc-explorer-section-title', + ); + title.removeChild(title.childNodes[0]); + expect(title).toHaveTextContent('Enum Values'); + + let enums = container.querySelectorAll('.graphiql-doc-explorer-enum-value'); expect(enums).toHaveLength(2); // click button to show deprecated enum values fireEvent.click(showBtn); expect(showBtn).not.toBeInTheDocument(); - enums = container.querySelectorAll('.enum-value'); + + const deprecatedTitle = container.querySelectorAll( + '.graphiql-doc-explorer-section-title', + )[1]; + deprecatedTitle.removeChild(deprecatedTitle.childNodes[0]); + expect(deprecatedTitle).toHaveTextContent('Deprecated Enum Values'); + + enums = container.querySelectorAll('.graphiql-doc-explorer-enum-value'); expect(enums).toHaveLength(3); expect(enums[2]).toHaveTextContent('value3'); expect( @@ -123,3 +143,66 @@ describe('TypeDoc', () => { ).toHaveTextContent('Only two are needed'); }); }); + +const ExampleInterface = new GraphQLInterfaceType({ + name: 'exampleInterface', + fields: { + name: { type: GraphQLString }, + }, +}); + +const ExampleEnum = new GraphQLEnumType({ + name: 'exampleEnum', + values: { + value1: { value: 'Value 1' }, + value2: { value: 'Value 2' }, + value3: { value: 'Value 3', deprecationReason: 'Only two are needed' }, + }, +}); + +const ExampleUnionType1 = new GraphQLObjectType({ + name: 'Union_Type_1', + interfaces: [ExampleInterface], + fields: { + name: { type: GraphQLString }, + enum: { type: ExampleEnum }, + }, +}); + +const ExampleUnionType2 = new GraphQLObjectType({ + name: 'Union_Type_2', + interfaces: [ExampleInterface], + fields: { + name: { type: GraphQLString }, + string: { type: GraphQLString }, + }, +}); + +const ExampleUnion = new GraphQLUnionType({ + name: 'exampleUnion', + types: [ExampleUnionType1, ExampleUnionType2], +}); + +const ExampleQuery = new GraphQLObjectType({ + name: 'Query', + description: 'Query description\n Second line', + fields: { + string: { type: GraphQLString }, + union: { type: ExampleUnion }, + fieldWithArgs: { + type: GraphQLString, + args: { + stringArg: { type: GraphQLString }, + }, + }, + deprecatedField: { + type: GraphQLBoolean, + deprecationReason: 'example deprecation reason', + }, + }, +}); + +const ExampleSchema = new GraphQLSchema({ + query: ExampleQuery, + description: 'GraphQL Schema for testing', +}); diff --git a/packages/graphiql-react/src/explorer/components/deprecation-reason.css b/packages/graphiql-react/src/explorer/components/deprecation-reason.css new file mode 100644 index 00000000000..9c863766a01 --- /dev/null +++ b/packages/graphiql-react/src/explorer/components/deprecation-reason.css @@ -0,0 +1,12 @@ +.graphiql-doc-explorer-deprecation { + background-color: var(--color-orche-background); + border: 1px solid var(--color-orche); + border-radius: var(--px-4); + color: var(--color-orche); + padding: var(--px-8); +} + +.graphiql-doc-explorer-deprecation-label { + font-size: var(--font-size-hint); + font-weight: var(--font-weight-medium); +} diff --git a/packages/graphiql-react/src/explorer/components/deprecation-reason.tsx b/packages/graphiql-react/src/explorer/components/deprecation-reason.tsx new file mode 100644 index 00000000000..3ad2f5f99ef --- /dev/null +++ b/packages/graphiql-react/src/explorer/components/deprecation-reason.tsx @@ -0,0 +1,16 @@ +import { MarkdownContent } from '../../ui'; + +import './deprecation-reason.css'; + +type DeprecationReasonProps = { children?: string | null }; + +export function DeprecationReason(props: DeprecationReasonProps) { + return props.children ? ( +
+
Deprecated
+ + {props.children} + +
+ ) : null; +} diff --git a/packages/graphiql-react/src/explorer/components/doc-explorer.css b/packages/graphiql-react/src/explorer/components/doc-explorer.css new file mode 100644 index 00000000000..740755e8e33 --- /dev/null +++ b/packages/graphiql-react/src/explorer/components/doc-explorer.css @@ -0,0 +1,79 @@ +/* The header of the doc explorer */ +.graphiql-doc-explorer-header { + display: flex; + justify-content: space-between; + position: relative; +} +.graphiql-doc-explorer-header-content { + display: flex; + flex-direction: column; + min-width: 0; +} +.graphiql-doc-explorer-header:focus-within + > .graphiql-doc-explorer-header-content + > *:not(.graphiql-doc-explorer-back) { + /* Hide the header when focussing the search input */ + visibility: hidden; +} + +/* The search input in the header of the doc explorer */ +.graphiql-doc-explorer-search { + height: 100%; + position: absolute; + right: 0; + top: 0; +} +.graphiql-doc-explorer-search:focus-within { + left: 0; +} +.graphiql-doc-explorer-search [data-reach-combobox-input] { + height: 24px; + width: 4ch; +} +.graphiql-doc-explorer-search [data-reach-combobox-input]:focus { + width: 100%; +} + +/* The back-button in the doc explorer */ +.graphiql-doc-explorer-back { + align-items: center; + color: var(--color-neutral-60); + display: flex; + text-decoration: none; +} +.graphiql-doc-explorer-back:hover { + text-decoration: underline; +} +.graphiql-doc-explorer-back > svg { + height: var(--px-8); + margin-right: var(--px-8); + width: var(--px-8); +} + +/* The title of the currently active page in the doc explorer */ +.graphiql-doc-explorer-title { + font-weight: var(--font-weight-medium); + font-size: var(--font-size-h2); + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.graphiql-doc-explorer-title:not(:first-child) { + font-size: var(--font-size-h3); + margin-top: var(--px-8); +} + +/* The contents of the currently active page in the doc explorer */ +.graphiql-doc-explorer-content > * { + color: var(--color-neutral-60); + margin-top: var(--px-20); +} + +/* Error message */ +.graphiql-doc-explorer-error { + background-color: var(--color-red-background); + border: 1px solid var(--color-red); + border-radius: var(--border-radius-8); + color: var(--color-red); + padding: var(--px-8) var(--px-12); +} diff --git a/packages/graphiql-react/src/explorer/components/doc-explorer.tsx b/packages/graphiql-react/src/explorer/components/doc-explorer.tsx index feb151c464b..a582d89b7cc 100644 --- a/packages/graphiql-react/src/explorer/components/doc-explorer.tsx +++ b/packages/graphiql-react/src/explorer/components/doc-explorer.tsx @@ -1,6 +1,7 @@ -import { GraphQLSchema, isType } from 'graphql'; +import { isType } from 'graphql'; import { ReactNode } from 'react'; +import { ChevronLeftIcon } from '../../icons'; import { useSchemaContext } from '../../schema'; import { Spinner } from '../../ui'; import { useExplorerContext } from '../context'; @@ -9,41 +10,27 @@ import { SchemaDocumentation } from './schema-documentation'; import { Search } from './search'; import { TypeDocumentation } from './type-documentation'; -type DocExplorerProps = { - onClose?(): void; - /** - * @deprecated Passing a schema prop directly to this component will be - * removed in the next major version. Instead you need to wrap this component - * with the `SchemaContextProvider` from `@graphiql/react`. This context - * provider accepts a `schema` prop that you can use to skip fetching the - * schema with an introspection request. - */ - schema?: GraphQLSchema | null; -}; +import './doc-explorer.css'; -export function DocExplorer(props: DocExplorerProps) { - const { - fetchError, - isFetching, - schema: schemaFromContext, - validationErrors, - } = useSchemaContext({ nonNull: true, caller: DocExplorer }); - const { explorerNavStack, hide, pop } = useExplorerContext({ +export function DocExplorer() { + const { fetchError, isFetching, schema, validationErrors } = useSchemaContext( + { nonNull: true, caller: DocExplorer }, + ); + const { explorerNavStack, pop } = useExplorerContext({ nonNull: true, caller: DocExplorer, }); const navItem = explorerNavStack[explorerNavStack.length - 1]; - // The schema passed via props takes precedence until we remove the prop - const schema = props.schema === undefined ? schemaFromContext : props.schema; - let content: ReactNode = null; if (fetchError) { - content =
Error fetching schema
; + content = ( +
Error fetching schema
+ ); } else if (validationErrors.length > 0) { content = ( -
+
Schema is invalid: {validationErrors[0].message}
); @@ -53,13 +40,17 @@ export function DocExplorer(props: DocExplorerProps) { } else if (!schema) { // Schema is null when it explicitly does not exist, typically due to // an error during introspection. - content =
No Schema Available
; + content = ( +
+ No GraphQL schema available +
+ ); } else if (explorerNavStack.length === 1) { - content = ; + content = ; } else if (isType(navItem.def)) { - content = ; + content = ; } else if (navItem.def) { - content = ; + content = ; } let prevName; @@ -69,42 +60,31 @@ export function DocExplorer(props: DocExplorerProps) { return (
-
- {prevName && ( - - )} -
- {navItem.title || navItem.name} +
+
+ {prevName && ( + + + {prevName} + + )} +
+ {navItem.title || navItem.name} +
-
- +
+
-
- - {content} -
+
{content}
); } diff --git a/packages/graphiql-react/src/explorer/components/field-documentation.tsx b/packages/graphiql-react/src/explorer/components/field-documentation.tsx index 502833cf453..a3ddfcfd386 100644 --- a/packages/graphiql-react/src/explorer/components/field-documentation.tsx +++ b/packages/graphiql-react/src/explorer/components/field-documentation.tsx @@ -1,94 +1,94 @@ -import { isType } from 'graphql'; +import { GraphQLArgument } from 'graphql'; import { useState } from 'react'; -import { MarkdownContent } from '../../ui'; -import { useExplorerContext } from '../context'; +import { Button, MarkdownContent } from '../../ui'; +import { ExplorerFieldDef } from '../context'; import { Argument } from './argument'; +import { DeprecationReason } from './deprecation-reason'; import { Directive } from './directive'; +import { ExplorerSection } from './section'; import { TypeLink } from './type-link'; -export function FieldDocumentation() { - const { explorerNavStack } = useExplorerContext({ - nonNull: true, - caller: FieldDocumentation, - }); - const [showDeprecated, handleShowDeprecated] = useState(false); +type FieldDocumentationProps = { field: ExplorerFieldDef }; - const navItem = explorerNavStack[explorerNavStack.length - 1]; - const field = navItem.def; - if (!field || isType(field)) { +export function FieldDocumentation(props: FieldDocumentationProps) { + return ( + <> + {props.field.description ? ( + + {props.field.description} + + ) : null} + {props.field.deprecationReason} + + + + + + + ); +} + +function Arguments({ field }: { field: ExplorerFieldDef }) { + const [showDeprecated, setShowDeprecated] = useState(false); + + if (!('args' in field)) { return null; } - let argsDef; - let deprecatedArgsDef; - if (field && 'args' in field && field.args.length > 0) { - argsDef = ( -
-
arguments
- {field.args - .filter(arg => !arg.deprecationReason) - .map(arg => ( - - ))} -
- ); - const deprecatedArgs = field.args.filter(arg => - Boolean(arg.deprecationReason), - ); - if (deprecatedArgs.length > 0) { - deprecatedArgsDef = ( -
-
deprecated arguments
- {!showDeprecated ? ( - - ) : ( - deprecatedArgs.map(arg => ) - )} -
- ); + const args: GraphQLArgument[] = []; + const deprecatedArgs: GraphQLArgument[] = []; + for (const argument of field.args) { + if (argument.deprecationReason) { + deprecatedArgs.push(argument); + } else { + args.push(argument); } } - let directivesDef; - if (field?.astNode?.directives && field.astNode.directives.length > 0) { - directivesDef = ( -
-
directives
- {field.astNode.directives.map(directive => ( -
-
- -
-
- ))} -
- ); - } - return ( -
- - {field.description || 'No Description'} - - {field && 'deprecationReason' in field && field.deprecationReason ? ( - - {field.deprecationReason} - + <> + {args.length > 0 ? ( + + {args.map(arg => ( + + ))} + ) : null} -
-
type
- -
- {argsDef} - {directivesDef} - {deprecatedArgsDef} -
+ {deprecatedArgs.length > 0 ? ( + showDeprecated || args.length === 0 ? ( + + {deprecatedArgs.map(arg => ( + + ))} + + ) : ( + + ) + ) : null} + + ); +} + +function Directives({ field }: { field: ExplorerFieldDef }) { + const directives = field.astNode?.directives || []; + if (!directives || directives.length === 0) { + return null; + } + return ( + + {directives.map(directive => ( +
+ +
+ ))} +
); } diff --git a/packages/graphiql-react/src/explorer/components/schema-documentation.css b/packages/graphiql-react/src/explorer/components/schema-documentation.css new file mode 100644 index 00000000000..b7b06d5e4ab --- /dev/null +++ b/packages/graphiql-react/src/explorer/components/schema-documentation.css @@ -0,0 +1,3 @@ +.graphiql-doc-explorer-root-type { + color: var(--color-blue); +} diff --git a/packages/graphiql-react/src/explorer/components/schema-documentation.tsx b/packages/graphiql-react/src/explorer/components/schema-documentation.tsx index 5143b1c6be3..2c5ea55bd5c 100644 --- a/packages/graphiql-react/src/explorer/components/schema-documentation.tsx +++ b/packages/graphiql-react/src/explorer/components/schema-documentation.tsx @@ -1,51 +1,49 @@ -import { useSchemaContext } from '../../schema'; +import type { GraphQLSchema } from 'graphql'; + import { MarkdownContent } from '../../ui'; +import { ExplorerSection } from './section'; import { TypeLink } from './type-link'; -export function SchemaDocumentation() { - const { schema } = useSchemaContext({ - nonNull: true, - caller: SchemaDocumentation, - }); +import './schema-documentation.css'; - if (!schema) { - return null; - } +type SchemaDocumentationProps = { schema: GraphQLSchema }; - const queryType = schema.getQueryType(); - const mutationType = schema.getMutationType?.(); - const subscriptionType = schema.getSubscriptionType?.(); +export function SchemaDocumentation(props: SchemaDocumentationProps) { + const queryType = props.schema.getQueryType(); + const mutationType = props.schema.getMutationType?.(); + const subscriptionType = props.schema.getSubscriptionType?.(); return ( -
+ <> - {schema.description || + {props.schema.description || 'A GraphQL schema provides a root type for each kind of operation.'} -
-
root types
+ {queryType ? ( -
- query +
+ query {': '}
) : null} {mutationType && ( -
- mutation +
+ mutation {': '}
)} {subscriptionType && ( -
- subscription +
+ + subscription + {': '}
)} -
-
+ + ); } diff --git a/packages/graphiql-react/src/explorer/components/section.css b/packages/graphiql-react/src/explorer/components/section.css new file mode 100644 index 00000000000..3cc72bb874a --- /dev/null +++ b/packages/graphiql-react/src/explorer/components/section.css @@ -0,0 +1,22 @@ +.graphiql-doc-explorer-section-title { + align-items: center; + display: flex; + font-size: var(--font-size-hint); + font-weight: var(--font-weight-medium); + line-height: 1; + + & > svg { + height: var(--px-16); + margin-right: var(--px-8); + width: var(--px-16); + } +} + +.graphiql-doc-explorer-section-content { + margin-left: var(--px-8); + margin-top: var(--px-16); + + & > * + * { + margin-top: var(--px-16); + } +} diff --git a/packages/graphiql-react/src/explorer/components/section.tsx b/packages/graphiql-react/src/explorer/components/section.tsx new file mode 100644 index 00000000000..9d98fa19c2e --- /dev/null +++ b/packages/graphiql-react/src/explorer/components/section.tsx @@ -0,0 +1,63 @@ +import { ComponentType, ReactNode } from 'react'; + +import { + ArgumentIcon, + DeprecatedArgumentIcon, + DeprecatedEnumValueIcon, + DeprecatedFieldIcon, + DirectiveIcon, + EnumValueIcon, + FieldIcon, + ImplementsIcon, + RootTypeIcon, + TypeIcon, +} from '../../icons'; + +import './section.css'; + +type ExplorerSectionProps = { + children: ReactNode; + title: + | 'Root Types' + | 'Fields' + | 'Deprecated Fields' + | 'Type' + | 'Arguments' + | 'Deprecated Arguments' + | 'Implements' + | 'Implementations' + | 'Possible Types' + | 'Enum Values' + | 'Deprecated Enum Values' + | 'Directives'; +}; + +export function ExplorerSection(props: ExplorerSectionProps) { + const Icon = TYPE_TO_ICON[props.title]; + return ( +
+
+ + {props.title} +
+
+ {props.children} +
+
+ ); +} + +const TYPE_TO_ICON: Record = { + Arguments: ArgumentIcon, + 'Deprecated Arguments': DeprecatedArgumentIcon, + 'Deprecated Enum Values': DeprecatedEnumValueIcon, + 'Deprecated Fields': DeprecatedFieldIcon, + Directives: DirectiveIcon, + 'Enum Values': EnumValueIcon, + Fields: FieldIcon, + Implements: ImplementsIcon, + Implementations: TypeIcon, + 'Possible Types': TypeIcon, + 'Root Types': RootTypeIcon, + Type: TypeIcon, +}; diff --git a/packages/graphiql-react/src/explorer/components/type-documentation.css b/packages/graphiql-react/src/explorer/components/type-documentation.css new file mode 100644 index 00000000000..4da4394ea0e --- /dev/null +++ b/packages/graphiql-react/src/explorer/components/type-documentation.css @@ -0,0 +1,11 @@ +.graphiql-doc-explorer-item > :not(:first-child) { + margin-top: var(--px-12); +} + +.graphiql-doc-explorer-argument-multiple { + margin-left: var(--px-8); +} + +.graphiql-doc-explorer-enum-value { + color: var(--color-blue); +} diff --git a/packages/graphiql-react/src/explorer/components/type-documentation.tsx b/packages/graphiql-react/src/explorer/components/type-documentation.tsx index 66599fcda5d..03aff28649f 100644 --- a/packages/graphiql-react/src/explorer/components/type-documentation.tsx +++ b/packages/graphiql-react/src/explorer/components/type-documentation.tsx @@ -1,217 +1,208 @@ import { GraphQLEnumValue, - GraphQLInterfaceType, GraphQLNamedType, - GraphQLObjectType, + isAbstractType, isEnumType, + isInputObjectType, isInterfaceType, isNamedType, isObjectType, - isUnionType, } from 'graphql'; -import { ReactNode, useState } from 'react'; +import { useState } from 'react'; import { useSchemaContext } from '../../schema'; -import { MarkdownContent } from '../../ui'; -import { ExplorerFieldDef, useExplorerContext } from '../context'; +import { Button, MarkdownContent } from '../../ui'; +import { ExplorerFieldDef } from '../context'; import { Argument } from './argument'; import { DefaultValue } from './default-value'; +import { DeprecationReason } from './deprecation-reason'; import { FieldLink } from './field-link'; +import { ExplorerSection } from './section'; import { TypeLink } from './type-link'; -export function TypeDocumentation() { - const { schema } = useSchemaContext({ - nonNull: true, - caller: TypeDocumentation, - }); - const { explorerNavStack } = useExplorerContext({ - nonNull: true, - caller: TypeDocumentation, - }); - const [showDeprecated, setShowDeprecated] = useState(false); +import './type-documentation.css'; - const navItem = explorerNavStack[explorerNavStack.length - 1]; - const type = navItem.def; +type TypeDocumentationProps = { type: GraphQLNamedType }; - if (!schema || !isNamedType(type)) { +export function TypeDocumentation(props: TypeDocumentationProps) { + return isNamedType(props.type) ? ( + <> + {props.type.description ? ( + + {props.type.description} + + ) : null} + + + + + + ) : null; +} + +function ImplementsInterfaces({ type }: { type: GraphQLNamedType }) { + if (!isObjectType(type)) { return null; } + const interfaces = type.getInterfaces(); + return interfaces.length > 0 ? ( + + {type.getInterfaces().map(implementedInterface => ( +
+ +
+ ))} +
+ ) : null; +} - let typesTitle: string | null = null; - let types: readonly (GraphQLObjectType | GraphQLInterfaceType)[] = []; - if (isUnionType(type)) { - typesTitle = 'possible types'; - types = schema.getPossibleTypes(type); - } else if (isInterfaceType(type)) { - typesTitle = 'implementations'; - types = schema.getPossibleTypes(type); - } else if (isObjectType(type)) { - typesTitle = 'implements'; - types = type.getInterfaces(); +function Fields({ type }: { type: GraphQLNamedType }) { + const [showDeprecated, setShowDeprecated] = useState(false); + if ( + !isObjectType(type) && + !isInterfaceType(type) && + !isInputObjectType(type) + ) { + return null; } - let typesDef; - if (types && types.length > 0) { - typesDef = ( -
-
{typesTitle}
- {types.map(subtype => ( -
- -
- ))} -
- ); - } + const fieldMap = type.getFields(); - // InputObject and Object - let fieldsDef; - let deprecatedFieldsDef; - if (type && 'getFields' in type) { - const fieldMap = type.getFields(); - const fields = Object.keys(fieldMap).map(name => fieldMap[name]); - fieldsDef = ( -
-
fields
- {fields - .filter(field => !field.deprecationReason) - .map(field => ( - - ))} -
- ); - - const deprecatedFields = fields.filter(field => - Boolean(field.deprecationReason), - ); - if (deprecatedFields.length > 0) { - deprecatedFieldsDef = ( -
-
deprecated fields
- {!showDeprecated ? ( - - ) : ( - deprecatedFields.map(field => ( - - )) - )} -
- ); - } - } + const fields: ExplorerFieldDef[] = []; + const deprecatedFields: ExplorerFieldDef[] = []; - let valuesDef: ReactNode; - let deprecatedValuesDef: ReactNode; - if (isEnumType(type)) { - const values = type.getValues(); - valuesDef = ( -
-
values
- {values - .filter(value => Boolean(!value.deprecationReason)) - .map(value => ( - - ))} -
- ); - - const deprecatedValues = values.filter(value => - Boolean(value.deprecationReason), - ); - if (deprecatedValues.length > 0) { - deprecatedValuesDef = ( -
-
deprecated values
- {!showDeprecated ? ( - - ) : ( - deprecatedValues.map(value => ( - - )) - )} -
- ); + for (const field of Object.keys(fieldMap).map(name => fieldMap[name])) { + if (field.deprecationReason) { + deprecatedFields.push(field); + } else { + fields.push(field); } } return ( -
- - {('description' in type && type.description) || 'No Description'} - - {isObjectType(type) && typesDef} - {fieldsDef} - {deprecatedFieldsDef} - {valuesDef} - {deprecatedValuesDef} - {!isObjectType(type) && typesDef} -
+ <> + {fields.length > 0 ? ( + + {fields.map(field => ( + + ))} + + ) : null} + {deprecatedFields.length > 0 ? ( + showDeprecated || fields.length === 0 ? ( + + {deprecatedFields.map(field => ( + + ))} + + ) : ( + + ) + ) : null} + ); } -type FieldProps = { - type: GraphQLNamedType; - field: ExplorerFieldDef; -}; - -function Field({ field }: FieldProps) { +function Field({ field }: { field: ExplorerFieldDef }) { + const args = + 'args' in field ? field.args.filter(arg => !arg.deprecationReason) : []; return ( -
- - {'args' in field && - field.args && - field.args.length > 0 && [ - '(', - - {field.args - .filter(arg => !arg.deprecationReason) - .map(arg => ( - - ))} - , - ')', - ]} - {': '} - - +
+
+ + {args.length > 0 ? ( + <> + ( + + {args.map(arg => + args.length === 1 ? ( + + ) : ( +
+ +
+ ), + )} +
+ ) + + ) : null} + {': '} + + +
{field.description ? ( - + {field.description} ) : null} - {'deprecationReason' in field && field.deprecationReason ? ( - - {field.deprecationReason} - - ) : null} + {field.deprecationReason}
); } -type EnumValueProps = { - value: GraphQLEnumValue; -}; +function EnumValues({ type }: { type: GraphQLNamedType }) { + const [showDeprecated, setShowDeprecated] = useState(false); + + if (!isEnumType(type)) { + return null; + } + + const values: GraphQLEnumValue[] = []; + const deprecatedValues: GraphQLEnumValue[] = []; + for (const value of type.getValues()) { + if (value.deprecationReason) { + deprecatedValues.push(value); + } else { + values.push(value); + } + } -function EnumValue({ value }: EnumValueProps) { return ( -
-
{value.name}
+ <> + {values.length > 0 ? ( + + {values.map(value => ( + + ))} + + ) : null} + {deprecatedValues.length > 0 ? ( + showDeprecated || values.length === 0 ? ( + + {deprecatedValues.map(value => ( + + ))} + + ) : ( + + ) + ) : null} + + ); +} + +function EnumValue({ value }: { value: GraphQLEnumValue }) { + return ( +
+
{value.name}
{value.description ? ( {value.description} @@ -225,3 +216,21 @@ function EnumValue({ value }: EnumValueProps) {
); } + +function PossibleTypes({ type }: { type: GraphQLNamedType }) { + const { schema } = useSchemaContext({ nonNull: true }); + if (!schema || !isAbstractType(type)) { + return null; + } + return ( + + {schema.getPossibleTypes(type).map(possibleType => ( +
+ +
+ ))} +
+ ); +} diff --git a/packages/graphiql-react/src/explorer/index.ts b/packages/graphiql-react/src/explorer/index.ts index 64d236b2fe8..9ecdb58bac2 100644 --- a/packages/graphiql-react/src/explorer/index.ts +++ b/packages/graphiql-react/src/explorer/index.ts @@ -1,11 +1,13 @@ export { Argument } from './components/argument'; export { DefaultValue } from './components/default-value'; +export { DeprecationReason } from './components/deprecation-reason'; export { Directive } from './components/directive'; export { DocExplorer } from './components/doc-explorer'; export { FieldDocumentation } from './components/field-documentation'; export { FieldLink } from './components/field-link'; export { SchemaDocumentation } from './components/schema-documentation'; export { Search } from './components/search'; +export { ExplorerSection } from './components/section'; export { TypeDocumentation } from './components/type-documentation'; export { TypeLink } from './components/type-link'; export { diff --git a/packages/graphiql-react/src/icons/argument.svg b/packages/graphiql-react/src/icons/argument.svg new file mode 100644 index 00000000000..3a981c90c84 --- /dev/null +++ b/packages/graphiql-react/src/icons/argument.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/graphiql-react/src/icons/chevron-left.svg b/packages/graphiql-react/src/icons/chevron-left.svg new file mode 100644 index 00000000000..b740c1d21df --- /dev/null +++ b/packages/graphiql-react/src/icons/chevron-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/graphiql-react/src/icons/deprecated-argument.svg b/packages/graphiql-react/src/icons/deprecated-argument.svg new file mode 100644 index 00000000000..5da9ce09f52 --- /dev/null +++ b/packages/graphiql-react/src/icons/deprecated-argument.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/graphiql-react/src/icons/deprecated-enum-value.svg b/packages/graphiql-react/src/icons/deprecated-enum-value.svg new file mode 100644 index 00000000000..8e44d419817 --- /dev/null +++ b/packages/graphiql-react/src/icons/deprecated-enum-value.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/graphiql-react/src/icons/deprecated-field.svg b/packages/graphiql-react/src/icons/deprecated-field.svg new file mode 100644 index 00000000000..fd07672d975 --- /dev/null +++ b/packages/graphiql-react/src/icons/deprecated-field.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/graphiql-react/src/icons/directive.svg b/packages/graphiql-react/src/icons/directive.svg new file mode 100644 index 00000000000..5cdd18314ff --- /dev/null +++ b/packages/graphiql-react/src/icons/directive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/graphiql-react/src/icons/enum-value.svg b/packages/graphiql-react/src/icons/enum-value.svg new file mode 100644 index 00000000000..d677ef519f0 --- /dev/null +++ b/packages/graphiql-react/src/icons/enum-value.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/graphiql-react/src/icons/field.svg b/packages/graphiql-react/src/icons/field.svg new file mode 100644 index 00000000000..58bfbfa987f --- /dev/null +++ b/packages/graphiql-react/src/icons/field.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/graphiql-react/src/icons/implements.svg b/packages/graphiql-react/src/icons/implements.svg new file mode 100644 index 00000000000..31b7ba1c310 --- /dev/null +++ b/packages/graphiql-react/src/icons/implements.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/graphiql-react/src/icons/index.tsx b/packages/graphiql-react/src/icons/index.tsx index 53857f87213..3f2bea36a2e 100644 --- a/packages/graphiql-react/src/icons/index.tsx +++ b/packages/graphiql-react/src/icons/index.tsx @@ -1,9 +1,18 @@ +import _ArgumentIcon from './argument.svg'; import _ChevronDownIcon from './chevron-down.svg'; +import _ChevronLeftIcon from './chevron-left.svg'; import _ChevronUpIcon from './chevron-up.svg'; import _CloseIcon from './close.svg'; import _CopyIcon from './copy.svg'; +import _DeprecatedArgumentIcon from './deprecated-argument.svg'; +import _DeprecatedEnumValueIcon from './deprecated-enum-value.svg'; +import _DeprecatedFieldIcon from './deprecated-field.svg'; +import _DirectiveIcon from './directive.svg'; import _DocsIcon from './docs.svg'; +import _EnumValueIcon from './enum-value.svg'; +import _FieldIcon from './field.svg'; import _HistoryIcon from './history.svg'; +import _ImplementsIcon from './implements.svg'; import _KeyboardShortcutIcon from './keyboard-shortcut.svg'; import _MagnifyingGlassIcon from './magnifying-glass.svg'; import _MergeIcon from './merge.svg'; @@ -11,20 +20,43 @@ import _PenIcon from './pen.svg'; import _PlayIcon from './play.svg'; import _PrettifyIcon from './prettify.svg'; import _ReloadIcon from './reload.svg'; +import _RootTypeIcon from './root-type.svg'; import _SettingsIcon from './settings.svg'; import _StarFilledIcon from './star-filled.svg'; import _StarIcon from './star.svg'; import _StopIcon from './stop.svg'; +import _TypeIcon from './type.svg'; +export const ArgumentIcon = generateIcon(_ArgumentIcon, 'argument icon'); export const ChevronDownIcon = generateIcon( _ChevronDownIcon, 'chevron down icon', ); +export const ChevronLeftIcon = generateIcon( + _ChevronLeftIcon, + 'chevron left icon', +); export const ChevronUpIcon = generateIcon(_ChevronUpIcon, 'chevron up icon'); export const CloseIcon = generateIcon(_CloseIcon, 'close icon'); export const CopyIcon = generateIcon(_CopyIcon, 'copy icon'); +export const DeprecatedArgumentIcon = generateIcon( + _DeprecatedArgumentIcon, + 'deprecated argument icon', +); +export const DeprecatedEnumValueIcon = generateIcon( + _DeprecatedEnumValueIcon, + 'deprecated enum value icon', +); +export const DeprecatedFieldIcon = generateIcon( + _DeprecatedFieldIcon, + 'deprecated field icon', +); +export const DirectiveIcon = generateIcon(_DirectiveIcon, 'directive icon'); export const DocsIcon = generateIcon(_DocsIcon, 'docs icon'); +export const EnumValueIcon = generateIcon(_EnumValueIcon, 'enum value icon'); +export const FieldIcon = generateIcon(_FieldIcon, 'field icon'); export const HistoryIcon = generateIcon(_HistoryIcon, 'history icon'); +export const ImplementsIcon = generateIcon(_ImplementsIcon, 'implements icon'); export const KeyboardShortcutIcon = generateIcon( _KeyboardShortcutIcon, 'keyboard shortcut icon', @@ -38,10 +70,12 @@ export const PenIcon = generateIcon(_PenIcon, 'pen icon'); export const PlayIcon = generateIcon(_PlayIcon, 'play icon'); export const PrettifyIcon = generateIcon(_PrettifyIcon, 'prettify icon'); export const ReloadIcon = generateIcon(_ReloadIcon, 'reload icon'); +export const RootTypeIcon = generateIcon(_RootTypeIcon, 'root type icon'); export const SettingsIcon = generateIcon(_SettingsIcon, 'settings icon'); export const StarFilledIcon = generateIcon(_StarFilledIcon, 'filled star icon'); export const StarIcon = generateIcon(_StarIcon, 'star icon'); export const StopIcon = generateIcon(_StopIcon, 'stop icon'); +export const TypeIcon = generateIcon(_TypeIcon, 'type icon'); function generateIcon(RawComponent: any, title: string) { const WithTitle = function IconComponent( diff --git a/packages/graphiql-react/src/icons/root-type.svg b/packages/graphiql-react/src/icons/root-type.svg new file mode 100644 index 00000000000..29ffd5a325a --- /dev/null +++ b/packages/graphiql-react/src/icons/root-type.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/graphiql-react/src/icons/type.svg b/packages/graphiql-react/src/icons/type.svg new file mode 100644 index 00000000000..59c2c5b21b5 --- /dev/null +++ b/packages/graphiql-react/src/icons/type.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index ff3bfc18637..907840192af 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -25,10 +25,12 @@ export { export { Argument, DefaultValue, + DeprecationReason, Directive, DocExplorer, ExplorerContext, ExplorerContextProvider, + ExplorerSection, FieldDocumentation, FieldLink, SchemaDocumentation, diff --git a/packages/graphiql-react/src/style/root.css b/packages/graphiql-react/src/style/root.css index 17fec5d4fec..3465c504a70 100644 --- a/packages/graphiql-react/src/style/root.css +++ b/packages/graphiql-react/src/style/root.css @@ -31,6 +31,7 @@ --font-size-inline-code: calc(13rem / 16); --font-size-body: calc(15rem / 16); --font-size-h4: calc(18rem / 16); + --font-size-h3: calc(22rem / 16); --font-size-h2: calc(29rem / 16); --font-weight-regular: 400; --font-weight-medium: 500; @@ -44,6 +45,7 @@ --px-10: 10px; --px-12: 12px; --px-16: 16px; + --px-20: 20px; /* Border radius */ --border-radius-2: 2px; diff --git a/packages/graphiql-react/src/ui/button.css b/packages/graphiql-react/src/ui/button.css index a8e98e46e23..c81313e2b09 100644 --- a/packages/graphiql-react/src/ui/button.css +++ b/packages/graphiql-react/src/ui/button.css @@ -11,3 +11,21 @@ button.graphiql-un-styled { outline: var(--color-neutral-15) auto 1px; } } + +button.graphiql-button { + background-color: var(--color-neutral-7); + border: none; + border-radius: var(--border-radius-4); + color: var(--color-neutral-100); + cursor: pointer; + font-size: var(--font-size-body); + padding: var(--px-8) var(--px-12); + + &:active { + background-color: var(--color-neutral-10); + } + + &:focus { + outline: var(--color-neutral-15) auto 1px; + } +} diff --git a/packages/graphiql-react/src/ui/button.tsx b/packages/graphiql-react/src/ui/button.tsx index 93d7c36dd59..50a30763ff3 100644 --- a/packages/graphiql-react/src/ui/button.tsx +++ b/packages/graphiql-react/src/ui/button.tsx @@ -8,3 +8,12 @@ export function UnStyledButton(props: JSX.IntrinsicElements['button']) { /> ); } + +export function Button(props: JSX.IntrinsicElements['button']) { + return ( +
diff --git a/packages/graphiql/src/css/doc-explorer.css b/packages/graphiql/src/css/doc-explorer.css deleted file mode 100644 index 167b5ca5237..00000000000 --- a/packages/graphiql/src/css/doc-explorer.css +++ /dev/null @@ -1,282 +0,0 @@ -.graphiql-container .doc-explorer { - background: white; -} - -.graphiql-container .doc-explorer-title-bar { - cursor: default; - display: flex; - height: 34px; - line-height: 14px; - padding: 8px 8px 5px; - position: relative; - user-select: none; -} - -.graphiql-container .doc-explorer-title { - flex: 1; - font-weight: bold; - overflow-x: hidden; - padding: 10px 0 10px 10px; - text-align: center; - text-overflow: ellipsis; - user-select: text; - white-space: nowrap; -} - -.graphiql-container .doc-explorer-back { - color: #3b5998; - cursor: pointer; - margin: -7px 0 -6px -8px; - overflow-x: hidden; - padding: 17px 12px 16px 16px; - text-overflow: ellipsis; - white-space: nowrap; - background: 0; - border: 0; - line-height: 14px; -} - -.graphiql-container .doc-explorer-back:before { - border-left: 2px solid #3b5998; - border-top: 2px solid #3b5998; - content: ''; - display: inline-block; - height: 9px; - margin: 0 3px -1px 0; - position: relative; - transform: rotate(-45deg); - width: 9px; -} - -.graphiql-container .doc-explorer-rhs { - position: relative; -} - -.graphiql-container .doc-explorer-contents { - background-color: #ffffff; - border-top: 1px solid #d6d6d6; - overflow-y: auto; - padding: 20px 15px; -} - -.graphiql-container .doc-type-description p:first-child, -.graphiql-container .doc-type-description blockquote:first-child { - margin-top: 0; -} - -.graphiql-container .doc-explorer-contents a { - cursor: pointer; - text-decoration: none; -} - -.graphiql-container .doc-explorer-contents a:hover { - text-decoration: underline; -} - -.graphiql-container .doc-value-description > :first-child { - margin-top: 4px; -} - -.graphiql-container .doc-value-description > :last-child { - margin-bottom: 4px; -} - -.graphiql-container .doc-type-description code, -.graphiql-container .doc-type-description pre, -.graphiql-container .doc-category code, -.graphiql-container .doc-category pre { - --saf-0: rgba(var(--sk_foreground_low, 29, 28, 29), 0.13); - font-size: 12px; - line-height: 1.50001; - font-variant-ligatures: none; - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; - word-break: normal; - -webkit-tab-size: 4; - -moz-tab-size: 4; - tab-size: 4; -} - -.graphiql-container .doc-type-description code, -.graphiql-container .doc-category code { - padding: 2px 3px 1px; - border: 1px solid var(--saf-0); - border-radius: 3px; - background-color: rgba(var(--sk_foreground_min, 29, 28, 29), 0.04); - color: #e01e5a; - background-color: white; -} - -.graphiql-container .doc-category { - margin: 20px 0; -} - -.graphiql-container .doc-category-title { - border-bottom: 1px solid #e0e0e0; - color: #777; - cursor: default; - font-size: 14px; - font-variant: small-caps; - font-weight: bold; - letter-spacing: 1px; - margin: 0 -15px 10px 0; - padding: 10px 0; - user-select: none; -} - -.graphiql-container .doc-category-item { - margin: 12px 0; - color: #555; -} - -.graphiql-container .keyword { - color: #b11a04; -} - -.graphiql-container .type-name { - color: #ca9800; -} - -.graphiql-container .field-name { - color: #1f61a0; -} - -.graphiql-container .field-short-description { - color: #666; - margin-left: 5px; - overflow: hidden; - text-overflow: ellipsis; -} - -.graphiql-container .enum-value { - color: #0b7fc7; -} - -.graphiql-container .arg-name { - color: #8b2bb9; -} - -.graphiql-container .arg { - display: block; - margin-left: 1em; -} - -.graphiql-container .arg:first-child:last-child, -.graphiql-container .arg:first-child:nth-last-child(2), -.graphiql-container .arg:first-child:nth-last-child(2) ~ .arg { - display: inherit; - margin: inherit; -} - -.graphiql-container .arg:first-child:nth-last-child(2):after { - content: ', '; -} - -.graphiql-container .arg-default-value { - color: #43a047; -} - -.graphiql-container .doc-deprecation { - background: #fffae8; - box-shadow: inset 0 0 1px #bfb063; - color: #867f70; - line-height: 16px; - margin: 8px -8px; - max-height: 80px; - overflow: hidden; - padding: 8px; - border-radius: 3px; -} - -.graphiql-container .doc-deprecation:before { - content: 'Deprecated:'; - color: #c79b2e; - cursor: default; - display: block; - font-size: 9px; - font-weight: bold; - letter-spacing: 1px; - line-height: 1; - padding-bottom: 5px; - text-transform: uppercase; - user-select: none; -} - -.graphiql-container .doc-deprecation > :first-child { - margin-top: 0; -} - -.graphiql-container .doc-deprecation > :last-child { - margin-bottom: 0; -} - -.graphiql-container .show-btn { - -webkit-appearance: initial; - display: block; - border-radius: 3px; - border: solid 1px #ccc; - text-align: center; - padding: 8px 12px 10px; - width: 100%; - box-sizing: border-box; - background: #fbfcfc; - color: #555; - cursor: pointer; -} - -.graphiql-container .search-box { - border-bottom: 1px solid #d3d6db; - display: flex; - align-items: center; - font-size: 14px; - margin: -15px -15px 12px 0; - position: relative; -} - -.graphiql-container .search-box-icon { - cursor: pointer; - display: block; - font-size: 24px; - transform: rotate(-45deg); - user-select: none; -} - -.graphiql-container .search-box .search-box-clear { - background-color: #d0d0d0; - border-radius: 12px; - color: #fff; - cursor: pointer; - font-size: 11px; - padding: 1px 5px 2px; - position: absolute; - right: 3px; - user-select: none; - border: 0; -} - -.graphiql-container .search-box .search-box-clear:hover { - background-color: #b9b9b9; -} - -.graphiql-container .search-box > input { - border: none; - box-sizing: border-box; - font-size: 14px; - outline: none; - padding: 6px 24px 8px 20px; - width: 100%; -} - -.graphiql-container .error-container { - font-weight: bold; - left: 0; - letter-spacing: 1px; - opacity: 0.5; - position: absolute; - right: 0; - text-align: center; - text-transform: uppercase; - top: 50%; - transform: translate(0, -50%); -} diff --git a/packages/graphiql/src/style.css b/packages/graphiql/src/style.css index 5ba47400278..e59b372eef1 100644 --- a/packages/graphiql/src/style.css +++ b/packages/graphiql/src/style.css @@ -142,6 +142,7 @@ border-left: 1px solid var(--color-neutral-15); flex: 1; max-width: calc(100% - 2 * var(--px-16)); + overflow-y: auto; padding: var(--px-16); }