Skip to content

Commit b73d951

Browse files
v6: Redesign Schema Explorer panel structure (#4299)
## Summary - `PanelHeader` for the docs panel becomes a fixed eyebrow label, `Schema Explorer`. The type name moves into a new breadcrumb row below the header. - New `SchemaDocumentation` root view: two eyebrow sections (`ROOT TYPES` and `ALL SCHEMA TYPES · N`) replace the old accordion. Root operation rows show a `MethodPill` and target type name. All-types rows show a kind badge (`TYPE`, `SCALAR`, `ENUM`, `INPUT`, `INTERFACE`, `UNION`) and type name in mono. - New `Breadcrumb` row: mono font, depth segments color-coded as `--accent-blue` / `--accent-green-light` / `--fg-strong`, chevron separators; clicking a segment navigates back to that depth. - New `SearchRow` content-area component: inline `Search schema` input with keycap hint; listbox renders as a floating popover in the content area, replacing the old absolutely-positioned search overlay. - New `TypeCard`: `TYPE` badge (mono, uppercase, accent-blue tinted), type name in mono, description, and an `implements X · Y` row with `--accent-orange` links. - New `FieldsList`: collapsible `FIELDS · N` header, mono rows with field name in `--accent-green-light`, colon in `--fg-muted`, return type in `--accent-orange`, optional description in `--fg-subtle`, and a 2px `--accent-blue` left border on the active row. Horizontal padding bumped to 16px across all content sections for breathing room. - `TypeDocumentation` gains a `hideHeader` prop so it can suppress its own description / implements / fields block when `TypeCard` + `FieldsList` render those above. ## Test plan - [ ] Open the Docs panel. Confirm the header shows `SCHEMA EXPLORER` with no extra buttons, a search input, and two eyebrow sections: `ROOT TYPES` and `ALL SCHEMA TYPES · N`. - [ ] Confirm each root operation row shows its `MethodPill` (`QRY`/`MUT`/`SUB`), operation label, and target type name. Click a row and confirm it navigates into the type detail. - [ ] In `ALL SCHEMA TYPES`, confirm each row shows a kind badge and mono type name. Click a row and confirm navigation. - [ ] Confirm the breadcrumb row appears below the header when inside a type, with color-coded segments. Click an intermediate segment and confirm it navigates back. - [ ] Confirm `TypeCard` shows the kind badge, name, description, and `implements` list for an object type that implements interfaces. - [ ] Confirm `FieldsList` shows mono rows with green field names and orange return types; click a field row and confirm it navigates in. - [ ] Type in the inline search row and confirm the listbox popover appears with results. - [ ] Toggle light / dark theme; confirm root view and type-detail both render correctly. - [ ] Navigate to an enum type and confirm enum values still render below. Refs: #4219
1 parent dabeb6d commit b73d951

27 files changed

Lines changed: 1707 additions & 316 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphiql/plugin-doc-explorer': minor
3+
---
4+
5+
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.

packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type Mock, describe, it, expect, vi, beforeEach } from 'vitest';
2-
import { useGraphiQL as $useGraphiQL } from '@graphiql/react';
2+
import { useGraphiQL as $useGraphiQL, Tooltip } from '@graphiql/react';
33
import { render } from '@testing-library/react';
44
import { GraphQLInt, GraphQLObjectType, GraphQLSchema } from 'graphql';
55
import { FC, useEffect } from 'react';
@@ -54,9 +54,11 @@ const withErrorSchemaContext = {
5454

5555
const DocExplorerWithContext: FC = () => {
5656
return (
57-
<DocExplorerStore>
58-
<DocExplorer />
59-
</DocExplorerStore>
57+
<Tooltip.Provider>
58+
<DocExplorerStore>
59+
<DocExplorer />
60+
</DocExplorerStore>
61+
</Tooltip.Provider>
6062
);
6163
};
6264

@@ -88,8 +90,8 @@ describe('DocExplorer', () => {
8890
const error = container.querySelectorAll('.graphiql-doc-explorer-error');
8991
expect(error).toHaveLength(0);
9092
expect(
91-
container.querySelector('.graphiql-markdown-description'),
92-
).toHaveTextContent('GraphQL Schema for testing');
93+
container.querySelector('.graphiql-doc-explorer-schema-overview'),
94+
).toBeInTheDocument();
9395
});
9496
it('renders correctly with schema error', () => {
9597
useGraphiQL.mockImplementation(cb => cb(withErrorSchemaContext));
@@ -124,20 +126,27 @@ describe('DocExplorer', () => {
124126
cb({ ...defaultSchemaContext, schema: initialSchema }),
125127
);
126128
const { container, rerender } = render(
127-
<DocExplorerStore>
128-
<SetInitialStack />
129-
</DocExplorerStore>,
129+
<Tooltip.Provider>
130+
<DocExplorerStore>
131+
<SetInitialStack />
132+
</DocExplorerStore>
133+
</Tooltip.Provider>,
130134
);
131135

132136
// First proper render of doc explorer
133137
rerender(
134-
<DocExplorerStore>
135-
<DocExplorer />
136-
</DocExplorerStore>,
138+
<Tooltip.Provider>
139+
<DocExplorerStore>
140+
<DocExplorer />
141+
</DocExplorerStore>
142+
</Tooltip.Provider>,
137143
);
138144

139-
const title = container.querySelector('.graphiql-doc-explorer-title')!;
140-
expect(title.textContent).toEqual('field');
145+
// The current page is shown as the last breadcrumb segment
146+
const breadcrumb = container.querySelector(
147+
'.graphiql-doc-explorer-breadcrumb-current',
148+
)!;
149+
expect(breadcrumb.textContent).toEqual('field');
141150

142151
// Second render of doc explorer, this time with a new schema, with _same_ field name
143152
useGraphiQL.mockImplementation(cb =>
@@ -147,13 +156,17 @@ describe('DocExplorer', () => {
147156
}),
148157
);
149158
rerender(
150-
<DocExplorerStore>
151-
<DocExplorer />
152-
</DocExplorerStore>,
159+
<Tooltip.Provider>
160+
<DocExplorerStore>
161+
<DocExplorer />
162+
</DocExplorerStore>
163+
</Tooltip.Provider>,
153164
);
154-
const title2 = container.querySelector('.graphiql-doc-explorer-title')!;
165+
const breadcrumb2 = container.querySelector(
166+
'.graphiql-doc-explorer-breadcrumb-current',
167+
)!;
155168
// Because `Query.field` still exists in the new schema, we can still render it
156-
expect(title2.textContent).toEqual('field');
169+
expect(breadcrumb2.textContent).toEqual('field');
157170
});
158171
it('trims nav stack when necessary', () => {
159172
const initialSchema = makeSchema();
@@ -179,20 +192,26 @@ describe('DocExplorer', () => {
179192
cb({ ...defaultSchemaContext, schema: initialSchema }),
180193
);
181194
const { container, rerender } = render(
182-
<DocExplorerStore>
183-
<SetInitialStack />
184-
</DocExplorerStore>,
195+
<Tooltip.Provider>
196+
<DocExplorerStore>
197+
<SetInitialStack />
198+
</DocExplorerStore>
199+
</Tooltip.Provider>,
185200
);
186201

187202
// First proper render of doc explorer
188203
rerender(
189-
<DocExplorerStore>
190-
<DocExplorer />
191-
</DocExplorerStore>,
204+
<Tooltip.Provider>
205+
<DocExplorerStore>
206+
<DocExplorer />
207+
</DocExplorerStore>
208+
</Tooltip.Provider>,
192209
);
193210

194-
const title = container.querySelector('.graphiql-doc-explorer-title')!;
195-
expect(title.textContent).toEqual('field');
211+
const breadcrumb = container.querySelector(
212+
'.graphiql-doc-explorer-breadcrumb-current',
213+
)!;
214+
expect(breadcrumb.textContent).toEqual('field');
196215

197216
// Second render of doc explorer, this time with a new schema, with a different field name
198217
useGraphiQL.mockImplementation(cb =>
@@ -202,12 +221,16 @@ describe('DocExplorer', () => {
202221
}),
203222
);
204223
rerender(
205-
<DocExplorerStore>
206-
<DocExplorer />
207-
</DocExplorerStore>,
224+
<Tooltip.Provider>
225+
<DocExplorerStore>
226+
<DocExplorer />
227+
</DocExplorerStore>
228+
</Tooltip.Provider>,
208229
);
209-
const title2 = container.querySelector('.graphiql-doc-explorer-title')!;
230+
const breadcrumb2 = container.querySelector(
231+
'.graphiql-doc-explorer-breadcrumb-current',
232+
)!;
210233
// Because `Query.field` doesn't exist anymore, the top-most item we can render is `Query`
211-
expect(title2.textContent).toEqual('Query');
234+
expect(breadcrumb2.textContent).toEqual('Query');
212235
});
213236
});

