diff --git a/.changeset/five-pillows-fail.md b/.changeset/five-pillows-fail.md index bae5935e230..e98c1fdc646 100644 --- a/.changeset/five-pillows-fail.md +++ b/.changeset/five-pillows-fail.md @@ -3,7 +3,7 @@ --- Add new components: -- UI components (`Button`, `ButtonGroup`, `Dialog`, `Menu`, `Spinner`, `Tab`, `Tabs`, `UnStyledButton` and lots of icon components) +- UI components (`Button`, `ButtonGroup`, `Dialog`, `Menu`, `Spinner`, `Tab`, `Tabs`, `Tooltip`, `UnStyledButton` and lots of icon components) - Editor components (`QueryEditor`, `VariableEditor`, `HeaderEditor` and `ResponseEditor`) - Toolbar components (`ExecuteButton`, `ToolbarButton`, `ToolbarMenu` and `ToolbarSelect`) - Docs components (`Argument`, `DefaultValue`, `DeprecationReason`, `Directive`, `DocExplorer`, `ExplorerSection`, `FieldDocumentation`, `FieldLink`, `SchemaDocumentation`, `Search`, `TypeDocumentation` and `TypeLink`) diff --git a/packages/graphiql-react/package.json b/packages/graphiql-react/package.json index a1b207db345..140f0ebe6f4 100644 --- a/packages/graphiql-react/package.json +++ b/packages/graphiql-react/package.json @@ -42,6 +42,7 @@ "@reach/dialog": "^0.17.0", "@reach/listbox": "^0.17.0", "@reach/menu-button": "^0.17.0", + "@reach/tooltip": "^0.17.0", "@reach/visually-hidden": "^0.17.0", "codemirror": "^5.65.3", "codemirror-graphql": "^2.0.0-next.1", diff --git a/packages/graphiql-react/src/history/__tests__/components.spec.tsx b/packages/graphiql-react/src/history/__tests__/components.spec.tsx index 9366c4b67bc..180287b0175 100644 --- a/packages/graphiql-react/src/history/__tests__/components.spec.tsx +++ b/packages/graphiql-react/src/history/__tests__/components.spec.tsx @@ -118,10 +118,10 @@ describe('QueryHistoryItem', () => { }); it('renders label input if the edit label button is clicked', () => { - const { container, getByTitle } = render( + const { container, getByLabelText } = render( , ); - fireEvent.click(getByTitle('Edit label')); + fireEvent.click(getByLabelText('Edit label')); expect(container.querySelectorAll('li.editable').length).toBe(1); expect(container.querySelectorAll('input').length).toBe(1); expect( diff --git a/packages/graphiql-react/src/history/components.tsx b/packages/graphiql-react/src/history/components.tsx index 921e0a68523..ab6ad9efaff 100644 --- a/packages/graphiql-react/src/history/components.tsx +++ b/packages/graphiql-react/src/history/components.tsx @@ -3,7 +3,7 @@ import { Fragment, useEffect, useRef, useState } from 'react'; import { useEditorContext } from '../editor'; import { CloseIcon, PenIcon, StarFilledIcon, StarIcon } from '../icons'; -import { UnStyledButton } from '../ui'; +import { Tooltip, UnStyledButton } from '../ui'; import { useHistoryContext } from './context'; import './style.css'; @@ -112,24 +112,35 @@ export function HistoryItem(props: QueryHistoryItemProps) { }}> {displayName} - { - e.stopPropagation(); - setIsEditable(true); - }}> - - - { - e.stopPropagation(); - toggleFavorite(props.item); - }} - title={props.item.favorite ? 'Remove favorite' : 'Add favorite'}> - {props.item.favorite ? : } - + + { + e.stopPropagation(); + setIsEditable(true); + }} + aria-label="Edit label"> + + + + { + e.stopPropagation(); + toggleFavorite(props.item); + }} + aria-label={ + props.item.favorite ? 'Remove favorite' : 'Add favorite' + }> + {props.item.favorite ? ( + + )} diff --git a/packages/graphiql-react/src/history/style.css b/packages/graphiql-react/src/history/style.css index ac0ebe2c1c6..c28a3d46df8 100644 --- a/packages/graphiql-react/src/history/style.css +++ b/packages/graphiql-react/src/history/style.css @@ -81,7 +81,6 @@ button.graphiql-history-item-action { & > svg { height: 14px; - pointer-events: none; width: 14px; } } diff --git a/packages/graphiql-react/src/toolbar/button.css b/packages/graphiql-react/src/toolbar/button.css index 83baae4c3c7..bfab9965976 100644 --- a/packages/graphiql-react/src/toolbar/button.css +++ b/packages/graphiql-react/src/toolbar/button.css @@ -6,8 +6,4 @@ button.graphiql-toolbar-button { &.error { background: var(--color-red-background); } - - & > svg { - pointer-events: none; - } } diff --git a/packages/graphiql-react/src/toolbar/button.tsx b/packages/graphiql-react/src/toolbar/button.tsx index c3a1969f273..e4f4fc5cb99 100644 --- a/packages/graphiql-react/src/toolbar/button.tsx +++ b/packages/graphiql-react/src/toolbar/button.tsx @@ -1,39 +1,44 @@ import { forwardRef, useState } from 'react'; - -import { UnStyledButton } from '../ui'; +import { Tooltip, UnStyledButton } from '../ui'; import { compose } from '../utility/compose'; import './button.css'; +type ToolbarButtonProps = { + label: string; +}; + export const ToolbarButton = forwardRef< HTMLButtonElement, - JSX.IntrinsicElements['button'] ->((props, ref) => { + ToolbarButtonProps & JSX.IntrinsicElements['button'] +>(({ label, ...props }, ref) => { const [error, setError] = useState(null); return ( - { - try { - props.onClick?.(event); - setError(null); - } catch (err) { - setError( - err instanceof Error - ? err - : new Error(`Toolbar button click failed: ${err}`), - ); - } - }} - title={error ? error.message : props.title} - aria-invalid={error ? 'true' : props['aria-invalid']} - /> + + { + try { + props.onClick?.(event); + setError(null); + } catch (err) { + setError( + err instanceof Error + ? err + : new Error(`Toolbar button click failed: ${err}`), + ); + } + }} + aria-label={error ? error.message : label} + aria-invalid={error ? 'true' : props['aria-invalid']} + /> + ); }); ToolbarButton.displayName = 'ToolbarButton'; diff --git a/packages/graphiql-react/src/toolbar/execute.css b/packages/graphiql-react/src/toolbar/execute.css index e1b415a92b3..9e7d4f95aaa 100644 --- a/packages/graphiql-react/src/toolbar/execute.css +++ b/packages/graphiql-react/src/toolbar/execute.css @@ -24,7 +24,6 @@ button.graphiql-execute-button { display: block; height: var(--px-16); margin: auto; - pointer-events: none; width: var(--px-16); } } diff --git a/packages/graphiql-react/src/toolbar/execute.tsx b/packages/graphiql-react/src/toolbar/execute.tsx index 306bdbd0760..9bb219230c2 100644 --- a/packages/graphiql-react/src/toolbar/execute.tsx +++ b/packages/graphiql-react/src/toolbar/execute.tsx @@ -1,7 +1,7 @@ import { useEditorContext } from '../editor'; import { useExecutionContext } from '../execution'; import { PlayIcon, StopIcon } from '../icons'; -import { Menu } from '../ui'; +import { Menu, Tooltip } from '../ui'; import './execute.css'; @@ -19,16 +19,20 @@ export function ExecuteButton() { const operations = queryEditor?.operations || []; const hasOptions = operations.length > 1; + const label = 'Execute Query (Ctrl-Enter)'; const buttonProps = { type: 'button' as const, className: 'graphiql-execute-button', - title: 'Execute Query (Ctrl-Enter)', children: isRunning ? : , + 'aria-label': label, }; return hasOptions ? ( - + + + + {operations.map((operation, i) => { const opName = operation.name diff --git a/packages/graphiql-react/src/toolbar/listbox.tsx b/packages/graphiql-react/src/toolbar/listbox.tsx index 863da63613f..45eecdcbdeb 100644 --- a/packages/graphiql-react/src/toolbar/listbox.tsx +++ b/packages/graphiql-react/src/toolbar/listbox.tsx @@ -1,5 +1,5 @@ import { ComponentProps, forwardRef, ReactNode } from 'react'; -import { Listbox } from '../ui'; +import { Listbox, Tooltip } from '../ui'; import { createComponentGroup } from '../utility/component-group'; import { compose } from '../utility/compose'; @@ -7,20 +7,27 @@ import './listbox.css'; type ToolbarListboxProps = { button: ReactNode; + label: string; }; const ToolbarListboxRoot = forwardRef< HTMLDivElement, ToolbarListboxProps & ComponentProps ->(({ button, children, ...props }, ref) => ( - - {button} - {children} - -)); +>(({ button, children, label, ...props }, ref) => { + const labelWithValue = `${label}${props.value ? `: ${props.value}` : ''}`; + return ( + + + {button} + + {children} + + ); +}); ToolbarListboxRoot.displayName = 'ToolbarListbox'; export const ToolbarListbox = createComponentGroup(ToolbarListboxRoot, { diff --git a/packages/graphiql-react/src/toolbar/menu.tsx b/packages/graphiql-react/src/toolbar/menu.tsx index f67aab27612..112ee97aa20 100644 --- a/packages/graphiql-react/src/toolbar/menu.tsx +++ b/packages/graphiql-react/src/toolbar/menu.tsx @@ -1,5 +1,5 @@ import { forwardRef, ReactNode } from 'react'; -import { Menu } from '../ui'; +import { Menu, Tooltip } from '../ui'; import { createComponentGroup } from '../utility/component-group'; import { compose } from '../utility/compose'; @@ -7,20 +7,24 @@ import './menu.css'; type ToolbarMenuProps = { button: ReactNode; + label: string; }; const ToolbarMenuRoot = forwardRef< HTMLDivElement, ToolbarMenuProps & JSX.IntrinsicElements['div'] ->(({ button, children, ...props }, ref) => ( +>(({ button, children, label, ...props }, ref) => ( - - {button} - + + + {button} + + {children} )); diff --git a/packages/graphiql-react/src/ui/index.ts b/packages/graphiql-react/src/ui/index.ts index a79f28f1edc..a0559b7103d 100644 --- a/packages/graphiql-react/src/ui/index.ts +++ b/packages/graphiql-react/src/ui/index.ts @@ -5,3 +5,4 @@ export * from './dropdown'; export * from './markdown'; export * from './spinner'; export * from './tabs'; +export * from './tooltip'; diff --git a/packages/graphiql-react/src/ui/tabs.tsx b/packages/graphiql-react/src/ui/tabs.tsx index fae6265a384..4ad6db1de8e 100644 --- a/packages/graphiql-react/src/ui/tabs.tsx +++ b/packages/graphiql-react/src/ui/tabs.tsx @@ -3,6 +3,7 @@ import { CloseIcon } from '../icons'; import { createComponentGroup } from '../utility/component-group'; import { compose } from '../utility/compose'; import { UnStyledButton } from './button'; +import { Tooltip } from './tooltip'; import './tabs.css'; @@ -45,15 +46,16 @@ TabButton.displayName = 'Tab.Button'; const TabClose = forwardRef( (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..71dec461e7a --- /dev/null +++ b/packages/graphiql-react/src/ui/tooltip.css @@ -0,0 +1,10 @@ +@import url('@reach/tooltip/styles.css'); + +[data-reach-tooltip] { + background: var(--color-neutral-7); + border: none; + border-radius: var(--border-radius-4); + box-shadow: var(--box-shadow); + font-size: inherit; + padding: var(--px-6) var(--px-4); +} 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 5c98014e957..bb4c69d307a 100644 --- a/packages/graphiql/cypress/support/commands.ts +++ b/packages/graphiql/cypress/support/commands.ts @@ -56,7 +56,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 95732a1353d..91bb1329980 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, @@ -644,25 +645,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)"> +