* Add autofill method for countryState

* Add isFocused state to SelectControl

* Allow children prop for SelectControl components
This commit is contained in:
Joshua T Flowers 2019-10-31 07:44:57 +08:00 committed by GitHub
parent ec450264af
commit a946d474ce
6 changed files with 125 additions and 12 deletions

View File

@ -4,7 +4,9 @@
*/
import { __ } from '@wordpress/i18n';
import { decodeEntities } from '@wordpress/html-entities';
import { useMemo } from 'react';
import { escapeRegExp } from 'lodash';
import { Fragment } from '@wordpress/element';
import { useEffect, useMemo, useState } from 'react';
import { getSetting } from '@woocommerce/wc-admin-settings';
/**
@ -69,6 +71,66 @@ export function getCountryStateOptions() {
return countryStateOptions;
}
/**
* Get the autofill countryState fields and set value from filtered options.
*
* @param {Array} options Array of filterable options.
* @param {String} countryState The value of the countryState field.
* @param {Function} setValue Set value of the countryState input.
* @return {Object} React component.
*/
export function getCountryStateAutofill( options, countryState, setValue ) {
const [ autofillCountry, setAutofillCountry ] = useState( '' );
const [ autofillState, setAutofillState ] = useState( '' );
useEffect(
() => {
let filteredOptions = [];
if ( autofillState.length || autofillCountry.length ) {
const countrySearch = new RegExp(
escapeRegExp( autofillCountry.replace( /\s/g, '' ) ),
'i'
);
filteredOptions = options.filter( option =>
countrySearch.test( option.label.replace( '-', '' ).replace( /\s/g, '' ) )
);
}
if ( autofillCountry.length && autofillState.length ) {
const stateSearch = new RegExp( escapeRegExp( autofillState.replace( /\s/g, '' ) ), 'i' );
filteredOptions = filteredOptions.filter( option =>
stateSearch.test( option.label.replace( '-', '' ).replace( /\s/g, '' ) )
);
}
if ( 1 === filteredOptions.length && countryState !== filteredOptions[ 0 ].key ) {
setValue( 'countryState', filteredOptions[ 0 ].key );
}
},
[ autofillCountry, autofillState ]
);
return (
<Fragment>
<input
onChange={ event => setAutofillCountry( event.target.value ) }
value={ autofillCountry }
name="country"
type="text"
className="woocommerce-select-control__autofill-input"
tabIndex="-1"
/>
<input
onChange={ event => setAutofillState( event.target.value ) }
value={ autofillState }
name="state"
type="text"
className="woocommerce-select-control__autofill-input"
tabIndex="-1"
/>
</Fragment>
);
}
/**
* Store address fields.
*
@ -76,7 +138,7 @@ export function getCountryStateOptions() {
* @return {Object} -
*/
export function StoreAddress( props ) {
const { getInputProps } = props;
const { getInputProps, setValue } = props;
const countryStateOptions = useMemo( () => getCountryStateOptions(), [] );
return (
@ -99,7 +161,13 @@ export function StoreAddress( props ) {
options={ countryStateOptions }
isSearchable
{ ...getInputProps( 'countryState' ) }
/>
>
{ getCountryStateAutofill(
countryStateOptions,
getInputProps( 'countryState' ).value,
setValue
) }
</SelectControl>
<TextControl
label={ __( 'City', 'woocommerce-admin' ) }

View File

@ -139,7 +139,7 @@ class StoreDetails extends Component {
onSubmitCallback={ this.onSubmit }
validate={ validateStoreAddress }
>
{ ( { getInputProps, handleSubmit, values, isValidForm } ) => (
{ ( { getInputProps, handleSubmit, values, isValidForm, setValue } ) => (
<Fragment>
{ showUsageModal && (
<UsageModal
@ -147,7 +147,7 @@ class StoreDetails extends Component {
onClose={ () => this.setState( { showUsageModal: false } ) }
/>
) }
<StoreAddress getInputProps={ getInputProps } />
<StoreAddress getInputProps={ getInputProps } setValue={ setValue } />
<CheckboxControl
label={ __( "I'm setting up a store for a client", 'woocommerce-admin' ) }
{ ...getInputProps( 'isClient' ) }

View File

@ -81,9 +81,9 @@ export default class StoreLocation extends Component {
onSubmitCallback={ this.onSubmit }
validate={ validateStoreAddress }
>
{ ( { getInputProps, handleSubmit } ) => (
{ ( { getInputProps, handleSubmit, setValue } ) => (
<Fragment>
<StoreAddress getInputProps={ getInputProps } />
<StoreAddress getInputProps={ getInputProps } setValue={ setValue } />
<Button isPrimary onClick={ handleSubmit }>
{ __( 'Continue', 'woocommerce-admin' ) }
</Button>

View File

@ -110,6 +110,7 @@ class Control extends Component {
return (
<input
autoComplete="off"
className="woocommerce-select-control__control-input"
id={ `woocommerce-select-control-${ instanceId }__control-input` }
ref={ this.input }
@ -133,14 +134,14 @@ class Control extends Component {
}
getInputValue() {
const { isSearchable, multiple, query, selected } = this.props;
const { isFocused, isSearchable, multiple, query, selected } = this.props;
const selectedValue = selected.length ? selected[ 0 ].label : '';
if ( ! isSearchable && multiple ) {
return '';
}
return isSearchable ? query : selectedValue;
return isSearchable && isFocused ? query : selectedValue;
}
render() {

View File

@ -25,6 +25,7 @@ export class SelectControl extends Component {
static getInitialState() {
return {
isExpanded: false,
isFocused: false,
query: '',
};
}
@ -40,6 +41,7 @@ export class SelectControl extends Component {
this.bindNode = this.bindNode.bind( this );
this.decrementSelectedIndex = this.decrementSelectedIndex.bind( this );
this.incrementSelectedIndex = this.incrementSelectedIndex.bind( this );
this.onAutofillChange = this.onAutofillChange.bind( this );
this.search = this.search.bind( this );
this.selectOption = this.selectOption.bind( this );
this.setExpanded = this.setExpanded.bind( this );
@ -198,7 +200,7 @@ export class SelectControl extends Component {
search( query ) {
const { hideBeforeSearch, onSearch, options } = this.props;
this.setState( { query } );
this.setState( { query, isFocused: true } );
const promise = ( this.activePromise = Promise.resolve( onSearch( options, query ) ).then(
searchOptions => {
@ -226,9 +228,26 @@ export class SelectControl extends Component {
) );
}
onAutofillChange( event ) {
const { options } = this.props;
const filteredOptions = this.getFilteredOptions( options, event.target.value );
if ( 1 === filteredOptions.length ) {
this.selectOption( filteredOptions[ 0 ] );
}
}
render() {
const { className, inlineTags, instanceId, isSearchable, options } = this.props;
const { isExpanded, selectedIndex } = this.state;
const {
autofill,
children,
className,
inlineTags,
instanceId,
isSearchable,
options,
} = this.props;
const { isExpanded, isFocused, selectedIndex } = this.state;
const hasTags = this.hasTags();
const { key: selectedKey = '' } = options[ selectedIndex ] || {};
@ -241,10 +260,21 @@ export class SelectControl extends Component {
<div
className={ classnames( 'woocommerce-select-control', className, {
'has-inline-tags': hasTags && inlineTags,
'is-focused': isFocused,
'is-searchable': isSearchable,
} ) }
ref={ this.bindNode }
>
{ autofill && (
<input
onChange={ this.onAutofillChange }
name={ autofill }
type="text"
className="woocommerce-select-control__autofill-input"
tabIndex="-1"
/>
) }
{ children }
<Control
{ ...this.props }
{ ...this.state }
@ -280,6 +310,14 @@ export class SelectControl extends Component {
}
SelectControl.propTypes = {
/**
* Name to use for the autofill field, not used if no string is passed.
*/
autofill: PropTypes.string,
/**
* A renderable component (or string) which will be displayed before the `Control` of this component.
*/
children: PropTypes.node,
/**
* Class name applied to parent div.
*/
@ -380,6 +418,7 @@ SelectControl.propTypes = {
};
SelectControl.defaultProps = {
autofill: null,
excludeSelectedOptions: true,
getSearchExpression: identity,
inlineTags: false,

View File

@ -75,6 +75,11 @@
}
}
.woocommerce-select-control__autofill-input {
position: absolute;
z-index: -1;
}
.woocommerce-select-control__tags {
position: relative;
margin: $gap-small 0;