Add an autofill option to SelectControl (https://github.com/woocommerce/woocommerce-admin/pull/3105)
* Add autofill method for countryState * Add isFocused state to SelectControl * Allow children prop for SelectControl components
This commit is contained in:
parent
ec450264af
commit
a946d474ce
|
@ -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' ) }
|
||||
|
|
|
@ -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' ) }
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -75,6 +75,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.woocommerce-select-control__autofill-input {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.woocommerce-select-control__tags {
|
||||
position: relative;
|
||||
margin: $gap-small 0;
|
||||
|
|
Loading…
Reference in New Issue