Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/doc-explorer-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphiql/plugin-doc-explorer': minor
---

Redesign the Schema Explorer panel: eyebrow header with filter and search icon buttons, dedicated breadcrumb row with color-coded depth segments, inline search row with keycap hint, type card with TYPE badge and implements list, and a mono field list with type colors and active-row accent border.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Mock, describe, it, expect, vi, beforeEach } from 'vitest';
import { useGraphiQL as $useGraphiQL } from '@graphiql/react';
import { useGraphiQL as $useGraphiQL, Tooltip } from '@graphiql/react';
import { render } from '@testing-library/react';
import { GraphQLInt, GraphQLObjectType, GraphQLSchema } from 'graphql';
import { FC, useEffect } from 'react';
Expand Down Expand Up @@ -54,9 +54,11 @@ const withErrorSchemaContext = {

const DocExplorerWithContext: FC = () => {
return (
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>
<Tooltip.Provider>
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>
</Tooltip.Provider>
);
};

Expand Down Expand Up @@ -88,8 +90,8 @@ describe('DocExplorer', () => {
const error = container.querySelectorAll('.graphiql-doc-explorer-error');
expect(error).toHaveLength(0);
expect(
container.querySelector('.graphiql-markdown-description'),
).toHaveTextContent('GraphQL Schema for testing');
container.querySelector('.graphiql-doc-explorer-schema-overview'),
).toBeInTheDocument();
});
it('renders correctly with schema error', () => {
useGraphiQL.mockImplementation(cb => cb(withErrorSchemaContext));
Expand Down Expand Up @@ -124,20 +126,27 @@ describe('DocExplorer', () => {
cb({ ...defaultSchemaContext, schema: initialSchema }),
);
const { container, rerender } = render(
<DocExplorerStore>
<SetInitialStack />
</DocExplorerStore>,
<Tooltip.Provider>
<DocExplorerStore>
<SetInitialStack />
</DocExplorerStore>
</Tooltip.Provider>,
);

// First proper render of doc explorer
rerender(
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>,
<Tooltip.Provider>
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>
</Tooltip.Provider>,
);

const title = container.querySelector('.graphiql-doc-explorer-title')!;
expect(title.textContent).toEqual('field');
// The current page is shown as the last breadcrumb segment
const breadcrumb = container.querySelector(
'.graphiql-doc-explorer-breadcrumb-current',
)!;
expect(breadcrumb.textContent).toEqual('field');

// Second render of doc explorer, this time with a new schema, with _same_ field name
useGraphiQL.mockImplementation(cb =>
Expand All @@ -147,13 +156,17 @@ describe('DocExplorer', () => {
}),
);
rerender(
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>,
<Tooltip.Provider>
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>
</Tooltip.Provider>,
);
const title2 = container.querySelector('.graphiql-doc-explorer-title')!;
const breadcrumb2 = container.querySelector(
'.graphiql-doc-explorer-breadcrumb-current',
)!;
// Because `Query.field` still exists in the new schema, we can still render it
expect(title2.textContent).toEqual('field');
expect(breadcrumb2.textContent).toEqual('field');
});
it('trims nav stack when necessary', () => {
const initialSchema = makeSchema();
Expand All @@ -179,20 +192,26 @@ describe('DocExplorer', () => {
cb({ ...defaultSchemaContext, schema: initialSchema }),
);
const { container, rerender } = render(
<DocExplorerStore>
<SetInitialStack />
</DocExplorerStore>,
<Tooltip.Provider>
<DocExplorerStore>
<SetInitialStack />
</DocExplorerStore>
</Tooltip.Provider>,
);

// First proper render of doc explorer
rerender(
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>,
<Tooltip.Provider>
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>
</Tooltip.Provider>,
);

const title = container.querySelector('.graphiql-doc-explorer-title')!;
expect(title.textContent).toEqual('field');
const breadcrumb = container.querySelector(
'.graphiql-doc-explorer-breadcrumb-current',
)!;
expect(breadcrumb.textContent).toEqual('field');

// Second render of doc explorer, this time with a new schema, with a different field name
useGraphiQL.mockImplementation(cb =>
Expand All @@ -202,12 +221,16 @@ describe('DocExplorer', () => {
}),
);
rerender(
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>,
<Tooltip.Provider>
<DocExplorerStore>
<DocExplorer />
</DocExplorerStore>
</Tooltip.Provider>,
);
const title2 = container.querySelector('.graphiql-doc-explorer-title')!;
const breadcrumb2 = container.querySelector(
'.graphiql-doc-explorer-breadcrumb-current',
)!;
// Because `Query.field` doesn't exist anymore, the top-most item we can render is `Query`
expect(title2.textContent).toEqual('Query');
expect(breadcrumb2.textContent).toEqual('Query');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe('FieldDocumentation', () => {
/>,
);
expect(
container.querySelector('.graphiql-markdown-description'),
container.querySelector('.graphiql-doc-explorer-field-card-description'),
).not.toBeInTheDocument();
expect(
container.querySelector('.graphiql-doc-explorer-type-name'),
Expand All @@ -95,7 +95,7 @@ describe('FieldDocumentation', () => {
/>,
);
expect(
container.querySelector('.graphiql-markdown-description'),
container.querySelector('.graphiql-doc-explorer-field-card-description'),
).not.toBeInTheDocument();
expect(
container.querySelector('.graphiql-doc-explorer-type-name'),
Expand All @@ -113,7 +113,7 @@ describe('FieldDocumentation', () => {
container.querySelector('.graphiql-doc-explorer-type-name'),
).toHaveTextContent('String');
expect(
container.querySelector('.graphiql-markdown-description'),
container.querySelector('.graphiql-doc-explorer-field-card-description'),
).toHaveTextContent('Example String field with arguments');
});

Expand All @@ -127,14 +127,14 @@ describe('FieldDocumentation', () => {
container.querySelector('.graphiql-doc-explorer-type-name'),
).toHaveTextContent('String');
expect(
container.querySelector('.graphiql-markdown-description'),
container.querySelector('.graphiql-doc-explorer-field-card-description'),
).toHaveTextContent('Example String field with arguments');
expect(
container.querySelectorAll('.graphiql-doc-explorer-argument'),
).toHaveLength(1);
expect(
container.querySelector('.graphiql-doc-explorer-argument'),
).toHaveTextContent('stringArg: String');
).toHaveTextContent('stringArg:String');
// by default, the deprecation docs should be hidden
expect(
container.querySelectorAll('.graphiql-markdown-deprecation'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
.graphiql-doc-explorer-arguments-list {
display: flex;
flex-direction: column;
padding: var(--px-4) 0;
}

/* Section header — "ARGUMENTS · N" / "DIRECTIVES · N" */
.graphiql-doc-explorer-arguments-list-header {
display: flex;
align-items: center;
gap: var(--px-6);
padding: var(--px-6) var(--px-16) var(--px-4);
background: transparent;
border: none;
cursor: pointer;
color: oklch(var(--fg-subtle));
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
width: 100%;
text-align: left;

& svg {
width: 9px;
height: 9px;
flex-shrink: 0;
}

&:hover {
color: oklch(var(--fg-muted));
}

&:focus-visible {
outline: 2px solid oklch(var(--accent-blue));
outline-offset: -2px;
}
}

.graphiql-doc-explorer-arguments-list-count {
color: oklch(var(--fg-dim));
font-weight: 500;
}

.graphiql-doc-explorer-arguments-list-body {
display: flex;
flex-direction: column;
}

/* Individual argument row */
.graphiql-doc-explorer-argument {
display: flex;
flex-direction: column;
gap: var(--px-4);
padding: 5px var(--px-16) 5px var(--px-24);
}

.graphiql-doc-explorer-argument--deprecated {
opacity: 0.6;
}

/* Signature line: name : type = default */
.graphiql-doc-explorer-argument-sig {
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: var(--px-4);
font-family: var(--font-family-mono);
font-size: 12px;
}

.graphiql-doc-explorer-argument-name {
color: oklch(var(--accent-green-light));
}

.graphiql-doc-explorer-argument-colon {
color: oklch(var(--fg-disabled));
}

.graphiql-doc-explorer-argument-type {
color: oklch(var(--accent-orange));

& .graphiql-doc-explorer-type-name {
color: oklch(var(--accent-orange));

&:hover {
text-decoration: underline;
}
}
}

.graphiql-doc-explorer-argument-default {
color: oklch(var(--fg-muted));
}

.graphiql-doc-explorer-argument-desc {
font-size: 11px;
color: oklch(var(--fg-subtle));
line-height: 15px;
}

.graphiql-doc-explorer-arguments-list-show-deprecated {
margin: var(--px-8) var(--px-16);
}

/* Directives — same eyebrow layout as ArgumentsList */
.graphiql-doc-explorer-directives-list {
display: flex;
flex-direction: column;
padding: var(--px-4) 0;
}

.graphiql-doc-explorer-directives-list-header {
padding: var(--px-6) var(--px-16) var(--px-4);
color: oklch(var(--fg-subtle));
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
}

.graphiql-doc-explorer-directives-list-count {
color: oklch(var(--fg-dim));
font-weight: 500;
}

.graphiql-doc-explorer-directives-list-body {
display: flex;
flex-direction: column;
}

.graphiql-doc-explorer-directive-row {
padding: 5px var(--px-16) 5px var(--px-24);
font-family: var(--font-family-mono);
font-size: 12px;
}

.graphiql-doc-explorer-directive-row .graphiql-doc-explorer-directive {
color: oklch(var(--accent-orange));
}
Loading
Loading