| import React, { useEffect, useState, useRef } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import { ErrorMessage } from 'formik'; | |||||
| import IconButton from '../IconButton/IconButton'; | |||||
| import { ReactComponent as Search } from '../../assets/images/svg/search.svg'; | |||||
| import { ReactComponent as EyeOn } from '../../assets/images/svg/eye-on.svg'; | |||||
| import { ReactComponent as EyeOff } from '../../assets/images/svg/eye-off.svg'; | |||||
| import { ReactComponent as CapsLock } from '../../assets/images/svg/caps-lock.svg'; | |||||
| const BaseInputField = ({ | |||||
| type, | |||||
| label, | |||||
| field, | |||||
| form, | |||||
| placeholder, | |||||
| clearPlaceholderOnFocus = true, | |||||
| isSearch, | |||||
| className, | |||||
| disabled, | |||||
| centerText, | |||||
| link, | |||||
| errorMessage, | |||||
| autoFocus, | |||||
| isCapsLockOn, | |||||
| ...props | |||||
| }) => { | |||||
| const [inputPlaceholder, setPlaceholder] = useState(placeholder); | |||||
| const inputField = useRef(null); | |||||
| useEffect(() => { | |||||
| if (autoFocus) { | |||||
| inputField.current.focus(); | |||||
| } | |||||
| }, [autoFocus, inputField]); | |||||
| useEffect(() => { | |||||
| if (errorMessage) { | |||||
| form.setFieldError(field.name, errorMessage); | |||||
| } | |||||
| }, [errorMessage]); // eslint-disable-line | |||||
| useEffect(() => { | |||||
| setPlaceholder(placeholder); | |||||
| }, [placeholder]); | |||||
| const [inputType, setInputType] = useState('password'); | |||||
| const passwordInput = type === 'password' ? ' c-input--password' : ''; | |||||
| const showPassword = () => { | |||||
| if (inputType === 'password') { | |||||
| setInputType('text'); | |||||
| } else { | |||||
| setInputType('password'); | |||||
| } | |||||
| }; | |||||
| // Nester Formik Field Names get bugged because of Undefined values, so i had to fix it like this | |||||
| // If you ask why 0 and 1? I dont see a need for forms to be nested more then 2 levels? | |||||
| const fieldName = field.name.split('.'); | |||||
| const formError = | |||||
| fieldName[0] && fieldName[1] | |||||
| ? form.errors[fieldName[0]] && form.errors[fieldName[0]][fieldName[1]] | |||||
| : form.errors[fieldName[0]]; | |||||
| const formTouched = | |||||
| fieldName[0] && fieldName[1] | |||||
| ? form.touched[fieldName[0]] && form.touched[fieldName[0]][fieldName[1]] | |||||
| : form.touched[fieldName[0]]; | |||||
| function styles() { | |||||
| let style = 'c-input'; | |||||
| if (formError && formTouched) { | |||||
| style += ` c-input--error`; | |||||
| } | |||||
| if (type === 'password') { | |||||
| style += ` c-input--password`; | |||||
| } | |||||
| if (isSearch) { | |||||
| style += ` c-input--search`; | |||||
| } | |||||
| if (centerText) { | |||||
| style += ` c-input--center-text`; | |||||
| } | |||||
| if (type === 'number') { | |||||
| style += ` c-input--demi-bold`; | |||||
| } | |||||
| if (className) { | |||||
| style += ` ${className}`; | |||||
| } | |||||
| return style; | |||||
| } | |||||
| const additionalActions = () => { | |||||
| if (!clearPlaceholderOnFocus) { | |||||
| return null; | |||||
| } | |||||
| return { | |||||
| onFocus: () => { | |||||
| setPlaceholder(''); | |||||
| }, | |||||
| onBlur: (e) => { | |||||
| setPlaceholder(placeholder); | |||||
| field.onBlur(e); | |||||
| }, | |||||
| }; | |||||
| }; | |||||
| return ( | |||||
| <div className={styles()}> | |||||
| {!!label && ( | |||||
| <label className="c-input__label" htmlFor={field.name}> | |||||
| {label} | |||||
| </label> | |||||
| )} | |||||
| {link && <div className="c-input__link">{link}</div>} | |||||
| <div className="c-input__field-wrap"> | |||||
| <input | |||||
| ref={inputField} | |||||
| type={type === 'password' ? inputType : type} | |||||
| placeholder={inputPlaceholder} | |||||
| disabled={disabled} | |||||
| {...field} | |||||
| {...props} | |||||
| {...additionalActions()} | |||||
| className="c-input__field" | |||||
| /> | |||||
| {!!isSearch && <Search className="c-input__icon" />} | |||||
| {!!passwordInput && ( | |||||
| <> | |||||
| {isCapsLockOn && <CapsLock className="c-input__caps-lock" />} | |||||
| <IconButton | |||||
| onClick={() => { | |||||
| showPassword(); | |||||
| }} | |||||
| className="c-input__icon" | |||||
| > | |||||
| {inputType === 'password' ? <EyeOff /> : <EyeOn />} | |||||
| </IconButton> | |||||
| </> | |||||
| )} | |||||
| </div> | |||||
| <ErrorMessage name={field.name}> | |||||
| {(errorMessage) => ( | |||||
| <span className="c-input__error">{errorMessage}</span> | |||||
| )} | |||||
| </ErrorMessage> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| BaseInputField.propTypes = { | |||||
| type: PropTypes.string, | |||||
| field: PropTypes.shape({ | |||||
| name: PropTypes.string, | |||||
| onFocus: PropTypes.func, | |||||
| onBlur: PropTypes.func, | |||||
| }), | |||||
| form: PropTypes.shape({ | |||||
| errors: PropTypes.shape({}), | |||||
| setFieldError: PropTypes.func, | |||||
| touched: PropTypes.shape({}), | |||||
| }), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||||
| disabled: PropTypes.bool, | |||||
| isSearch: PropTypes.bool, | |||||
| className: PropTypes.string, | |||||
| link: PropTypes.node, | |||||
| errorMessage: PropTypes.string, | |||||
| centerText: PropTypes.bool, | |||||
| clearPlaceholderOnFocus: PropTypes.bool, | |||||
| demiBold: PropTypes.bool, | |||||
| touched: PropTypes.bool, | |||||
| autoFocus: PropTypes.bool, | |||||
| isCapsLockOn: PropTypes.bool, | |||||
| }; | |||||
| export default BaseInputField; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import { ReactComponent as Checked } from '../../assets/images/svg/checked.svg'; | |||||
| import { ReactComponent as Unchecked } from '../../assets/images/svg/unchecked.svg'; | |||||
| const Checkbox = ({ className, children, name, onChange, checked, field }) => ( | |||||
| <label htmlFor={name} className={`c-checkbox ${className || ''}`}> | |||||
| <input | |||||
| name={name} | |||||
| id={name} | |||||
| className="c-checkbox__field" | |||||
| type="checkbox" | |||||
| checked={checked} | |||||
| {...field} | |||||
| onChange={onChange || field.onChange} | |||||
| /> | |||||
| <div className="c-checkbox__indicator"> | |||||
| {checked ? ( | |||||
| <Checked className="c-checkbox__icon" /> | |||||
| ) : ( | |||||
| <Unchecked className="c-checkbox__icon" /> | |||||
| )} | |||||
| </div> | |||||
| <div className="c-checkbox__text">{children}</div> | |||||
| </label> | |||||
| ); | |||||
| Checkbox.propTypes = { | |||||
| children: PropTypes.node, | |||||
| onChange: PropTypes.func, | |||||
| checked: PropTypes.bool, | |||||
| name: PropTypes.string, | |||||
| field: PropTypes.shape({ | |||||
| onChange: PropTypes.func, | |||||
| }), | |||||
| className: PropTypes.string, | |||||
| }; | |||||
| export default Checkbox; |
| import React, { useEffect, useRef } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import { ErrorMessage, useField } from 'formik'; | |||||
| import CurrencyInput from 'react-currency-input-field'; | |||||
| import { formatMoneyNumeral } from '../../util/helpers/numeralHelpers'; | |||||
| import { | |||||
| PLUS_SYMBOL, | |||||
| MINUS_SYMBOL, | |||||
| NUMPAD_MINUS_SYMBOL, | |||||
| NUMPAD_PLUS_SYMBOL, | |||||
| K_KEYCODE, | |||||
| } from '../../constants/keyCodeConstants'; | |||||
| const CurrencyField = ({ | |||||
| autoFocus, | |||||
| notCentered, | |||||
| notBold, | |||||
| label, | |||||
| onChange, | |||||
| value, | |||||
| ...props | |||||
| }) => { | |||||
| const [field, meta] = useField(props); | |||||
| const inputField = useRef(null); | |||||
| function styles() { | |||||
| let style = 'c-currency-field'; | |||||
| if (meta.error && meta.touched) { | |||||
| style += ` c-currency-field--error`; | |||||
| } | |||||
| if (notCentered) { | |||||
| style += ` c-currency-field--not-centered`; | |||||
| } | |||||
| if (notBold) { | |||||
| style += ` c-currency-field--not-bold`; | |||||
| } | |||||
| return style; | |||||
| } | |||||
| useEffect(() => { | |||||
| if (autoFocus) { | |||||
| inputField.current.focus(); | |||||
| } | |||||
| }, [autoFocus, inputField]); | |||||
| const onKeydownHandler = (event) => { | |||||
| if ( | |||||
| event.keyCode === MINUS_SYMBOL || | |||||
| event.keyCode === PLUS_SYMBOL || | |||||
| event.keyCode === NUMPAD_MINUS_SYMBOL || | |||||
| event.keyCode === NUMPAD_PLUS_SYMBOL || | |||||
| event.keyCode === K_KEYCODE | |||||
| ) { | |||||
| event.preventDefault(); | |||||
| } | |||||
| }; | |||||
| const prefix = formatMoneyNumeral(0); | |||||
| const prefixSymbol = () => { | |||||
| if (prefix.includes('CAD')) { | |||||
| return 'CAD '; | |||||
| } | |||||
| return '$'; | |||||
| }; | |||||
| return ( | |||||
| <div className={styles()}> | |||||
| {!!label && ( | |||||
| <label className="c-currency-field__label" htmlFor={field.name}> | |||||
| {label} | |||||
| </label> | |||||
| )} | |||||
| {value ? ( | |||||
| <CurrencyInput | |||||
| {...props} | |||||
| prefix={prefixSymbol()} | |||||
| onValueChange={(value) => { | |||||
| onChange(value ? Number(value) : ''); | |||||
| }} | |||||
| onKeyDown={(event) => onKeydownHandler(event)} | |||||
| ref={inputField} | |||||
| defaultValue={0} | |||||
| value={value} | |||||
| /> | |||||
| ) : ( | |||||
| <CurrencyInput | |||||
| {...props} | |||||
| prefix={prefixSymbol()} | |||||
| onValueChange={(value) => { | |||||
| onChange(value ? Number(value) : ''); | |||||
| }} | |||||
| onKeyDown={(event) => onKeydownHandler(event)} | |||||
| ref={inputField} | |||||
| /> | |||||
| )} | |||||
| <ErrorMessage name={field.name}> | |||||
| {(errorMessage) => ( | |||||
| <span className="c-currency-field__error">{errorMessage}</span> | |||||
| )} | |||||
| </ErrorMessage> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| CurrencyField.propTypes = { | |||||
| field: PropTypes.shape({ | |||||
| name: PropTypes.string, | |||||
| }), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| disabled: PropTypes.bool, | |||||
| onChange: PropTypes.func, | |||||
| autoFocus: PropTypes.bool, | |||||
| notCentered: PropTypes.bool, | |||||
| notBold: PropTypes.bool, | |||||
| value: PropTypes.number, | |||||
| }; | |||||
| export default CurrencyField; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import BaseInputField from './BaseInputField'; | |||||
| const EmailField = ({ | |||||
| field, | |||||
| form, | |||||
| label, | |||||
| placeholder, | |||||
| disabled, | |||||
| ...props | |||||
| }) => ( | |||||
| <BaseInputField | |||||
| type="email" | |||||
| label={label} | |||||
| placeholder={placeholder} | |||||
| disabled={disabled} | |||||
| form={form} | |||||
| field={field} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| EmailField.propTypes = { | |||||
| field: PropTypes.shape({}), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| placeholder: PropTypes.string, | |||||
| disabled: PropTypes.bool, | |||||
| }; | |||||
| export default EmailField; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import BaseInputField from './BaseInputField'; | |||||
| import { | |||||
| PERIOD_SYMBOL, | |||||
| COMMA_SYMBOL, | |||||
| PLUS_SYMBOL, | |||||
| MINUS_SYMBOL, | |||||
| NUMPAD_PERIOD_SYMBOL, | |||||
| NUMPAD_MINUS_SYMBOL, | |||||
| NUMPAD_PLUS_SYMBOL, | |||||
| DOWN_ARROW_KEYCODE, | |||||
| UP_ARROW_KEYCODE, | |||||
| } from '../../constants/keyCodeConstants'; | |||||
| const NumberField = ({ | |||||
| field, | |||||
| form, | |||||
| label, | |||||
| placeholder, | |||||
| disabled, | |||||
| preventAllExceptNumbers, | |||||
| ...props | |||||
| }) => { | |||||
| const onKeydownHandler = (event) => { | |||||
| if (preventAllExceptNumbers) { | |||||
| if ( | |||||
| event.keyCode === PERIOD_SYMBOL || | |||||
| event.keyCode === COMMA_SYMBOL || | |||||
| event.keyCode === NUMPAD_PERIOD_SYMBOL || | |||||
| event.keyCode === DOWN_ARROW_KEYCODE || | |||||
| event.keyCode === UP_ARROW_KEYCODE | |||||
| ) { | |||||
| event.preventDefault(); | |||||
| } | |||||
| } | |||||
| if ( | |||||
| event.keyCode === PLUS_SYMBOL || | |||||
| event.keyCode === MINUS_SYMBOL || | |||||
| event.keyCode === NUMPAD_MINUS_SYMBOL || | |||||
| event.keyCode === NUMPAD_PLUS_SYMBOL || | |||||
| event.keyCode === DOWN_ARROW_KEYCODE || | |||||
| event.keyCode === UP_ARROW_KEYCODE | |||||
| ) { | |||||
| event.preventDefault(); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <BaseInputField | |||||
| type="number" | |||||
| label={label} | |||||
| placeholder={placeholder} | |||||
| disabled={disabled} | |||||
| form={form} | |||||
| field={field} | |||||
| {...props} | |||||
| onKeyDown={(event) => onKeydownHandler(event)} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| NumberField.propTypes = { | |||||
| field: PropTypes.shape({}), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| placeholder: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | |||||
| disabled: PropTypes.bool, | |||||
| preventAllExceptNumbers: PropTypes.bool, | |||||
| }; | |||||
| export default NumberField; |
| import React, { useState } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import BaseInputField from './BaseInputField'; | |||||
| import PasswordStrength from './PasswordStrength'; | |||||
| const PasswordField = ({ | |||||
| field, | |||||
| form, | |||||
| label, | |||||
| placeholder, | |||||
| disabled, | |||||
| shouldTestPasswordStrength, | |||||
| autoFocus, | |||||
| ...props | |||||
| }) => { | |||||
| const [passwordValue, setPasswordValue] = useState(''); | |||||
| const [isCapsLockOn, setIsCapsLockOn] = useState(false); | |||||
| const onChange = (e) => { | |||||
| if (shouldTestPasswordStrength) { | |||||
| const { value } = e.target; | |||||
| setPasswordValue(value); | |||||
| } | |||||
| field.onChange(e); | |||||
| }; | |||||
| const onKeyDown = (keyEvent) => { | |||||
| if (keyEvent.getModifierState('CapsLock')) { | |||||
| setIsCapsLockOn(true); | |||||
| } else { | |||||
| setIsCapsLockOn(false); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <div className="c-password"> | |||||
| <BaseInputField | |||||
| type="password" | |||||
| label={label} | |||||
| placeholder={placeholder} | |||||
| disabled={disabled} | |||||
| form={form} | |||||
| field={field} | |||||
| {...props} | |||||
| onChange={onChange} | |||||
| autoFocus={autoFocus} | |||||
| onKeyDown={onKeyDown} | |||||
| isCapsLockOn={isCapsLockOn} | |||||
| /> | |||||
| {shouldTestPasswordStrength && ( | |||||
| <PasswordStrength | |||||
| passwordValue={passwordValue} | |||||
| shouldTestPasswordStrength | |||||
| /> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| PasswordField.propTypes = { | |||||
| field: PropTypes.shape({ | |||||
| onChange: PropTypes.func, | |||||
| }), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| placeholder: PropTypes.string, | |||||
| disabled: PropTypes.bool, | |||||
| shouldTestPasswordStrength: PropTypes.bool, | |||||
| autoFocus: PropTypes.bool, | |||||
| }; | |||||
| export default PasswordField; |
| import React, { useEffect, useRef, useState } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import owasp from 'owasp-password-strength-test'; | |||||
| import i18next from 'i18next'; | |||||
| owasp.config({ | |||||
| minOptionalTestsToPass: 3, | |||||
| }); | |||||
| const passwordStrengthOptions = [ | |||||
| { | |||||
| strength: 'weak', | |||||
| color: '#FF5028', | |||||
| }, | |||||
| { | |||||
| strength: 'average', | |||||
| color: '#FDB942', | |||||
| }, | |||||
| { | |||||
| strength: 'good', | |||||
| color: '#06BEE7', | |||||
| }, | |||||
| { | |||||
| strength: 'strong', | |||||
| color: '#00876A', | |||||
| }, | |||||
| ]; | |||||
| /** | |||||
| * User must pass a required test and at least 3 optional. | |||||
| * @param result - owasp result | |||||
| * @returns {number} - index of password strength 0-3 | |||||
| */ | |||||
| function getPasswordStrengthIndex(result) { | |||||
| // requirement for strong password is required test passed and at least 3 optional tests | |||||
| if (result.strong) { | |||||
| return 3; | |||||
| } | |||||
| if (!result.strong && result.optionalTestsPassed >= 3) { | |||||
| return 2; | |||||
| } | |||||
| if (result.optionalTestsPassed <= 0) { | |||||
| return 0; | |||||
| } | |||||
| return result.optionalTestsPassed - 1; | |||||
| } | |||||
| const PasswordStrength = ({ | |||||
| shouldTestPasswordStrength, | |||||
| passwordValue, | |||||
| passwordStrengthTestsRequired, | |||||
| }) => { | |||||
| const strengthContainer = useRef(null); | |||||
| const [passwordStrength, setPasswordStrength] = useState({ | |||||
| width: 0, | |||||
| color: 'red', | |||||
| }); | |||||
| const [error, setError] = useState(''); | |||||
| useEffect(() => { | |||||
| if (shouldTestPasswordStrength && passwordValue) { | |||||
| const bBox = strengthContainer.current.getBoundingClientRect(); | |||||
| const result = owasp.test(passwordValue); | |||||
| const passwordStrengthIndex = getPasswordStrengthIndex(result); | |||||
| const passwordOption = passwordStrengthOptions[passwordStrengthIndex]; | |||||
| const width = !passwordValue | |||||
| ? 0 | |||||
| : (bBox.width * (passwordStrengthIndex + 1)) / | |||||
| passwordStrengthTestsRequired; | |||||
| setPasswordStrength({ width, color: passwordOption.color }); | |||||
| const strength = i18next.t(`password.${passwordOption.strength}`); | |||||
| setError(i18next.t('login.passwordStrength', { strength })); | |||||
| } | |||||
| }, [ | |||||
| passwordValue, | |||||
| shouldTestPasswordStrength, | |||||
| passwordStrengthTestsRequired, | |||||
| ]); | |||||
| if (!shouldTestPasswordStrength || !passwordValue) { | |||||
| return null; | |||||
| } | |||||
| const renderError = () => { | |||||
| if (!error) { | |||||
| return null; | |||||
| } | |||||
| return ( | |||||
| <div | |||||
| className="c-input--error" | |||||
| style={{ | |||||
| color: passwordStrength.color, | |||||
| }} | |||||
| > | |||||
| {error} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| return ( | |||||
| <div ref={strengthContainer} className="c-password-strength__container"> | |||||
| <div className="c-password-strength__line--wrapper"> | |||||
| <div | |||||
| className="c-password-strength__line" | |||||
| style={{ | |||||
| backgroundColor: passwordStrength.color, | |||||
| width: passwordStrength.width, | |||||
| }} | |||||
| /> | |||||
| </div> | |||||
| {renderError()} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| PasswordStrength.propTypes = { | |||||
| shouldTestPasswordStrength: PropTypes.bool, | |||||
| passwordValue: PropTypes.string, | |||||
| passwordStrengthTestsRequired: PropTypes.number, | |||||
| }; | |||||
| PasswordStrength.defaultProps = { | |||||
| passwordStrengthTestsRequired: 4, | |||||
| }; | |||||
| export default PasswordStrength; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import NumberFormat from 'react-number-format'; | |||||
| import TextField from './TextField'; | |||||
| const PercentageField = ({ field, ...props }) => { | |||||
| const handleOnChange = (percentageField) => { | |||||
| const { floatValue } = percentageField; | |||||
| if (!props.onChange) { | |||||
| throw Error('Provide an onChange handler'); | |||||
| } | |||||
| if (floatValue > 100) { | |||||
| return props.onChange('100'); | |||||
| } | |||||
| if (floatValue <= 0 || !floatValue) { | |||||
| return props.onChange('0'); | |||||
| } | |||||
| return props.onChange(floatValue.toString()); | |||||
| }; | |||||
| return ( | |||||
| <NumberFormat | |||||
| format="###%" | |||||
| value={field.value} | |||||
| customInput={TextField} | |||||
| field={field} | |||||
| {...props} | |||||
| onValueChange={handleOnChange} | |||||
| onChange={() => {}} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| PercentageField.propTypes = { | |||||
| onChange: PropTypes.func, | |||||
| field: PropTypes.shape({ | |||||
| value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||||
| }), | |||||
| }; | |||||
| export default PercentageField; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import { ErrorMessage, useField } from 'formik'; | |||||
| import PhoneInput from 'react-phone-number-input'; | |||||
| import 'react-phone-number-input/style.css'; | |||||
| const PhoneNumberField = ({ label, ...props }) => { | |||||
| const [field, meta] = useField(props); | |||||
| const inputErrorClassName = | |||||
| meta.error && meta.touched ? 'c-input--error' : ''; | |||||
| return ( | |||||
| <div className={`c-input c-phone-number ${inputErrorClassName}`}> | |||||
| {!!label && ( | |||||
| <label className="c-input__label" htmlFor={field.name}> | |||||
| {label} | |||||
| </label> | |||||
| )} | |||||
| <PhoneInput | |||||
| international | |||||
| defaultCountry="US" | |||||
| {...field} | |||||
| {...props} | |||||
| onChange={(value) => { | |||||
| props.onPhoneChange(value); | |||||
| }} | |||||
| countryOptionsOrder={['US']} | |||||
| /> | |||||
| <ErrorMessage name={field.name}> | |||||
| {(errorMessage) => ( | |||||
| <span className="c-input__error">{errorMessage}</span> | |||||
| )} | |||||
| </ErrorMessage> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| PhoneNumberField.propTypes = { | |||||
| field: PropTypes.shape({ | |||||
| name: PropTypes.string, | |||||
| }), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| disabled: PropTypes.bool, | |||||
| onChange: PropTypes.func, | |||||
| onPhoneChange: PropTypes.func, | |||||
| }; | |||||
| export default PhoneNumberField; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import { ReactComponent as RadioOn } from '../../assets/images/svg/radio-on.svg'; | |||||
| import { ReactComponent as RadioOff } from '../../assets/images/svg/radio-off.svg'; | |||||
| const Checkbox = ({ | |||||
| className, | |||||
| children, | |||||
| name, | |||||
| checked, | |||||
| field, | |||||
| value, | |||||
| selected, | |||||
| id, | |||||
| }) => ( | |||||
| <label | |||||
| htmlFor={name} | |||||
| className={`c-radio ${selected ? 'c-radio--selected' : ''} ${ | |||||
| className || '' | |||||
| }`} | |||||
| > | |||||
| <input | |||||
| name={name} | |||||
| id={id} | |||||
| className="c-radio__field" | |||||
| type="radio" | |||||
| checked={checked} | |||||
| value={value} | |||||
| {...field} | |||||
| /> | |||||
| <div className="c-radio__indicator"> | |||||
| {selected ? ( | |||||
| <RadioOn className="c-radio__icon" /> | |||||
| ) : ( | |||||
| <RadioOff className="c-radio__icon" /> | |||||
| )} | |||||
| </div> | |||||
| <div className="c-radio__text">{children}</div> | |||||
| </label> | |||||
| ); | |||||
| Checkbox.propTypes = { | |||||
| children: PropTypes.node, | |||||
| checked: PropTypes.bool, | |||||
| name: PropTypes.string, | |||||
| field: PropTypes.shape({}), | |||||
| form: PropTypes.shape({}), | |||||
| className: PropTypes.string, | |||||
| value: PropTypes.string, | |||||
| selected: PropTypes.bool, | |||||
| id: PropTypes.string, | |||||
| }; | |||||
| export default Checkbox; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import BaseInputField from './BaseInputField'; | |||||
| const Search = ({ | |||||
| field, | |||||
| form, | |||||
| label, | |||||
| placeholder, | |||||
| disabled, | |||||
| className, | |||||
| ...props | |||||
| }) => ( | |||||
| <BaseInputField | |||||
| type="text" | |||||
| label="" | |||||
| placeholder={placeholder} | |||||
| disabled={disabled} | |||||
| form={form} | |||||
| field={field} | |||||
| isSearch | |||||
| className={className} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| Search.propTypes = { | |||||
| field: PropTypes.shape({}), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| placeholder: PropTypes.string, | |||||
| disabled: PropTypes.bool, | |||||
| className: PropTypes.string, | |||||
| }; | |||||
| export default Search; |
| import React, { useEffect } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import Select, { components, createFilter } from 'react-select'; | |||||
| import { ErrorMessage, useField } from 'formik'; | |||||
| import { ReactComponent as FilledChevronDown } from '../../assets/images/svg/filled-chevron-down.svg'; | |||||
| const SelectField = ({ | |||||
| label, | |||||
| disabled, | |||||
| options, | |||||
| link, | |||||
| defaultSelected = null, | |||||
| dropdownFullHeight, | |||||
| selectOption, | |||||
| ...props | |||||
| }) => { | |||||
| const [field, meta, helpers] = useField(props); | |||||
| const filterConfig = { | |||||
| ignoreCase: true, | |||||
| ignoreAccents: true, | |||||
| trim: true, | |||||
| matchFrom: 'start', | |||||
| }; | |||||
| useEffect(() => { | |||||
| if (defaultSelected) { | |||||
| helpers.setValue(defaultSelected); | |||||
| } | |||||
| }, [defaultSelected]); // eslint-disable-line | |||||
| const DropdownIndicator = (props) => | |||||
| components.DropdownIndicator && ( | |||||
| <components.DropdownIndicator {...props}> | |||||
| <FilledChevronDown /> | |||||
| </components.DropdownIndicator> | |||||
| ); | |||||
| function styles() { | |||||
| let style = 'c-input'; | |||||
| if (meta.error && meta.touched) { | |||||
| style += ` c-input--error`; | |||||
| } | |||||
| if (dropdownFullHeight) { | |||||
| style += ` c-input--dropdown-full-height`; | |||||
| } | |||||
| return style; | |||||
| } | |||||
| return ( | |||||
| <div className={styles()}> | |||||
| {!!label && ( | |||||
| <label className="c-input__label" htmlFor={field.name}> | |||||
| {label} | |||||
| </label> | |||||
| )} | |||||
| {!!link && <div className="c-input__link">{link}</div>} | |||||
| <Select | |||||
| defaultValue={defaultSelected || options[0]} | |||||
| components={{ DropdownIndicator }} | |||||
| isSearchable={false} | |||||
| classNamePrefix="c-select" | |||||
| options={options} | |||||
| isDisabled={disabled} | |||||
| {...field} | |||||
| {...props} | |||||
| onBlur={(e) => { | |||||
| helpers.setTouched(true); | |||||
| field.onBlur(e); | |||||
| }} | |||||
| onChange={(selectedOption) => { | |||||
| helpers.setValue(selectedOption); | |||||
| if (props.onChange) { | |||||
| props.onChange(); | |||||
| } | |||||
| if (selectOption) { | |||||
| selectOption(selectedOption); | |||||
| } | |||||
| }} | |||||
| filterOption={createFilter(filterConfig)} | |||||
| /> | |||||
| <ErrorMessage name={field.name}> | |||||
| {(errorMessage) => { | |||||
| if (typeof errorMessage === 'string') { | |||||
| return <span className="c-input__error">{errorMessage}</span>; | |||||
| } | |||||
| return <span className="c-input__error">{errorMessage.value}</span>; | |||||
| }} | |||||
| </ErrorMessage> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| SelectField.propTypes = { | |||||
| field: PropTypes.shape({ | |||||
| name: PropTypes.string, | |||||
| }), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||||
| disabled: PropTypes.bool, | |||||
| options: PropTypes.arrayOf( | |||||
| PropTypes.shape({ | |||||
| label: PropTypes.string, | |||||
| value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||||
| }), | |||||
| ), | |||||
| onChange: PropTypes.func, | |||||
| link: PropTypes.node, | |||||
| defaultSelected: PropTypes.shape({ | |||||
| label: PropTypes.string, | |||||
| value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||||
| }), | |||||
| dropdownFullHeight: PropTypes.bool, | |||||
| selectOption: PropTypes.func, | |||||
| }; | |||||
| export default SelectField; |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import BaseInputField from './BaseInputField'; | |||||
| import { | |||||
| BACKSPACE_KEYCODE, | |||||
| TAB_KEYCODE, | |||||
| RIGHT_ARROW_KEYCODE, | |||||
| LEFT_ARROW_KEYCODE, | |||||
| } from '../../constants/keyCodeConstants'; | |||||
| const TextField = ({ | |||||
| field, | |||||
| form, | |||||
| label, | |||||
| placeholder, | |||||
| disabled, | |||||
| centerText, | |||||
| autoFocus, | |||||
| preventAllExceptNumbers, | |||||
| ...props | |||||
| }) => { | |||||
| const onKeydownHandler = (event) => { | |||||
| if (preventAllExceptNumbers) { | |||||
| if ( | |||||
| event.keyCode === BACKSPACE_KEYCODE || | |||||
| event.keyCode === TAB_KEYCODE || | |||||
| event.keyCode === RIGHT_ARROW_KEYCODE || | |||||
| event.keyCode === LEFT_ARROW_KEYCODE | |||||
| ) { | |||||
| return; | |||||
| } | |||||
| if ( | |||||
| (event.keyCode < 58 && event.keyCode > 47) || | |||||
| (event.keyCode < 106 && event.keyCode > 95) | |||||
| ) { | |||||
| return; | |||||
| } | |||||
| event.preventDefault(); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <BaseInputField | |||||
| autoFocus={autoFocus} | |||||
| type="text" | |||||
| label={label} | |||||
| placeholder={placeholder} | |||||
| disabled={disabled} | |||||
| form={form} | |||||
| field={field} | |||||
| centerText={centerText} | |||||
| {...props} | |||||
| onKeyDown={(event) => onKeydownHandler(event)} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| TextField.propTypes = { | |||||
| field: PropTypes.shape({}), | |||||
| form: PropTypes.shape({}), | |||||
| label: PropTypes.string, | |||||
| placeholder: PropTypes.string, | |||||
| disabled: PropTypes.bool, | |||||
| centerText: PropTypes.bool, | |||||
| autoFocus: PropTypes.bool, | |||||
| preventAllExceptNumbers: PropTypes.bool, | |||||
| }; | |||||
| export default TextField; |