Skip to content

Commit

Permalink
make dropdown menu accessible (#2627)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasheyenbrock committed Aug 9, 2022
1 parent f0b5036 commit bb1077f
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 146 deletions.
2 changes: 1 addition & 1 deletion .changeset/five-pillows-fail.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
---

Add new components:
- UI components (`Button`, `ButtonGroup`, `Dialog`, `Dropdown`, `Spinner`, `Tab`, `Tabs`, `UnStyledButton` and lots of icon components)
- UI components (`Button`, `ButtonGroup`, `Dialog`, `Menu`, `Spinner`, `Tab`, `Tabs`, `UnStyledButton` and lots of icon components)
- Editor components (`QueryEditor`, `VariableEditor`, `HeaderEditor` and `ResponseEditor`)
- Toolbar components (`ExecuteButton` and `ToolbarButton`)
- Docs components (`Argument`, `DefaultValue`, `DeprecationReason`, `Directive`, `DocExplorer`, `ExplorerSection`, `FieldDocumentation`, `FieldLink`, `SchemaDocumentation`, `Search`, `TypeDocumentation` and `TypeLink`)
Expand Down
1 change: 1 addition & 0 deletions packages/graphiql-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@graphiql/toolkit": "^1.0.0-next.1",
"@reach/combobox": "^0.17.0",
"@reach/dialog": "^0.17.0",
"@reach/menu-button": "^0.17.0",
"@reach/visually-hidden": "^0.17.0",
"codemirror": "^5.65.3",
"codemirror-graphql": "^2.0.0-next.1",
Expand Down
138 changes: 45 additions & 93 deletions packages/graphiql-react/src/toolbar/execute.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { OperationDefinitionNode } from 'graphql';
import { useState } from 'react';

import { useEditorContext } from '../editor';
import { useExecutionContext } from '../execution';
import { PlayIcon, StopIcon } from '../icons';
import { Dropdown } from '../ui';
import { Menu } from '../ui';

import './execute.css';

Expand All @@ -18,102 +15,57 @@ export function ExecuteButton() {
nonNull: true,
caller: ExecuteButton,
});
const [optionsOpen, setOptionsOpen] = useState(false);
const [highlight, setHighlight] = useState<OperationDefinitionNode | null>(
null,
);

const isRunning = isFetching || Boolean(subscription);
const operations = queryEditor?.operations || [];
const hasOptions = operations.length > 1 && typeof operationName !== 'string';

