230 lines
7.0 KiB
JavaScript
230 lines
7.0 KiB
JavaScript
import React from 'react';
|
|
|
|
import { Icon, IconButton } from '@edx/paragon';
|
|
import { ExpandLess, ExpandMore } from '@edx/paragon/icons';
|
|
import PropTypes from 'prop-types';
|
|
import onClickOutside from 'react-onclickoutside';
|
|
|
|
import { FormGroup } from '../common-components';
|
|
import { COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY, FORM_SUBMISSION_ERROR } from './data/constants';
|
|
|
|
class CountryDropdown extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.handleFocus = this.handleFocus.bind(this);
|
|
this.handleOnBlur = this.handleOnBlur.bind(this);
|
|
|
|
this.state = {
|
|
displayValue: '',
|
|
icon: this.expandMoreButton(),
|
|
errorMessage: '',
|
|
showFieldError: true,
|
|
};
|
|
}
|
|
|
|
shouldComponentUpdate(nextProps) {
|
|
const selectedCountry = this.props.options.find((o) => o[COUNTRY_CODE_KEY] === nextProps.value);
|
|
if (this.props.value !== nextProps.value) {
|
|
if (selectedCountry) {
|
|
this.setState({
|
|
displayValue: selectedCountry[COUNTRY_DISPLAY_KEY],
|
|
showFieldError: false,
|
|
errorMessage: nextProps.errorMessage,
|
|
});
|
|
return false;
|
|
}
|
|
// Set persisted country value as display value.
|
|
this.setState({ displayValue: nextProps.value, showFieldError: true, errorMessage: nextProps.errorMessage });
|
|
return false;
|
|
// eslint-disable-next-line no-else-return
|
|
} else if (nextProps.value && selectedCountry && this.state.displayValue === nextProps.value) {
|
|
// Handling a case here where user enters a valid country code that needs to be
|
|
// evaluated and set its display value.
|
|
this.setState({ displayValue: selectedCountry[COUNTRY_DISPLAY_KEY] });
|
|
return false;
|
|
}
|
|
if (this.props.errorCode !== nextProps.errorCode && nextProps.errorCode === 'invalid-country') {
|
|
this.setState({ showFieldError: true, errorMessage: nextProps.errorMessage });
|
|
return false;
|
|
}
|
|
if (this.state.errorMessage !== nextProps.errorMessage) {
|
|
this.setState({ showFieldError: true, errorMessage: nextProps.errorMessage });
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static getDerivedStateFromProps(props, state) {
|
|
if (props.errorCode === FORM_SUBMISSION_ERROR && state.showFieldError) {
|
|
return { errorMessage: props.errorMessage };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getItems(strToFind = '') {
|
|
let { options } = this.props;
|
|
if (strToFind.length > 0) {
|
|
options = options.filter((option) => (option.name.toLowerCase().includes(strToFind.toLowerCase())));
|
|
}
|
|
|
|
return options.map((opt) => {
|
|
const value = opt[COUNTRY_CODE_KEY];
|
|
const displayValue = opt[COUNTRY_DISPLAY_KEY];
|
|
|
|
return (
|
|
<button type="button" name="countryItem" className="dropdown-item data-hj-suppress" value={displayValue} key={value} onClick={(e) => { this.handleItemClick(e); }}>
|
|
{displayValue.length > 30 ? displayValue.substring(0, 30).concat('...') : displayValue}
|
|
</button>
|
|
);
|
|
});
|
|
}
|
|
|
|
handleOnChange = (e) => {
|
|
const filteredItems = this.getItems(e.target.value);
|
|
this.setState({
|
|
dropDownItems: filteredItems,
|
|
displayValue: e.target.value,
|
|
});
|
|
}
|
|
|
|
handleClickOutside = () => {
|
|
if (this.state.dropDownItems?.length > 0) {
|
|
this.setState(() => ({
|
|
icon: this.expandMoreButton(),
|
|
dropDownItems: '',
|
|
}));
|
|
}
|
|
}
|
|
|
|
handleExpandLess() {
|
|
this.setState({ dropDownItems: '', icon: this.expandMoreButton() });
|
|
}
|
|
|
|
handleExpandMore() {
|
|
this.setState(prevState => ({
|
|
dropDownItems: this.getItems(prevState.displayValue), icon: this.expandLessButton(), errorMessage: '', showFieldError: false,
|
|
}));
|
|
}
|
|
|
|
handleFocus(e) {
|
|
const { name, value } = e.target;
|
|
this.setState(prevState => ({
|
|
dropDownItems: this.getItems(name === 'country' ? value : prevState.displayValue),
|
|
icon: this.expandLessButton(),
|
|
errorMessage: '',
|
|
showFieldError: false,
|
|
}));
|
|
if (this.props.handleFocus) { this.props.handleFocus(e); }
|
|
}
|
|
|
|
handleOnBlur(e, itemClicked = false, country = '') {
|
|
const { name } = e.target;
|
|
let countryValue = '';
|
|
if (country) {
|
|
countryValue = country;
|
|
} else {
|
|
countryValue = itemClicked ? e.target.value : this.state.displayValue;
|
|
}
|
|
// For a better user experience, do not validate when focus out from 'country' field
|
|
// and focus on 'countryItem' or 'countryExpand' button.
|
|
if (e.relatedTarget && e.relatedTarget.name === 'countryItem' && (name === 'country' || name === 'countryExpand')) {
|
|
return;
|
|
}
|
|
const normalized = countryValue.toLowerCase();
|
|
const selectedCountry = this.props.options.find((o) => o[COUNTRY_DISPLAY_KEY].toLowerCase() === normalized);
|
|
if (!selectedCountry) {
|
|
this.setState({ errorMessage: this.props.errorMessage, showFieldError: true });
|
|
}
|
|
if (this.props.handleBlur) { this.props.handleBlur({ target: { name: 'country', value: countryValue } }); }
|
|
}
|
|
|
|
handleItemClick(e) {
|
|
let countryValue = '';
|
|
if (!e.target.value) {
|
|
countryValue = e.target.parentElement.parentElement.value;
|
|
}
|
|
this.setState({ dropDownItems: '', icon: this.expandMoreButton() });
|
|
this.handleOnBlur(e, true, countryValue);
|
|
}
|
|
|
|
expandMoreButton() {
|
|
return (
|
|
<IconButton
|
|
className="expand-more"
|
|
onFocus={this.handleFocus}
|
|
onBlur={this.handleOnBlur}
|
|
name="countryExpand"
|
|
src={ExpandMore}
|
|
iconAs={Icon}
|
|
size="sm"
|
|
variant="secondary"
|
|
alt="expand-more"
|
|
onClick={(e) => { this.handleExpandMore(e); }}
|
|
/>
|
|
);
|
|
}
|
|
|
|
expandLessButton() {
|
|
return (
|
|
<IconButton
|
|
className="expand-less"
|
|
onFocus={this.handleFocus}
|
|
onBlur={this.handleOnBlur}
|
|
name="countryExpand"
|
|
src={ExpandLess}
|
|
iconAs={Icon}
|
|
size="sm"
|
|
variant="secondary"
|
|
alt="expand-less"
|
|
onClick={(e) => { this.handleExpandLess(e); }}
|
|
/>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className="mb-4">
|
|
<FormGroup
|
|
as="input"
|
|
name={this.props.name}
|
|
autoComplete="off"
|
|
className="mb-0"
|
|
floatingLabel={this.props.floatingLabel}
|
|
trailingElement={this.state.icon}
|
|
handleChange={this.handleOnChange}
|
|
handleBlur={this.handleOnBlur}
|
|
handleFocus={this.handleFocus}
|
|
value={this.state.displayValue}
|
|
errorMessage={this.state.errorMessage}
|
|
/>
|
|
<div className="dropdown-container">
|
|
{ this.state.dropDownItems?.length > 0 ? this.state.dropDownItems : null }
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
CountryDropdown.defaultProps = {
|
|
options: null,
|
|
floatingLabel: null,
|
|
handleFocus: null,
|
|
handleBlur: null,
|
|
value: null,
|
|
errorMessage: null,
|
|
errorCode: null,
|
|
};
|
|
|
|
CountryDropdown.propTypes = {
|
|
options: PropTypes.arrayOf(PropTypes.object),
|
|
floatingLabel: PropTypes.string,
|
|
handleFocus: PropTypes.func,
|
|
handleBlur: PropTypes.func,
|
|
value: PropTypes.string,
|
|
errorMessage: PropTypes.string,
|
|
errorCode: PropTypes.string,
|
|
name: PropTypes.string.isRequired,
|
|
};
|
|
|
|
export default onClickOutside(CountryDropdown);
|