woocommerce/plugins/woocommerce-blocks/assets/js/base/components/state-input/state-input.tsx

146 lines
3.1 KiB
TypeScript
Raw Normal View History

/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { decodeEntities } from '@wordpress/html-entities';
import { useCallback, useMemo, useEffect, useRef } from '@wordpress/element';
import classnames from 'classnames';
Convert validation context to data store (https://github.com/woocommerce/woocommerce-blocks/pull/6402) * Add validation reducers, actions, and action types * Add selector for getValidationErrors * Export store key and register store * Export validation store key * Move TextInput files to checkout package * Export ValidatedTextInput from blocks-checkout package * Update imports of ValidatedTextInput to reflect new location * Use the validation wp-data store for showing error messages * Export getValidationError in checkout package * Move validation store to checkout package * Move ValidationInputError to blocks-checkout package * Only export "exposedSelectors" from validation * Convert validation context to data store * Fixed linting error * Fixed linting error * Change the validation selectors to return a function * Convert reducer and selectors to TS * Remove superfluous comments and improve test titles * Test to ensure visible errors remain visible * Make test for hasValidationErrors more robust * Augment the wp-data module to include our selectors and actions * Removed unused `exposedSelectors` variable * Remove TS error because of `instanceId` on props * Remove unnecessary as const * Use function returned by getValidationError * Use correct selector/action names now context has been decoupled * hide validation error when input value changes * Add correct aria-describedBy now we can get error id from store * Clear validation error from store when component unmounts * Clear validation error if input is valid * convert ValidationInputError to TS and get correct id/error from store * Ensure checkout block doesn't break when there are no errors * Get validation data from the store instead of context * Update country input to remove validation context * Move validation store out of checkout package * Move TextInput and ValidationInputError back out of the checkout package * Remove duplicate internal styles comment * Remove exports that no longer exist * Get validation store key from block-data * Make attribute-select-control use validation data store * Export FieldValidationStatus type * Make combobox use validation store not context * Make Address use validation store not context * Make Address use validation store not context * Use hasValidationErrors selector as a function in shipping calculator * Remove validation context from coupon story * Import VALIDATION_STORE_KEY from correct location * Stop coupon story from erroring * Update useStoreCartCoupons to use validation store not context * Make TotalsCoupon use validation store instead of context * Make AddToCartFormContext use validation store not context * Remove ValidationContext * Import FieldValidationStatus from correct location * Import ValidatedTextInput and ValidatedTextInput from correct location * Remove ValidationContextProvider * Update components to use validation store not context * Update useValidation to use the data store * Replace the validation context in checkout-events file * Use the re-mapped path for the store key import * Use "register" instead of the deprecated "registerStore" * Fix import error of the "FieldValidationStatus" type * Use TS instead of React's "PropTypes" * Fix the type of "ValidationInputError" in the "payment-method-interface" * Fix error not showing on the first place order click bug We were mutating the state in the reducer, which prevented re-rendering on state change * Fix state mutation issue in the Validation reducer Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Saad Tarhi <saad.trh@gmail.com>
2022-07-01 23:06:25 +00:00
import { ValidatedTextInput } from '@woocommerce/base-components/text-input';
/**
* Internal dependencies
*/
import Combobox from '../combobox';
import './style.scss';
import type { StateInputWithStatesProps } from './StateInputProps';
const optionMatcher = (
value: string,
options: { label: string; value: string }[]
): string => {
const foundOption = options.find(
( option ) =>
option.label.toLocaleUpperCase() === value.toLocaleUpperCase() ||
option.value.toLocaleUpperCase() === value.toLocaleUpperCase()
);
return foundOption ? foundOption.value : '';
};
const StateInput = ( {
className,
id,
states,
country,
label,
onChange,
autoComplete = 'off',
value = '',
required = false,
}: StateInputWithStatesProps ): JSX.Element => {
const countryStates = states[ country ];
const options = useMemo(
() =>
countryStates
? Object.keys( countryStates ).map( ( key ) => ( {
value: key,
label: decodeEntities( countryStates[ key ] ),
} ) )
: [],
[ countryStates ]
);
/**
* Handles state selection onChange events. Finds a matching state by key or value.
*/
const onChangeState = useCallback(
( stateValue: string ) => {
onChange(
options.length > 0
? optionMatcher( stateValue, options )
: stateValue
);
},
[ onChange, options ]
);
/**
* Track value changes.
*/
const valueRef = useRef< string >( value );
useEffect( () => {
if ( valueRef.current !== value ) {
valueRef.current = value;
}
}, [ value ] );
/**
* If given a list of options, ensure the value matches those options or trigger change.
*/
useEffect( () => {
if ( options.length > 0 && valueRef.current ) {
const match = optionMatcher( valueRef.current, options );
if ( match !== valueRef.current ) {
onChangeState( match );
}
}
}, [ options, onChangeState ] );
if ( options.length > 0 ) {
return (
<>
<Combobox
className={ classnames(
className,
'wc-block-components-state-input'
) }
id={ id }
label={ label }
onChange={ onChangeState }
options={ options }
value={ value }
errorMessage={ __(
'Please select a state.',
'woo-gutenberg-products-block'
) }
required={ required }
autoComplete={ autoComplete }
/>
{ autoComplete !== 'off' && (
<input
type="text"
aria-hidden={ true }
autoComplete={ autoComplete }
value={ value }
onChange={ ( event ) =>
onChangeState( event.target.value )
}
style={ {
minHeight: '0',
height: '0',
border: '0',
padding: '0',
position: 'absolute',
} }
tabIndex={ -1 }
/>
) }
</>
);
}
return (
<ValidatedTextInput
className={ className }
id={ id }
label={ label }
onChange={ onChangeState }
autoComplete={ autoComplete }
value={ value }
required={ required }
/>
);
};
export default StateInput;