Skip to content

Commit

Permalink
[deps][a11y] Use Reach UI for dropdown menus
Browse files Browse the repository at this point in the history
  • Loading branch information
solomonhawk committed May 11, 2020
1 parent a9177df commit 03dd475
Show file tree
Hide file tree
Showing 18 changed files with 9,544 additions and 254 deletions.
1 change: 1 addition & 0 deletions __mocks__/styleMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
1 change: 1 addition & 0 deletions example/example.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ body {
margin: 0;
width: 100%;
height: 100%;
padding: 16px;
}

#app {
Expand Down
10 changes: 10 additions & 0 deletions example/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ module.exports = {
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.css$/,
include: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
{
test: /\.scss$/,
exclude: /node_modules/,
Expand Down
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module.exports = {
testURL: 'https://viget.dev',
modulePathIgnorePatterns: ['example', 'build'],
moduleNameMapper: {
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js'
},
collectCoverageFrom: ['src/**/*.js'],
testMatch: ['**/?(*.)test.js?(x)']
}
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"@babel/core": "7.9.6",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.9.4",
"@testing-library/dom": "^7.5.1",
"@testing-library/user-event": "^10.1.2",
"autoprefixer": "9.7.6",
"babel-eslint": "10.1.0",
"babel-loader": "8.1.0",
Expand All @@ -68,13 +70,15 @@
"react-dom": "16.13.1",
"rollup": "2.8.2",
"rollup-plugin-buble": "^0.19.2",
"sass-loader": "~6.0.7",
"style-loader": "~0.20.3",
"sass-loader": "8.0.2",
"style-loader": "1.2.1",
"webpack": "4.43.0",
"webpack-cli": "3.3.11",
"webpack-dev-server": "3.11.0"
},
"dependencies": {
"@reach/menu-button": "^0.10.1",
"@reach/popover": "^0.10.1",
"classnames": "~2",
"group-by": "0.0.1",
"microcosm": "12.15",
Expand Down
16 changes: 2 additions & 14 deletions src/components/Block.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ export default class Block extends React.PureComponent {
super(...arguments)

this.state = {
extraMenuItems: [],
menuOpen: false
extraMenuItems: []
}
}

Expand All @@ -35,14 +34,6 @@ export default class Block extends React.PureComponent {
}
}

openMenu() {
this.setState({ menuOpen: true })
}

closeMenu() {
this.setState({ menuOpen: false })
}

componentDidMount() {
this.setMenuItems(this.block)

Expand All @@ -62,7 +53,7 @@ export default class Block extends React.PureComponent {
render() {
let { app, block, children } = this.props
let { component: Component } = this.getBlockType()
let { menuOpen, extraMenuItems } = this.state
let { extraMenuItems } = this.state

// Determine content by taking the default content and extend it with
// the current block content
Expand All @@ -88,9 +79,6 @@ export default class Block extends React.PureComponent {
app={app}
block={block}
items={extraMenuItems}
active={menuOpen}
onOpen={this.openMenu.bind(this)}
onExit={this.closeMenu.bind(this)}
/>
</div>

Expand Down
50 changes: 20 additions & 30 deletions src/components/BlockMenu.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Animator from './Animator'
import FocusTrap from 'react-focus-trap'
import Handle from './MenuHandle'
import Item from './MenuItem'
import MenuItem from './MenuItem'
import React from 'react'
import menuItems from '../config/menu'
import Handle from './MenuHandle'
import { Menu, MenuItems, MenuPopover } from '@reach/menu-button'
import { positionRight } from '@reach/popover'

const defaultProps = {
items: []
Expand All @@ -14,7 +15,12 @@ export default class BlockMenu extends React.Component {
let { id } = item

return (
<Item key={id} ref={(el) => (this[id] = el)} {...item} {...this.props} />
<MenuItem
key={id}
ref={(el) => (this[id] = el)}
{...item}
{...this.props}
/>
)
}

Expand All @@ -24,42 +30,26 @@ export default class BlockMenu extends React.Component {
return items.concat(menuItems).map(this.getMenuItem, this)
}

getMenu() {
if (!this.props.active) return null

return React.createElement(
FocusTrap,
{
active: true,
key: 'menu',
className: 'col-menu',
element: 'nav',
onExit: this.props.onExit,
role: 'navigation'
},
this.getMenuItems()
)
}

render() {
return (
<Animator
className="col-menu-wrapper"
classNames="col-menu"
timeout={{ exit: 200, enter: 300 }}
>
<>
<Handle
key="handle"
ref={(el) => (this.handle = el)}
onClick={this.props.onOpen}
/>
{this.getMenu()}
</>
<Menu>
<Handle key="handle" />

<MenuPopover position={positionRight}>
<div className="colonel">
<MenuItems className="col-menu">{this.getMenuItems()}</MenuItems>
</div>
</MenuPopover>
</Menu>
</Animator>
)
}
}

BlockMenu.Item = Item
BlockMenu.Item = MenuItem
BlockMenu.defaultProps = defaultProps
114 changes: 59 additions & 55 deletions src/components/BlockTypeGroup.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,85 @@
import React from 'react'
import Animator from './Animator'
import FocusTrap from 'react-focus-trap'
import Btn from './Button'
import Item from './MenuItem'
import { Menu, MenuButton, MenuItems, MenuPopover } from '@reach/menu-button'
import { positionRight } from '@reach/popover'

const defaultProps = {
items: []
}

export default class BlockTypeGroup extends React.Component {
constructor() {
super(...arguments)
getMenuItem(type) {
const { id, label } = type
const { onAdd } = this.props

this.state = {
open: false
}
}

open() {
this.setState({ open: true })
}

close() {
this.setState({ open: false })
}

getButton(type) {
let { id, label } = type
let { onAdd } = this.props

return (
<Btn key={id} className="col-menu-item" onClick={() => onAdd(type)}>
{label}
</Btn>
)
return <Item key={id} label={label} onClick={() => onAdd(type)} />
}

getMenu() {
return this.state.open ? (
<FocusTrap
key="menu"
className="col-menu"
element="nav"
active
onExit={this.close.bind(this)}
role="navigation"
>
{this.props.items.map(this.getButton, this)}
</FocusTrap>
) : null
getMenuItems() {
return this.props.items.map(this.getMenuItem, this)
}

render() {
return (
<Animator
role="button"
classNames="col-menu"
<div
className="col-switch-dropdown"
transition={{ exit: 200, enter: 300 }}
onKeyUp={this._onKeyUp.bind(this)}
onKeyDown={this._onKeyDown.bind(this)}
>
<>
<Btn
key="label"
className="col-switch-btn col-menu-label"
onClick={this.open.bind(this)}
>
{this.props.label}
</Btn>
{this.getMenu()}
</>
</Animator>
<Animator
role="button"
classNames="col-menu"
className="col-switch-dropdown"
transition={{ exit: 200, enter: 300 }}
>
<Menu>
{({ isExpanded }) => {
// @HACK(shawk): workaround for the inability to introspect
// the React UI Menu state
this.menuIsOpen = isExpanded

return (
<>
<MenuButton
key="label"
className="col-switch-btn col-menu-label"
>
{this.props.label}
</MenuButton>

<MenuPopover
ref={(el) => (this.menu = el)}
position={positionRight}
>
<div className="colonel">
<MenuItems className="col-menu">
{this.getMenuItems()}
</MenuItems>
</div>
</MenuPopover>
</>
)
}}
</Menu>
</Animator>
</div>
)
}

_onKeyDown(event) {
// This is a bit of a hack to workaround the event lifecycle of the Reach UI
// Menu. By the time the key up event bubbles up to our wrapper, `this.menuIsOpen`
// is already `false` so we need to track separately whether the menu was open.
if (event.key === 'Escape' && this.menuIsOpen) {
this.menuWasOpen = true
}
}

_onKeyUp(event) {
// Do not allow escape presses to bubble up to parent switch
if (event.key === 'Escape' && this.state.open) {
if (event.key === 'Escape' && this.menuWasOpen) {
this.menuWasOpen = false
event.stopPropagation()
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/components/MenuHandle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import Btn from './Button'
import { MenuButton } from '@reach/menu-button'

const defaultProps = {
className: 'col-menu-handle',
Expand All @@ -9,15 +9,15 @@ const defaultProps = {

export default class MenuHandle extends React.Component {
render() {
const { label, ...safe } = this.props
const { label, ...props } = this.props

return (
<Btn {...safe}>
<MenuButton {...props}>
<span className="col-hidden">{label}</span>
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
</svg>
</Btn>
</MenuButton>
)
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/components/MenuItem.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Button from './Button'
import React from 'react'
import { MenuItem as ReachMenuItem } from '@reach/menu-button'

const defaultProps = {
className: 'col-menu-item',
Expand All @@ -21,20 +21,22 @@ export default class MenuItem extends React.Component {
block,
onOpen,
onExit,
onClick, // don't forward this to the Reach component
active,
isDisabled,
items,
...safe
} = this.props

return (
<Button
<ReachMenuItem
{...safe}
onClick={this._onClick.bind(this)}
as="button"
onSelect={this._onClick.bind(this)}
disabled={this.isDisabled()}
>
{this._formatLabel(label)}
</Button>
</ReachMenuItem>
)
}

Expand Down
Loading

0 comments on commit 03dd475

Please sign in to comment.