diff --git a/package-lock.json b/package-lock.json index d3e1fbbe..cfb0ff55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20967,6 +20967,11 @@ "@emotion/core": "^10.0.22" } }, + "react-onclickoutside": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.11.2.tgz", + "integrity": "sha512-640486eSwU/t5iD6yeTlefma8dI3bxPXD93hM9JGKyYITAd0P1JFkkcDeyHZRqNpY/fv1YW0Fad9BXr44OY8wQ==" + }, "react-overlays": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.1.1.tgz", diff --git a/package.json b/package.json index 726fc25e..ac50454f 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "react-dom": "16.14.0", "react-helmet": "6.1.0", "react-loading-skeleton": "2.2.0", + "react-onclickoutside": "^6.11.2", "react-redux": "7.2.3", "react-responsive": "8.2.0", "react-router": "5.2.0", diff --git a/src/_style.scss b/src/_style.scss index 00b0ffe1..e2c648b6 100644 --- a/src/_style.scss +++ b/src/_style.scss @@ -425,13 +425,11 @@ select.form-control { .medium-screen-svg { fill: $primary; overflow: inherit; - position: absolute; } .large-screen-svg { fill: $primary; overflow: hidden; - position: absolute; } .small-screen-header { @@ -518,7 +516,7 @@ select.form-control { } .large-heading { - margin-left: 7px; + margin-left: 8px; color: $white; max-width: 24rem; line-height: 78px; @@ -541,9 +539,9 @@ select.form-control { } .logo { - width: 4.44rem; - margin-top: 1.5rem; - margin-left: 1.5rem; + width: 4.5rem; + padding-top: 2rem; + padding-left: 25px; } .username-suggestion { @@ -612,3 +610,33 @@ select.form-control { color: $gray-700; text-decoration: none; } + +.dropdown-item:active { + background-color: #F2F0EF; +} + +.dropdown-container { + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15), 0px 2px 8px rgba(0, 0, 0, 0.15); + border-radius: 4px; + max-height: 200px; + font-size: 1rem; + font-weight: normal; + line-height: 1.25rem; + overflow-y: scroll; + position: absolute; + background-color: #fff; + width: 464px; +} + +@media (max-width: 464px) { + .dropdown-container { + width: auto; + left: 0; + right: 0; + position: relative; + } +} + +.-mt-4 { + margin-top: -1.5rem; +} diff --git a/src/common-components/FormGroup.jsx b/src/common-components/FormGroup.jsx index 764fa62a..172e0652 100644 --- a/src/common-components/FormGroup.jsx +++ b/src/common-components/FormGroup.jsx @@ -13,6 +13,9 @@ const FormGroup = (props) => { setHasFocus(true); if (props.handleFocus) { props.handleFocus(e); } }; + const handleClick = (e) => { + if (props.handleClick) { props.handleClick(e); } + }; const handleOnBlur = (e) => { setHasFocus(false); if (props.handleBlur) { props.handleBlur(e); } @@ -24,10 +27,12 @@ const FormGroup = (props) => { as={props.as} type={props.type} + autoComplete={props.autoComplete} name={props.name} value={props.value} onFocus={handleFocus} onBlur={handleOnBlur} + onClick={handleClick} onChange={props.handleChange} controlClassName={props.borderClass} @@ -66,9 +71,11 @@ FormGroup.defaultProps = { borderClass: '', suggestedTopLevelDomain: '', suggestedServiceLevelDomain: '', + autoComplete: null, handleBlur: null, handleChange: () => {}, handleFocus: null, + handleClick: null, helpText: [], options: null, trailingElement: null, @@ -82,10 +89,12 @@ FormGroup.propTypes = { borderClass: PropTypes.string, suggestedTopLevelDomain: PropTypes.string, suggestedServiceLevelDomain: PropTypes.string, + autoComplete: PropTypes.string, floatingLabel: PropTypes.string.isRequired, handleBlur: PropTypes.func, handleChange: PropTypes.func, handleFocus: PropTypes.func, + handleClick: PropTypes.func, helpText: PropTypes.arrayOf(PropTypes.string), name: PropTypes.string.isRequired, options: PropTypes.func, diff --git a/src/register/CountryDropdown.jsx b/src/register/CountryDropdown.jsx new file mode 100644 index 00000000..d5988d3d --- /dev/null +++ b/src/register/CountryDropdown.jsx @@ -0,0 +1,172 @@ +import React from 'react'; +import { Icon } from '@edx/paragon'; +import { ExpandMore, ExpandLess } from '@edx/paragon/icons'; +import onClickOutside from 'react-onclickoutside'; +import PropTypes from 'prop-types'; +import { FormGroup } from '../common-components'; +import { FORM_SUBMISSION_ERROR } from './data/constants'; + +class CountryDropdown extends React.Component { + constructor(props) { + super(props); + this.state = { + displayValue: '', + icon: ExpandMore, + errorMessage: '', + showFieldError: true, + }; + + this.handleFocus = this.handleFocus.bind(this); + this.handleOnBlur = this.handleOnBlur.bind(this); + } + + 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[this.props.valueKey]; + let displayValue = opt[this.props.displayValueKey]; + if (displayValue.length > 30) { + displayValue = displayValue.substring(0, 30).concat('...'); + } + + return ( + + ); + }); + } + + setValue(value) { + if (this.props.value === value) { + return; + } + + if (this.props.handleChange) { + this.props.handleChange(value); + } + + const opt = this.props.options.find((o) => o[this.props.valueKey] === value); + if (opt && opt[this.props.displayValueKey] !== this.state.displayValue) { + this.setState({ displayValue: opt[this.props.displayValueKey] }); + } + } + + setDisplayValue(value) { + const normalized = value.toLowerCase(); + const opt = this.props.options.find((o) => o[this.props.displayValueKey].toLowerCase() === normalized); + if (opt) { + this.setValue(opt[this.props.valueKey]); + this.setState({ displayValue: opt[this.props.displayValueKey] }); + } else { + this.setValue(null); + this.setState({ displayValue: value }); + } + } + + handleClick = () => { + if (!this.props.value) { + const dropDownItems = this.getItems(); + this.setState({ + dropDownItems, icon: ExpandLess, errorMessage: '', showFieldError: false, + }); + } + } + + handleOnChange = (e) => { + const findstr = e.target.value; + + if (findstr.length > 0) { + const filteredItems = this.getItems(findstr); + this.setState({ dropDownItems: filteredItems, icon: ExpandLess, errorMessage: '' }); + } else { + this.setState({ dropDownItems: '', icon: ExpandMore, errorMessage: this.props.errorMessage }); + } + + this.setDisplayValue(e.target.value); + } + + handleClickOutside = () => { + if (this.state.dropDownItems?.length > 0) { + const msg = this.state.displayValue === '' ? this.props.errorMessage : ''; + this.setState(() => ({ + icon: ExpandMore, + dropDownItems: '', + errorMessage: msg, + })); + } + } + + handleFocus(e) { + if (this.props.handleFocus) { this.props.handleFocus(e); } + } + + handleOnBlur(e) { + if (this.props.handleBlur) { this.props.handleBlur(e); } + } + + handleItemClick(e) { + this.setValue(e.target.value); + this.setState({ dropDownItems: '', icon: ExpandMore }); + } + + render() { + return ( +