Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components: Close popover by escape keypress #2697

Merged
merged 4 commits into from Sep 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions components/popover/README.md
Expand Up @@ -68,3 +68,17 @@ An optional additional class name to apply to the rendered popover.

- Type: `String`
- Required: No

## onClose

A callback invoked when the popover should be closed.

- Type: `Function`
- Required: No

## onClickOutside

A callback invoked when the user clicks outside the opened popover, passing the click event. The popover should be closed in response to this interaction. Defaults to `onClose`.

- Type: `Function`
- Required: No
11 changes: 10 additions & 1 deletion components/popover/detect-outside.js
Expand Up @@ -2,12 +2,18 @@
* External dependencies
*/
import clickOutside from 'react-click-outside';
import { flowRight } from 'lodash';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';

/**
* Internal dependencies
*/
import withFocusReturn from '../higher-order/with-focus-return';

class PopoverDetectOutside extends Component {
handleClickOutside( event ) {
const { onClickOutside } = this.props;
Expand All @@ -21,4 +27,7 @@ class PopoverDetectOutside extends Component {
}
}

export default clickOutside( PopoverDetectOutside );
export default flowRight( [
withFocusReturn,
clickOutside,
] )( PopoverDetectOutside );
49 changes: 41 additions & 8 deletions components/popover/index.js
Expand Up @@ -8,16 +8,20 @@ import { isEqual, noop } from 'lodash';
* WordPress dependencies
*/
import { createPortal, Component } from '@wordpress/element';
import { focus } from '@wordpress/utils';
import { focus, keycodes } from '@wordpress/utils';

/**
* Internal dependencies
*/
import './style.scss';
import PopoverDetectOutside from './detect-outside';

const { ESCAPE } = keycodes;

/**
* module constants
* Offset by which popover should adjust horizontally to account for tail.
*
* @type {Number}
*/
const ARROW_OFFSET = 20;

Expand All @@ -29,6 +33,7 @@ export class Popover extends Component {
this.bindNode = this.bindNode.bind( this );
this.setOffset = this.setOffset.bind( this );
this.throttledSetOffset = this.throttledSetOffset.bind( this );
this.maybeClose = this.maybeClose.bind( this );

this.nodes = {};

Expand Down Expand Up @@ -173,16 +178,38 @@ export class Popover extends Component {
];
}

maybeClose( event ) {
const { onKeyDown, onClose } = this.props;

// Close on escape
if ( event.keyCode === ESCAPE && onClose ) {
onClose();
}

// Preserve original content prop behavior
if ( onKeyDown ) {
onKeyDown( event );
}
}

bindNode( name ) {
return ( node ) => this.nodes[ name ] = node;
}

render() {
// Disable reason: We generate the `...contentProps` rest as remainder
// of props which aren't explicitly handled by this component.
//
// eslint-disable-next-line no-unused-vars
const { isOpen, onClose, position, children, className, ...contentProps } = this.props;
const {
isOpen,
onClose,
onClickOutside = onClose,
// Disable reason: We generate the `...contentProps` rest as remainder
// of props which aren't explicitly handled by this component.
//
// eslint-disable-next-line no-unused-vars
position,
children,
className,
...contentProps
} = this.props;
const [ yAxis, xAxis ] = this.getPositions();

if ( ! isOpen ) {
Expand All @@ -197,15 +224,20 @@ export class Popover extends Component {
'is-' + xAxis,
);

// Disable reason: We care to capture the _bubbled_ events from inputs
// within popover as inferring close intent.

/* eslint-disable jsx-a11y/no-static-element-interactions */
return (
<span ref={ this.bindNode( 'anchor' ) }>
{ createPortal(
<PopoverDetectOutside onClickOutside={ onClose }>
<PopoverDetectOutside onClickOutside={ onClickOutside }>
<div
ref={ this.bindNode( 'popover' ) }
className={ classes }
tabIndex="0"
{ ...contentProps }
onKeyDown={ this.maybeClose }
>
<div
ref={ this.bindNode( 'content' ) }
Expand All @@ -219,6 +251,7 @@ export class Popover extends Component {
) }
</span>
);
/* eslint-enable jsx-a11y/no-static-element-interactions */
}
}

Expand Down
22 changes: 11 additions & 11 deletions editor/inserter/index.js
Expand Up @@ -56,16 +56,15 @@ class Inserter extends Component {
}

insertBlock( name ) {
if ( name ) {
const {
insertionPoint,
onInsertBlock,
} = this.props;
onInsertBlock(
name,
insertionPoint
);
}
const {
insertionPoint,
onInsertBlock,
} = this.props;

onInsertBlock(
name,
insertionPoint
);

this.close();
}
Expand All @@ -89,7 +88,8 @@ class Inserter extends Component {
<Popover
isOpen={ opened }
position={ position }
onClose={ this.closeOnClickOutside }
onClose={ this.close }
onClickOutside={ this.closeOnClickOutside }
>
<InserterMenu onSelect={ this.insertBlock } />
</Popover>
Expand Down
9 changes: 2 additions & 7 deletions editor/inserter/menu.js
Expand Up @@ -9,7 +9,7 @@ import { connect } from 'react-redux';
*/
import { __, _n, sprintf } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { withFocusReturn, withInstanceId, withSpokenMessages } from '@wordpress/components';
import { withInstanceId, withSpokenMessages } from '@wordpress/components';
import { keycodes } from '@wordpress/utils';
import { getCategories, getBlockTypes, BlockIcon } from '@wordpress/blocks';

Expand All @@ -20,7 +20,7 @@ import './style.scss';
import { getBlocks, getRecentlyUsedBlocks } from '../selectors';
import { showInsertionPoint, hideInsertionPoint } from '../actions';

const { TAB, ESCAPE, LEFT, UP, RIGHT, DOWN } = keycodes;
const { TAB, LEFT, UP, RIGHT, DOWN } = keycodes;

export const searchBlocks = ( blocks, searchTerm ) => {
const normalizedSearchTerm = searchTerm.toLowerCase().trim();
Expand Down Expand Up @@ -233,11 +233,7 @@ export class InserterMenu extends Component {
keydown.preventDefault();
this.focusNext( this );
break;
case ESCAPE:
keydown.preventDefault();
this.props.onSelect( null );

break;
case LEFT:
if ( this.state.currentFocus === 'search' ) {
return;
Expand Down Expand Up @@ -411,6 +407,5 @@ const connectComponent = connect(
export default flow(
withInstanceId,
withSpokenMessages,
withFocusReturn,
connectComponent
)( InserterMenu );
12 changes: 7 additions & 5 deletions editor/sidebar/post-schedule/index.js
Expand Up @@ -2,7 +2,6 @@
* External dependencies
*/
import { connect } from 'react-redux';
import clickOutside from 'react-click-outside';
import DatePicker from 'react-datepicker';
import moment from 'moment';
import { flowRight } from 'lodash';
Expand All @@ -26,17 +25,20 @@ import { editPost } from '../../actions';
export class PostSchedule extends Component {
constructor() {
super( ...arguments );

this.toggleDialog = this.toggleDialog.bind( this );
this.closeDialog = this.closeDialog.bind( this );

this.state = {
opened: false,
};
this.toggleDialog = this.toggleDialog.bind( this );
}

toggleDialog() {
this.setState( ( state ) => ( { opened: ! state.opened } ) );
}

handleClickOutside() {
closeDialog() {
this.setState( { opened: false } );
}

Expand Down Expand Up @@ -77,6 +79,7 @@ export class PostSchedule extends Component {
<Popover
position="bottom left"
isOpen={ this.state.opened }
onClose={ this.closeDialog }
className="editor-post-schedule__dialog"
>
<DatePicker
Expand Down Expand Up @@ -120,6 +123,5 @@ const applyWithAPIData = withAPIData( () => {

export default flowRight(
applyConnect,
applyWithAPIData,
clickOutside
applyWithAPIData
)( PostSchedule );
12 changes: 10 additions & 2 deletions editor/sidebar/post-visibility/index.js
Expand Up @@ -28,6 +28,7 @@ export class PostVisibility extends Component {
this.toggleDialog = this.toggleDialog.bind( this );
this.stopPropagation = this.stopPropagation.bind( this );
this.closeOnClickOutside = this.closeOnClickOutside.bind( this );
this.close = this.close.bind( this );
this.setPublic = this.setPublic.bind( this );
this.setPrivate = this.setPrivate.bind( this );
this.setPasswordProtected = this.setPasswordProtected.bind( this );
Expand All @@ -48,8 +49,14 @@ export class PostVisibility extends Component {
}

closeOnClickOutside( event ) {
if ( ! this.buttonNode.contains( event.target ) ) {
this.close();
}
}

close() {
const { opened } = this.state;
if ( opened && ! this.buttonNode.contains( event.target ) ) {
if ( opened ) {
this.toggleDialog();
}
}
Expand Down Expand Up @@ -133,7 +140,8 @@ export class PostVisibility extends Component {
<Popover
position="bottom left"
isOpen={ this.state.opened }
onClose={ this.closeOnClickOutside }
onClickOutside={ this.closeOnClickOutside }
onClose={ this.close }
onClick={ this.stopPropagation }
className="editor-post-visibility__dialog"
>
Expand Down