Skip to content

Commit

Permalink
Autocomplete: Reuse the Popover component
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Sep 5, 2017
1 parent 089ab69 commit 5c8f242
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 85 deletions.
60 changes: 10 additions & 50 deletions components/autocomplete/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { keycodes } from '@wordpress/utils';
*/
import './style.scss';
import Button from '../button';
import Popover from '../popover';

const { ENTER, ESCAPE, UP, DOWN } = keycodes;

Expand All @@ -24,15 +25,13 @@ class Autocomplete extends Component {
isOpen: false,
search: /.*/,
selectedIndex: 0,
position: {},
};
}

constructor() {
super( ...arguments );

this.bindNode = this.bindNode.bind( this );
this.reposition = this.reposition.bind( this );
this.select = this.select.bind( this );
this.reset = this.reset.bind( this );
this.onBlur = this.onBlur.bind( this );
Expand All @@ -42,36 +41,10 @@ class Autocomplete extends Component {
this.state = this.constructor.getInitialState();
}

componentDidUpdate( prevProps, prevState ) {
// Only monitor resizing and scroll events for duration of results open
const { isOpen } = this.state;
if ( isOpen === prevState.isOpen ) {
return;
}

const bindFn = isOpen ? 'addEventListener' : 'removeEventListener';
window[ bindFn ]( 'resize', this.reposition );
window[ bindFn ]( 'scroll', this.reposition );
}

componentWillUnmount() {
window.removeEventListener( 'resize', this.reposition );
window.removeEventListener( 'scroll', this.reposition );
window.cancelAnimationFrame( this.pendingReposition );
}

bindNode( node ) {
this.node = node;
}

reposition() {
this.pendingReposition = window.requestAnimationFrame( () => {
this.setState( {
position: this.getInputPosition(),
} );
} );
}

select( option ) {
const { onSelect } = this.props;

Expand All @@ -94,20 +67,6 @@ class Autocomplete extends Component {
}
}

getInputPosition() {
// Get position of bottom of text caret. Currently this only supports
// contenteditable, but could be enhanced to support input and textarea
// with a library like `textarea-caret-position`
const range = window.getSelection().getRangeAt( 0 );
if ( range ) {
const { top, left, height } = range.getBoundingClientRect();
return {
top: top + height,
left,
};
}
}

search( event ) {
const { triggerPrefix } = this.props;
const { isOpen } = this.state;
Expand Down Expand Up @@ -137,7 +96,6 @@ class Autocomplete extends Component {
if ( match ) {
// Reset selection to initial when search changes
this.setState( {
position: this.getInputPosition(),
selectedIndex: 0,
search,
} );
Expand All @@ -148,7 +106,6 @@ class Autocomplete extends Component {
} else if ( match ) {
this.setState( {
isOpen: true,
position: this.getInputPosition(),
search,
} );
}
Expand Down Expand Up @@ -224,25 +181,28 @@ class Autocomplete extends Component {

render() {
const { children, className } = this.props;
const { position, isOpen, selectedIndex } = this.state;
const classes = classnames( 'components-autocomplete', className );
const { isOpen, selectedIndex } = this.state;
const classes = classnames( 'components-autocomplete__popover', className );

// Blur is applied to the wrapper node, since if the child is Editable,
// the event will not have `relatedTarget` assigned.
return (
<div
ref={ this.bindNode }
onBlur={ this.onBlur }
className={ classes }
className="components-autocomplete"
>
{ cloneElement( Children.only( children ), {
onInput: this.search,
onKeyDown: this.setSelectedIndex,
} ) }
{ isOpen && (
<Popover
isOpen={ isOpen }
position="top right"
className={ classes }
>
<ul
role="menu"
style={ { ...position } }
className="components-autocomplete__results"
>
{ this.getFilteredOptions().map( ( option, index ) => (
Expand All @@ -259,7 +219,7 @@ class Autocomplete extends Component {
</li>
) ) }
</ul>
) }
</Popover>
</div>
);
}
Expand Down
38 changes: 5 additions & 33 deletions components/autocomplete/style.scss
Original file line number Diff line number Diff line change
@@ -1,42 +1,14 @@
.components-autocomplete .components-autocomplete__results {
list-style-type: none;
position: fixed;
z-index: z-index( '.components-autocomplete__results' );
.components-autocomplete__popover .components-popover__content {
width: 200px;
}

.components-autocomplete__popover .components-autocomplete__results {
list-style-type: none;
padding: 3px;
box-shadow: $shadow-popover;
border: 1px solid $light-gray-500;
background: $white;
margin-top: 10px;
margin-left: -15px;

&:empty {
display: none;
}

&:before {
top: -10px;
border: 10px solid $light-gray-500;
}

&:after {
top: -8px;
border: 10px solid $white;
}

&:before,
&:after {
content: "";
position: absolute;
margin-left: 5px;
height: 0;
width: 0;
border-left-color: transparent;
border-right-color: transparent;
border-bottom-style: solid;
border-top: none;
line-height: 0;
}
}

.components-autocomplete__result {
Expand Down
6 changes: 4 additions & 2 deletions components/autocomplete/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ describe( 'Autocomplete', () => {
{ input }
</Autocomplete>
);
const popover = wrapper.find( 'Popover' );

expect( wrapper.hasClass( 'my-autocomplete' ) ).toBe( true );
expect( wrapper.state( 'isOpen' ) ).toBe( false );
expect( popover.hasClass( 'my-autocomplete' ) ).toBe( true );
expect( popover.hasClass( 'components-autocomplete__popover' ) ).toBe( true );
expect( wrapper.hasClass( 'components-autocomplete' ) ).toBe( true );
expect( wrapper.find( '[data-ok]' ) ).toHaveLength( 1 );
expect( wrapper.find( 'ul' ) ).toHaveLength( 0 );
} );

it( 'opens on absent trigger prefix search', () => {
Expand Down

0 comments on commit 5c8f242

Please sign in to comment.