return (
<div className="graphiql-execute-button-wrapper">
<button
type="button"
className="graphiql-execute-button"
onMouseDown={
// Allow mouse down if there is no running query, there are options
// for which operation to run, and the dropdown is currently closed.
!isRunning && hasOptions && !optionsOpen
? downEvent => {
let initialPress = true;
const downTarget = downEvent.currentTarget;
setHighlight(null);
setOptionsOpen(true);
const buttonProps = {
type: 'button' as const,
className: 'graphiql-execute-button',
title: 'Execute Query (Ctrl-Enter)',
children: isRunning ? <StopIcon /> : <PlayIcon />,
};

type MouseUpEventHandler = (upEvent: MouseEvent) => void;
let onMouseUp: MouseUpEventHandler | null = upEvent => {
if (initialPress && upEvent.target === downTarget) {
initialPress = false;
} else {
document.removeEventListener('mouseup', onMouseUp!);
onMouseUp = null;
const isOptionsMenuClicked =
upEvent.currentTarget &&
downTarget.parentNode?.compareDocumentPosition(
upEvent.currentTarget as Node,
) &&
Node.DOCUMENT_POSITION_CONTAINED_BY;
if (!isOptionsMenuClicked) {
// menu calls setState if it was clicked
setOptionsOpen(false);
}
}
};

document.addEventListener('mouseup', onMouseUp);
}
: undefined
}
onClick={
// Allow click event if there is a running query or if there are not
// options for which operation to run.
isRunning || !hasOptions
? () => {
if (isRunning) {
stop();
} else {
run();
return hasOptions ? (
<Menu>
<Menu.Button {...buttonProps} />
<Menu.List>
{operations.map((operation, i) => {
const opName = operation.name
? operation.name.value
: `<Unnamed ${operation.operation}>`;
return (
<Menu.Item
key={`${opName}-${i}`}
onSelect={() => {
const selectedOperationName = operation.name?.value;
if (
queryEditor &&
selectedOperationName &&
selectedOperationName !== queryEditor.operationName
) {
setOperationName(selectedOperationName);
}
}
: undefined
run();
}}
>
{opName}
</Menu.Item>
);
})}
</Menu.List>
</Menu>
) : (
<button
{...buttonProps}
onClick={() => {
if (isRunning) {
stop();
} else {
run();
}
title="Execute Query (Ctrl-Enter)"
>
{isRunning ? <StopIcon /> : <PlayIcon />}
</button>
{hasOptions && optionsOpen ? (
<Dropdown>
{operations.map((operation, i) => {
const opName = operation.name
? operation.name.value
: `<Unnamed ${operation.operation}>`;
return (
<Dropdown.Item
key={`${opName}-${i}`}
className={operation === highlight ? 'selected' : undefined}
onMouseOver={() => setHighlight(operation)}
onMouseOut={() => setHighlight(null)}
onMouseUp={() => {
setOptionsOpen(false);
const selectedOperationName = operation.name?.value;
if (
queryEditor &&
selectedOperationName &&
selectedOperationName !== queryEditor.operationName
) {
setOperationName(selectedOperationName);
}
run();
}}
>
{opName}
</Dropdown.Item>
);
})}
</Dropdown>
) : null}
</div>
}}
/>
);
}
30 changes: 0 additions & 30 deletions packages/graphiql-react/src/ui/dropdown.css

This file was deleted.

21 changes: 0 additions & 21 deletions packages/graphiql-react/src/ui/dropdown.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion packages/graphiql-react/src/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from './button';
export * from './button-group';
export * from './dialog';
export * from './dropdown';
export * from './menu';
export * from './markdown';
export * from './spinner';
export * from './tabs';
31 changes: 31 additions & 0 deletions packages/graphiql-react/src/ui/menu.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@import url('@reach/menu-button/styles.css');

[data-reach-menu-list] {
background-color: var(--color-neutral-0);
box-shadow: var(--box-shadow);
border: none;
border-radius: var(--border-radius-8);
font-size: inherit;
max-width: 250px;
padding: var(--px-4);
}

[data-reach-menu-item] {
border-radius: var(--border-radius-4);
font-size: inherit;
margin: var(--px-4);
overflow: hidden;
padding: var(--px-6) var(--px-8);
text-overflow: ellipsis;
white-space: nowrap;

&[data-selected],
&:hover {
background-color: var(--color-neutral-7);
color: inherit;
}

&:not(:first-child) {
margin-top: 0;
}
}
15 changes: 15 additions & 0 deletions packages/graphiql-react/src/ui/menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
Menu as MenuRoot,
MenuButton,
MenuItem,
MenuList,
} from '@reach/menu-button';
import { createComponentGroup } from '../utility/component-group';

import './menu.css';

export const Menu = createComponentGroup(MenuRoot, {
Button: MenuButton,
Item: MenuItem,
List: MenuList,
});
13 changes: 13 additions & 0 deletions packages/graphiql-react/src/utility/component-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { JSXElementConstructor } from 'react';

export const createComponentGroup = <
Root extends JSXElementConstructor<any>,
Children extends { [key: string]: JSXElementConstructor<any> },
>(
root: Root,
children: Children,
): Root & Children =>
Object.entries(children).reduce<any>((r, [key, value]) => {
r[key] = value;
return r;
}, root);
23 changes: 23 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4469,6 +4469,29 @@
react-remove-scroll "^2.4.3"
tslib "^2.3.0"

"@reach/[email protected]":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@reach/dropdown/-/dropdown-0.17.0.tgz#8140bb2e6a045f91e07c6d5a6ff960958df2ef33"
integrity sha512-qBTIGInhxtPHtdj4Pl2XZgZMz3e37liydh0xR3qc48syu7g71sL4nqyKjOzThykyfhA3Pb3/wFgsFJKGTSdaig==
dependencies:
"@reach/auto-id" "0.17.0"
"@reach/descendants" "0.17.0"
"@reach/popover" "0.17.0"
"@reach/utils" "0.17.0"
tslib "^2.3.0"

"@reach/menu-button@^0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.17.0.tgz#9f40979129b61f8bdc19590c527f7ed4883d2dce"
integrity sha512-YyuYVyMZKamPtivoEI6D0UEILYH3qZtg4kJzEAuzPmoR/aHN66NZO75Fx0gtjG1S6fZfbiARaCOZJC0VEiDOtQ==
dependencies:
"@reach/dropdown" "0.17.0"
"@reach/popover" "0.17.0"
"@reach/utils" "0.17.0"
prop-types "^15.7.2"
tiny-warning "^1.0.3"
tslib "^2.3.0"

"@reach/[email protected]":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
Expand Down

0 comments on commit bb1077f

Please sign in to comment.