UNPKG

appirio-tech-react-components-test10

Version:

Topcoder UI Kit library of components for our React apps.

237 lines (203 loc) 7.63 kB
import React, { Component } from 'react' import PT from 'prop-types' import { HOC as hoc } from 'formsy-react' import classNames from 'classnames' import _ from 'lodash' import HelpIcon from '../HelpIcon/HelpIcon' import Dropdown from '../Dropdown/Dropdown' import IconDown from '../Icons/IconTcCarretDown' import IconUICheckSimple from '../Icons/IconUICheckSimple' import styles from './PhoneInput.scss' import { AsYouType } from 'libphonenumber-js' class PhoneInput extends Component { constructor(props) { super(props) this.changeValue = this.changeValue.bind(this) this.choseCountry = this.choseCountry.bind(this) this.isValidInput = this.isValidInput.bind(this) this.onDocClick = this.onDocClick.bind(this) this.state = { currentCountry: {}, asYouType: {} } } componentWillReceiveProps(newProps) { let stateUpdate // initialize currentCountry and asYouType states if (_.isEmpty(this.state.asYouType) && this.props.getValue()) { const { country: currentCountry, asYouType} = this.getCountryFromPhoneNumber(this.props.getValue()) if (asYouType.country && currentCountry) { stateUpdate = { asYouType, currentCountry } this.setState(stateUpdate) } } // If a new country is forced externally, update the country code if (newProps.forceCountry && newProps.forceCountry !== this.props.forceCountry ) { const currentCountry = _.get(this.state, 'currentCountry.name') || _.get(stateUpdate, 'currentCountry.name') if (newProps.forceCountry !== currentCountry) { const country = _.find(this.props.listCountry, c => c.name === newProps.forceCountry) this.choseCountry(country, stateUpdate, true) } } } componentDidMount() { document.addEventListener('click', this.onDocClick) } componentWillUnmount() { document.removeEventListener('click', this.onDocClick) } onDocClick(e) { if (!e.path.includes(this.refs.wrapper)) { this.props.onOutsideClick() } } isValidInput() { const value = this.props.getValue() const hasError = !this.props.isPristine() && !this.props.isValid() return (!this.props.forceErrorMessage && value && !hasError && this.state.currentCountry.code) } /** * Gets the country object and associated asYouType object for the given phone number * @param {string} phoneNumber The phone number */ getCountryFromPhoneNumber(phoneNumber) { let country const asYouType = new AsYouType() asYouType.input(phoneNumber[0] === '+' ? phoneNumber : '+' + phoneNumber) if (asYouType.country) { country = _.filter(this.props.listCountry, { alpha2: asYouType.country })[0] } return { country, asYouType } } changeValue(e, externalChange) { const value = e.target.value this.props.setValue(value) const { country: currentCountry, asYouType} = this.getCountryFromPhoneNumber(value) if (asYouType.country && currentCountry) { this.setState({ asYouType, currentCountry }) } this.props.onChange(this.props.name, value) this.props.onChangeCountry({ phone: value, country: currentCountry || {}, externalChange }) } choseCountry(country, updatedState, externalChange) { if (country.code !== this.state.currentCountry.code) { const asYouTypeTmp = new AsYouType(country.alpha2) const { asYouType } = updatedState || this.state let phoneNumber = '' if (asYouType && asYouType.getNationalNumber) { phoneNumber = ` ${asYouType.getNationalNumber()}` } if (asYouTypeTmp.countryCallingCode) { this.setState({ currentCountry: country }) this.changeValue({ target: { value: `+${asYouTypeTmp.countryCallingCode}${phoneNumber}` } }, externalChange) } } } render() { const { label, name, type, minValue, maxValue, placeholder, wrapperClass, maxLength, theme, labelHelpTooltip, readonly, readonlyValueTooltip, showCheckMark } = this.props const hasError = !this.props.isPristine() && !this.props.isValid() const disabled = this.props.isFormDisabled() || this.props.disabled const wrapperClasses = classNames(wrapperClass, theme, { [styles['readonly-wrapper']]: readonly, 'phone-input-container': true }) const classes = classNames('tc-file-field__inputs', { error: hasError }, { empty: this.props.getValue() === '' }) const errorMessage = this.props.getErrorMessage() || this.props.validationError return ( <div className={wrapperClasses} ref="wrapper"> <label className="tc-label"> {label} {labelHelpTooltip && <HelpIcon tooltip={labelHelpTooltip} />} </label> <div className="input-container"> <Dropdown handleKeyboardNavigation pointerShadow> <div className="dropdown-menu-header flex center middle">{this.state.currentCountry && this.state.currentCountry.alpha3 ? this.state.currentCountry.alpha3 : 'Country code'} <IconDown width={20} height={12} fill="#fff" wrapperClass="arrow" /></div> <ul className="dropdown-menu-list"> { this.props.listCountry.map((country, i) => { /* eslint-disable react/jsx-no-bind */ return <li tabIndex="-1" className={(this.state.currentCountry.code === country.code) ? 'selected' : ''} onClick={() => this.choseCountry(country)} key={i}><a href="javascript:;">{country.name}</a></li> }) } </ul> </Dropdown> <input name={name} className={classes} type={type} placeholder={placeholder} value={this.props.getValue()} disabled={disabled} onChange={this.changeValue} maxLength={maxLength} min={minValue} max={maxValue} /> </div> {this.isValidInput() && showCheckMark && ( <IconUICheckSimple wrapperClass="check-success-icon" width={10} height={10} fill="#5CC900" /> )} {readonly && ( <div styleName="readonly-value"> {this.props.getValue()} {readonlyValueTooltip && <HelpIcon tooltip={readonlyValueTooltip} />} </div> )} {hasError ? (<p className="error-message">{errorMessage}</p>) : (this.props.forceErrorMessage && (<p className="error-message">{this.props.forceErrorMessage}</p>))} </div> ) } } PhoneInput.defaultProps = { onChange: () => { }, onOutsideClick: () => { }, forceErrorMessage: null, listCountry: [], showCheckMark: false } PhoneInput.propTypes = { /** * The difference from `disabled` is that instead of showing disabled input * we show value using <div> which let us position something immediately after the value */ readonly: PT.bool, /** * Show help icon next to the label with the tooltip defined by this prop */ labelHelpTooltip: PT.node, /** * Show help icon next to the value with the tooltip defined by this prop * This only has any effect if `readonly` is set to `true` */ readonlyValueTooltip: PT.node, /** * Show error message without any condition */ forceErrorMessage: PT.string, /** * country list */ listCountry: PT.array, /** * event when change phone */ onChangeCountry: PT.func, /** * triggered when clicked outside */ onOutsideClick: PT.func, /** * should show check mark icon when valid input */ showCheckMark: PT.bool } export default hoc(PhoneInput)