(
(props, ref) => (
-
-
-
+
+
+
+
+
),
);
TabClose.displayName = 'Tab.Close';
diff --git a/packages/graphiql-react/src/ui/tooltip.css b/packages/graphiql-react/src/ui/tooltip.css
new file mode 100644
index 00000000000..ceee7561a89
--- /dev/null
+++ b/packages/graphiql-react/src/ui/tooltip.css
@@ -0,0 +1,10 @@
+@import url('@reach/tooltip/styles.css');
+
+[data-reach-tooltip] {
+ background: hsl(var(--color-base));
+ border: none;
+ border-radius: var(--border-radius-4);
+ box-shadow: var(--box-shadow);
+ font-size: inherit;
+ padding: var(--px-4) var(--px-6);
+}
diff --git a/packages/graphiql-react/src/ui/tooltip.tsx b/packages/graphiql-react/src/ui/tooltip.tsx
new file mode 100644
index 00000000000..bc156c6213d
--- /dev/null
+++ b/packages/graphiql-react/src/ui/tooltip.tsx
@@ -0,0 +1,3 @@
+import './tooltip.css';
+
+export { Tooltip } from '@reach/tooltip';
diff --git a/packages/graphiql/__mocks__/@graphiql/react.tsx b/packages/graphiql/__mocks__/@graphiql/react.tsx
index cbcc24f67f6..92c6a079022 100644
--- a/packages/graphiql/__mocks__/@graphiql/react.tsx
+++ b/packages/graphiql/__mocks__/@graphiql/react.tsx
@@ -31,7 +31,6 @@ export {
DirectiveIcon,
DocExplorer,
DocsIcon,
- Dropdown,
EditorContext,
EditorContextProvider,
EnumValueIcon,
@@ -51,8 +50,10 @@ export {
ImagePreview,
ImplementsIcon,
KeyboardShortcutIcon,
+ Listbox,
MagnifyingGlassIcon,
MarkdownContent,
+ Menu,
MergeIcon,
onHasCompletion,
PenIcon,
@@ -75,6 +76,9 @@ export {
Tab,
Tabs,
ToolbarButton,
+ ToolbarListbox,
+ ToolbarMenu,
+ Tooltip,
TypeDocumentation,
TypeIcon,
TypeLink,
diff --git a/packages/graphiql/cypress/support/commands.ts b/packages/graphiql/cypress/support/commands.ts
index e66f8299ffe..ce81f28f58b 100644
--- a/packages/graphiql/cypress/support/commands.ts
+++ b/packages/graphiql/cypress/support/commands.ts
@@ -52,7 +52,7 @@ Cypress.Commands.add('clickExecuteQuery', () => {
});
Cypress.Commands.add('clickPrettify', () => {
- return cy.get('[title="Prettify Query (Shift-Ctrl-P)"]').click();
+ return cy.get('[aria-label="Prettify query (Shift-Ctrl-P)"]').click();
});
Cypress.Commands.add('visitWithOp', ({ query, variables, variablesString }) => {
diff --git a/packages/graphiql/src/components/GraphiQL.tsx b/packages/graphiql/src/components/GraphiQL.tsx
index 70e73e2dd81..035c3f70b67 100644
--- a/packages/graphiql/src/components/GraphiQL.tsx
+++ b/packages/graphiql/src/components/GraphiQL.tsx
@@ -62,6 +62,7 @@ import {
TabsState,
Theme,
ToolbarButton,
+ Tooltip,
UnStyledButton,
useAutoCompleteLeafs,
useCopyQuery,
@@ -650,25 +651,22 @@ class GraphiQLWithContext extends React.Component<
onClick={() => {
this.props.prettify();
}}
- title="Prettify Query (Shift-Ctrl-P)"
- aria-label="Prettify">
-
+ label="Prettify query (Shift-Ctrl-P)">
+
{
this.props.merge();
}}
- title="Merge Query (Shift-Ctrl-M)"
- aria-label="Merge">
-
+ label="Merge fragments into query (Shift-Ctrl-M)">
+
{
this.props.copy();
}}
- title="Copy Query (Shift-Ctrl-C)"
- aria-label="Copy">
-
+ label="Copy query (Shift-Ctrl-C)">
+
{this.props.toolbar?.additionalContent
? this.props.toolbar.additionalContent
@@ -700,76 +698,104 @@ class GraphiQLWithContext extends React.Component<
{this.props.explorerContext ? (
- {
- if (this.props.explorerContext?.isVisible) {
- this.props.explorerContext?.hide();
- this.props.pluginResize.setHiddenElement('first');
- } else {
- this.props.explorerContext?.show();
- this.props.pluginResize.setHiddenElement(null);
- if (this.props.historyContext?.isVisible) {
- this.props.historyContext.hide();
- }
- }
- }}
- title={
+
-
-
- ) : null}
- {this.props.historyContext ? (
- {
- if (!this.props.historyContext) {
- return;
+ {
if (this.props.explorerContext?.isVisible) {
- this.props.explorerContext.hide();
+ this.props.explorerContext?.hide();
+ this.props.pluginResize.setHiddenElement('first');
+ } else {
+ this.props.explorerContext?.show();
+ this.props.pluginResize.setHiddenElement(null);
+ if (this.props.historyContext?.isVisible) {
+ this.props.historyContext.hide();
+ }
}
- }
- }}
- title={
+ }}
+ aria-label={
+ this.props.explorerContext.isVisible
+ ? 'Hide Documentation Explorer'
+ : 'Show Documentation Explorer'
+ }>
+
+
+
+ ) : null}
+ {this.props.historyContext ? (
+
-
-
+ {
+ if (!this.props.historyContext) {
+ return;
+ }
+ this.props.historyContext.toggle();
+ if (this.props.historyContext.isVisible) {
+ this.props.pluginResize.setHiddenElement('first');
+ } else {
+ this.props.pluginResize.setHiddenElement(null);
+ if (this.props.explorerContext?.isVisible) {
+ this.props.explorerContext.hide();
+ }
+ }
+ }}
+ aria-label={
+ this.props.historyContext.isVisible
+ ? 'Hide History'
+ : 'Show History'
+ }>
+
+
+
) : null}
- this.props.schemaContext.introspect()}>
-
-
- {
- this.setState({ showShortKeys: true });
- }}>
-
-
- {
- this.setState({ showSettings: true });
- }}>
-
-
+
+ this.props.schemaContext.introspect()}
+ aria-label="Re-fetch GraphQL schema">
+
+
+
+
+ {
+ this.setState({ showShortKeys: true });
+ }}
+ aria-label="Open short keys dialog">
+
+
+
+
+ {
+ this.setState({ showSettings: true });
+ }}
+ aria-label="Open settings dialog">
+
+
+
@@ -802,8 +828,7 @@ class GraphiQLWithContext extends React.Component<
key={tab.id}
isActive={
index === this.props.editorContext.activeTabIndex
- }
- title={tab.title}>
+ }>
))}
-
{
- this.props.editorContext.addTab();
- }}>
-
-
+
+ {
+ this.props.editorContext.addTab();
+ }}
+ aria-label="Add tab">
+
+
+
>
) : null}
{this.props.editorContext.tabs.length === 1 ? (
-
{
- this.props.editorContext.addTab();
- }}>
-
-
+
+ {
+ this.props.editorContext.addTab();
+ }}
+ aria-label="Add tab">
+
+
+
) : null}
{logo}
@@ -933,22 +964,42 @@ class GraphiQLWithContext extends React.Component<
) : null}
- {
- this.props.editorToolsResize.setHiddenElement(
+
+ {
+ this.props.editorToolsResize.setHiddenElement(
+ this.props.editorToolsResize.hiddenElement ===
+ 'second'
+ ? null
+ : 'second',
+ );
+ }}
+ aria-label={
this.props.editorToolsResize.hiddenElement ===
- 'second'
- ? null
- : 'second',
- );
- }}>
- {this.props.editorToolsResize.hiddenElement ===
- 'second' ? (
-
- ) : (
-
- )}
-
+ 'second'
+ ? 'Show editor tools'
+ : 'Hide editor tools'
+ }>
+ {this.props.editorToolsResize.hiddenElement ===
+ 'second' ? (
+
+ ) : (
+
+ )}
+
+
diff --git a/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx b/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx
index 0175b46db78..e383a118585 100644
--- a/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx
+++ b/packages/graphiql/src/components/__tests__/GraphiQL.spec.tsx
@@ -173,7 +173,7 @@ describe('GraphiQL', () => {
});
it('will save history item even when history panel is closed', () => {
- const { getByTitle, container } = render(
+ const { getByLabelText, container } = render(
{
fetcher={noOpFetcher}
/>,
);
- fireEvent.click(getByTitle('Execute Query (Ctrl-Enter)'));
- fireEvent.click(getByTitle('Show History'));
+ fireEvent.click(getByLabelText('Execute query (Ctrl-Enter)'));
+ fireEvent.click(getByLabelText('Show History'));
expect(
container.querySelectorAll('.graphiql-history-items li'),
).toHaveLength(1);
});
it('adds a history item when the execute query function button is clicked', () => {
- const { getByTitle, container } = render(
+ const { getByLabelText, container } = render(
{
fetcher={noOpFetcher}
/>,
);
- fireEvent.click(getByTitle('Show History'));
- fireEvent.click(getByTitle('Execute Query (Ctrl-Enter)'));
+ fireEvent.click(getByLabelText('Show History'));
+ fireEvent.click(getByLabelText('Execute query (Ctrl-Enter)'));
expect(
container.querySelectorAll('.graphiql-history-items li'),
).toHaveLength(1);
});
it('will not save invalid queries', () => {
- const { getByTitle, container } = render(
+ const { getByLabelText, container } = render(
,
);
- fireEvent.click(getByTitle('Show History'));
- fireEvent.click(getByTitle('Execute Query (Ctrl-Enter)'));
+ fireEvent.click(getByLabelText('Show History'));
+ fireEvent.click(getByLabelText('Execute query (Ctrl-Enter)'));
expect(
container.querySelectorAll('.graphiql-history-items li'),
).toHaveLength(0);
});
it('will save if there was not a previously saved query', () => {
- const { getByTitle, container } = render(
+ const { getByLabelText, container } = render(
{
headers={mockHeaders1}
/>,
);
- fireEvent.click(getByTitle('Show History'));
- fireEvent.click(getByTitle('Execute Query (Ctrl-Enter)'));
+ fireEvent.click(getByLabelText('Show History'));
+ fireEvent.click(getByLabelText('Execute query (Ctrl-Enter)'));
expect(
container.querySelectorAll('.graphiql-history-items li'),
).toHaveLength(1);
});
- it('will not save a query if the query is the same as previous query', () => {
- const { getByTitle, container } = render(
+ it('will not save a query if the query is the same as previous query', async () => {
+ const { getByLabelText, findByLabelText, container } = render(
{
headers={mockHeaders1}
/>,
);
- fireEvent.click(getByTitle('Show History'));
- fireEvent.click(getByTitle('Execute Query (Ctrl-Enter)'));
+ fireEvent.click(getByLabelText('Show History'));
+ fireEvent.click(getByLabelText('Execute query (Ctrl-Enter)'));
expect(
container.querySelectorAll('.graphiql-history-items li'),
).toHaveLength(1);
- fireEvent.click(getByTitle('Execute Query (Ctrl-Enter)'));
+ fireEvent.click(await findByLabelText('Execute query (Ctrl-Enter)'));
expect(
container.querySelectorAll('.graphiql-history-items li'),
).toHaveLength(1);
});
it('will save if new query is different than previous query', async () => {
- const { getByTitle, container } = render(
+ const { getByLabelText, container } = render(
{
/>,
);
await wait();
- fireEvent.click(getByTitle('Show History'));
- const executeQueryButton = getByTitle('Execute Query (Ctrl-Enter)');
+ fireEvent.click(getByLabelText('Show History'));
+ const executeQueryButton = getByLabelText('Execute query (Ctrl-Enter)');
fireEvent.click(executeQueryButton);
expect(container.querySelectorAll('.graphiql-history-item')).toHaveLength(
1,
@@ -290,7 +290,7 @@ describe('GraphiQL', () => {
});
it('will save query if variables are different', async () => {
- const { getByTitle, container } = render(
+ const { getByLabelText, container } = render(
{
/>,
);
await wait();
- fireEvent.click(getByTitle('Show History'));
- const executeQueryButton = getByTitle('Execute Query (Ctrl-Enter)');
+ fireEvent.click(getByLabelText('Show History'));
+ const executeQueryButton = getByLabelText('Execute query (Ctrl-Enter)');
fireEvent.click(executeQueryButton);
expect(container.querySelectorAll('.graphiql-history-item')).toHaveLength(
1,
@@ -322,7 +322,7 @@ describe('GraphiQL', () => {
});
it('will save query if headers are different', async () => {
- const { getByTitle, getByText, container } = render(
+ const { getByLabelText, getByText, container } = render(
{
);
await wait();
- fireEvent.click(getByTitle('Show History'));
- const executeQueryButton = getByTitle('Execute Query (Ctrl-Enter)');
+ fireEvent.click(getByLabelText('Show History'));
+ const executeQueryButton = getByLabelText('Execute query (Ctrl-Enter)');
fireEvent.click(executeQueryButton);
expect(container.querySelectorAll('.graphiql-history-item')).toHaveLength(
1,
@@ -571,7 +571,7 @@ describe('GraphiQL', () => {
const { container } = render();
fireEvent.click(
- container.querySelector('[title="Show Documentation Explorer"]'),
+ container.querySelector('[aria-label="Show Documentation Explorer"]'),
);
const dragBar = container.querySelectorAll(
'.graphiql-horizontal-drag-bar',
diff --git a/packages/graphiql/src/style.css b/packages/graphiql/src/style.css
index 662108de716..1f6c60dfe6c 100644
--- a/packages/graphiql/src/style.css
+++ b/packages/graphiql/src/style.css
@@ -30,7 +30,6 @@
.graphiql-container .graphiql-sidebar button > svg {
height: calc(var(--sidebar-width) - (2 * var(--px-12)));
padding: var(--px-12);
- pointer-events: none;
width: calc(var(--sidebar-width) - (2 * var(--px-12)));
}
@@ -291,3 +290,8 @@ reach-portal .graphiql-key {
border-radius: var(--border-radius-4);
padding: var(--px-4);
}
+
+/* Avoid showing native tooltips for icons with titles */
+.graphiql-container svg {
+ pointer-events: none;
+}
diff --git a/yarn.lock b/yarn.lock
index 1722c407479..ef66b06e283 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4549,6 +4549,20 @@
tiny-warning "^1.0.3"
tslib "^2.3.0"
+"@reach/tooltip@^0.17.0":
+ version "0.17.0"
+ resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.17.0.tgz#044b43de248a05b18641b4220310983cb54675a2"
+ integrity sha512-HP8Blordzqb/Cxg+jnhGmWQfKgypamcYLBPlcx6jconyV5iLJ5m93qipr1giK7MqKT2wlsKWy44ZcOrJ+Wrf8w==
+ dependencies:
+ "@reach/auto-id" "0.17.0"
+ "@reach/portal" "0.17.0"
+ "@reach/rect" "0.17.0"
+ "@reach/utils" "0.17.0"
+ "@reach/visually-hidden" "0.17.0"
+ prop-types "^15.7.2"
+ tiny-warning "^1.0.3"
+ tslib "^2.3.0"
+
"@reach/utils@0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.17.0.tgz#3d1d2ec56d857f04fe092710d8faee2b2b121303"
@@ -4557,7 +4571,7 @@
tiny-warning "^1.0.3"
tslib "^2.3.0"
-"@reach/visually-hidden@^0.17.0":
+"@reach/visually-hidden@0.17.0", "@reach/visually-hidden@^0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.17.0.tgz#033adba10b5ec419649da8d6bd8e46db06d4c3a1"
integrity sha512-T6xF3Nv8vVnjVkGU6cm0+kWtvliLqPAo8PcZ+WxkKacZsaHTjaZb4v1PaCcyQHmuTNT/vtTVNOJLG0SjQOIb7g==