packages/graphiql-plugin-doc-explorer/src/components/__tests__/field-documentation.spec.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ describe('FieldDocumentation', () => {
7878
/>,
7979
);
8080
expect(
81-
container.querySelector('.graphiql-markdown-description'),
81+
container.querySelector('.graphiql-doc-explorer-field-card-description'),
8282
).not.toBeInTheDocument();
8383
expect(
8484
container.querySelector('.graphiql-doc-explorer-type-name'),
@@ -95,7 +95,7 @@ describe('FieldDocumentation', () => {
9595
/>,
9696
);
9797
expect(
98-
container.querySelector('.graphiql-markdown-description'),
98+
container.querySelector('.graphiql-doc-explorer-field-card-description'),
9999
).not.toBeInTheDocument();
100100
expect(
101101
container.querySelector('.graphiql-doc-explorer-type-name'),
@@ -113,7 +113,7 @@ describe('FieldDocumentation', () => {
113113
container.querySelector('.graphiql-doc-explorer-type-name'),
114114
).toHaveTextContent('String');
115115
expect(
116-
container.querySelector('.graphiql-markdown-description'),
116+
container.querySelector('.graphiql-doc-explorer-field-card-description'),
117117
).toHaveTextContent('Example String field with arguments');
118118
});
119119

@@ -127,14 +127,14 @@ describe('FieldDocumentation', () => {
127127
container.querySelector('.graphiql-doc-explorer-type-name'),
128128
).toHaveTextContent('String');
129129
expect(
130-
container.querySelector('.graphiql-markdown-description'),
130+
container.querySelector('.graphiql-doc-explorer-field-card-description'),
131131
).toHaveTextContent('Example String field with arguments');
132132
expect(
133133
container.querySelectorAll('.graphiql-doc-explorer-argument'),
134134
).toHaveLength(1);
135135
expect(
136136
container.querySelector('.graphiql-doc-explorer-argument'),
137-
).toHaveTextContent('stringArg: String');
137+
).toHaveTextContent('stringArg:String');
138138
// by default, the deprecation docs should be hidden
139139
expect(
140140
container.querySelectorAll('.graphiql-markdown-deprecation'),
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
.graphiql-doc-explorer-arguments-list {
2+
display: flex;
3+
flex-direction: column;
4+
padding: var(--px-4) 0;
5+
}
6+
7+
/* Section header — "ARGUMENTS · N" / "DIRECTIVES · N" */
8+
.graphiql-doc-explorer-arguments-list-header {
9+
display: flex;
10+
align-items: center;
11+
gap: var(--px-6);
12+
padding: var(--px-6) var(--px-16) var(--px-4);
13+
background: transparent;
14+
border: none;
15+
cursor: pointer;
16+
color: oklch(var(--fg-subtle));
17+
font-size: 10px;
18+
font-weight: 600;
19+
text-transform: uppercase;
20+
letter-spacing: 0.08em;
21+
width: 100%;
22+
text-align: left;
23+
24+
& svg {
25+
width: 9px;
26+
height: 9px;
27+
flex-shrink: 0;
28+
}
29+
30+
&:hover {
31+
color: oklch(var(--fg-muted));
32+
}
33+
34+
&:focus-visible {
35+
outline: 2px solid oklch(var(--accent-blue));
36+
outline-offset: -2px;
37+
}
38+
}
39+
40+
.graphiql-doc-explorer-arguments-list-count {
41+
color: oklch(var(--fg-dim));
42+
font-weight: 500;
43+
}
44+
45+
.graphiql-doc-explorer-arguments-list-body {
46+
display: flex;
47+
flex-direction: column;
48+
}
49+
50+
/* Individual argument row */
51+
.graphiql-doc-explorer-argument {
52+
display: flex;
53+
flex-direction: column;
54+
gap: var(--px-4);
55+
padding: 5px var(--px-16) 5px var(--px-24);
56+
}
57+
58+
.graphiql-doc-explorer-argument--deprecated {
59+
opacity: 0.6;
60+
}
61+
62+
/* Signature line: name : type = default */
63+
.graphiql-doc-explorer-argument-sig {
64+
display: flex;
65+
align-items: baseline;
66+
flex-wrap: wrap;
67+
gap: var(--px-4);
68+
font-family: var(--font-family-mono);
69+
font-size: 12px;
70+
}
71+
72+
.graphiql-doc-explorer-argument-name {
73+
color: oklch(var(--accent-green-light));
74+
}
75+
76+
.graphiql-doc-explorer-argument-colon {
77+
color: oklch(var(--fg-disabled));
78+
}
79+
80+
.graphiql-doc-explorer-argument-type {
81+
color: oklch(var(--accent-orange));
82+
83+
& .graphiql-doc-explorer-type-name {
84+
color: oklch(var(--accent-orange));
85+
86+
&:hover {
87+
text-decoration: underline;
88+
}
89+
}
90+
}
91+
92+
.graphiql-doc-explorer-argument-default {
93+
color: oklch(var(--fg-muted));
94+
}
95+
96+
.graphiql-doc-explorer-argument-desc {
97+
font-size: 11px;
98+
color: oklch(var(--fg-subtle));
99+
line-height: 15px;
100+
}
101+
102+
.graphiql-doc-explorer-arguments-list-show-deprecated {
103+
margin: var(--px-8) var(--px-16);
104+
}
105+
106+
/* Directives — same eyebrow layout as ArgumentsList */
107+
.graphiql-doc-explorer-directives-list {
108+
display: flex;
109+
flex-direction: column;
110+
padding: var(--px-4) 0;
111+
}
112+
113+
.graphiql-doc-explorer-directives-list-header {
114+
padding: var(--px-6) var(--px-16) var(--px-4);
115+
color: oklch(var(--fg-subtle));
116+
font-size: 10px;
117+
font-weight: 600;
118+
text-transform: uppercase;
119+
letter-spacing: 0.08em;
120+
}
121+
122+
.graphiql-doc-explorer-directives-list-count {
123+
color: oklch(var(--fg-dim));
124+
font-weight: 500;
125+
}
126+
127+
.graphiql-doc-explorer-directives-list-body {
128+
display: flex;
129+
flex-direction: column;
130+
}
131+
132+
.graphiql-doc-explorer-directive-row {
133+
padding: 5px var(--px-16) 5px var(--px-24);
134+
font-family: var(--font-family-mono);
135+
font-size: 12px;
136+
}
137+
138+
.graphiql-doc-explorer-directive-row .graphiql-doc-explorer-directive {
139+
color: oklch(var(--accent-orange));
140+
}

0 commit comments

Comments
 (0)