diff --git a/src/components/import-error/import-error.css b/src/components/import-error/import-error.css new file mode 100644 index 0000000000000000000000000000000000000000..4ffac015f190e63252f9b6a242a949173de43bef --- /dev/null +++ b/src/components/import-error/import-error.css @@ -0,0 +1,61 @@ +/* + * NOTE: the copious use of `important` is needed to overwrite + * the default tooltip styling, and is required by the 3rd party + * library being used, `react-tooltip` + */ + +@import "../../css/colors.css"; + +.import-error { + background-color: $data-primary !important; + border: 1px solid hsla(0, 0%, 0%, .1) !important; + border-radius: .25rem !important; + box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important; + padding: .75rem 1rem !important; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-size: 1rem !important; + line-height: 1.25rem !important; + z-index: 100 !important; +} + +.import-error:after { + content: ""; + border-top: 1px solid hsla(0, 0%, 0%, .1) !important; + border-left: 0 !important; + border-bottom: 0 !important; + border-right: 1px solid hsla(0, 0%, 0%, .1) !important; + border-radius: .25rem; + background-color: $data-primary !important; + height: 1rem !important; + width: 1rem !important; +} + +.show, +.show:before, +.show:after { + opacity: 1 !important; +} + +.left:after { + margin-top: -.5rem !important; + right: -.5rem !important; + transform: rotate(45deg) !important; +} + +.right:after { + margin-top: -.5rem !important; + left: -.5rem !important; + transform: rotate(-135deg) !important; +} + +.top:after { + margin-right: -.5rem !important; + bottom: -.5rem !important; + transform: rotate(135deg) !important; +} + +.bottom:after { + margin-left: -.5rem !important; + top: -.5rem !important; + transform: rotate(-45deg) !important; +} diff --git a/src/components/import-error/import-error.jsx b/src/components/import-error/import-error.jsx new file mode 100644 index 0000000000000000000000000000000000000000..5147138a63813cbfd29600550e0feace4208e037 --- /dev/null +++ b/src/components/import-error/import-error.jsx @@ -0,0 +1,111 @@ +import bindAll from 'lodash.bindall'; +import classNames from 'classnames'; +import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl'; +import PropTypes from 'prop-types'; +import React from 'react'; +import ReactTooltip from 'react-tooltip'; + +import styles from './import-error.css'; + +class ImportErrorContent extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'setHide', + 'setShow', + 'getContent' + ]); + this.state = { + isShowing: true + }; + } + setShow () { + // needed to set the opacity to 1, since the default is .9 on show + this.setState({isShowing: true}); + } + setHide () { + this.setState({isShowing: false}); + } + getContent () { + return ( + <p>{this.props.errorMessage}</p> + ); + } + render () { + return ( + <ReactTooltip + afterHide={this.setHide} + afterShow={this.setShow} + className={classNames( + styles.importError, + this.props.className, + { + [styles.show]: (this.state.isShowing), + [styles.left]: (this.props.place === 'left'), + [styles.right]: (this.props.place === 'right'), + [styles.top]: (this.props.place === 'top'), + [styles.bottom]: (this.props.place === 'bottom') + } + )} + getContent={this.getContent} + id={this.props.tooltipId} + /> + ); + } +} + +ImportErrorContent.propTypes = { + className: PropTypes.string, + intl: intlShape, + place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + tooltipId: PropTypes.string.isRequired, + errorMessage: PropTypes.string.isRequired +}; + +ImportErrorContent.defaultProps = { + place: 'bottom' +}; + +const ImportError = injectIntl(ImportErrorContent); + +const ImportErrorTooltip = props => ( + <div className={props.className}> + <div + data-delay-hide={props.delayHide} + data-delay-show={props.delayShow} + data-effect="solid" + data-for={props.tooltipId} + data-place={props.place} + data-tip="tooltip" + > + {props.children} + </div> + <ImportError + className={props.tooltipClassName} + place={props.place} + tooltipId={props.tooltipId} + errorMessage={props.errorMessage} + /> + </div> +); + +ImportErrorTooltip.propTypes = { + children: PropTypes.node.isRequired, + className: PropTypes.string, + delayHide: PropTypes.number, + delayShow: PropTypes.number, + place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + tooltipClassName: PropTypes.string, + tooltipId: PropTypes.string.isRequired, + errorMessage: PropTypes.string.isRequired +}; + +ImportErrorTooltip.defaultProps = { + delayHide: 0, + delayShow: 0 +}; + +export { + ImportError as ImportErrorComponent, + ImportErrorTooltip +}; diff --git a/src/components/import-input/import-input.jsx b/src/components/import-input/import-input.jsx new file mode 100644 index 0000000000000000000000000000000000000000..997de86af2d12465dd9b225904260bb4fd04819f --- /dev/null +++ b/src/components/import-input/import-input.jsx @@ -0,0 +1,57 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import classNames from 'classnames'; + +import {ImportErrorTooltip} from '../import-error/import-error.jsx'; + + +class ImportInput extends React.Component { + render () { + let input = null; + if (!this.props.hasValidationError) { + input = ( + <input + autoFocus + className={this.props.okClassName} + placeholder={this.props.placeholder} + onChange={this.props.onChange} + onKeyPress={this.props.onKeyPress} + value={this.props.inputValue} + /> + ); + } else { + input = ( + <ImportErrorTooltip + place="bottom" + tooltipId="import-input-error" + errorMessage={this.props.errorMessage} + className={this.props.errorDivClassName} + > + <input + autoFocus + className={this.props.badClassName} + placeholder={this.props.placeholder} + onChange={this.props.onChange} + onKeyPress={this.props.onKeyPress} + value={this.props.inputValue} + /> + </ImportErrorTooltip> + ); + } + return input; + } +} + +ImportInput.propTypes = { + onChange: PropTypes.func.isRequired, + onKeyPress: PropTypes.func.isRequired, + placeholder: PropTypes.string, + errorMessage: PropTypes.string.isRequired, + hasValidationError: PropTypes.bool.isRequired, + inputValue: PropTypes.string.isRequired, + okClassName: PropTypes.string, + badClassName: PropTypes.string, + errorDivClassName: PropTypes.string +}; + +export default ImportInput; diff --git a/src/components/import-modal/import-modal.css b/src/components/import-modal/import-modal.css index 80a2de5762266145b961887a684b9b8d9d38e572..e025f8137866338ca8c0d2c4147d328057704c01 100644 --- a/src/components/import-modal/import-modal.css +++ b/src/components/import-modal/import-modal.css @@ -113,8 +113,12 @@ $sides: 20rem; text-align: right; display: flex; justify-content: center; - /*border: 1px solid $import-primary; - border-radius: 0.25rem;*/ +} + +.input-row div.error-div { + margin: 0; + width: 100%; + padding: 0; } .input-row input { @@ -130,12 +134,18 @@ $sides: 20rem; color: rgba(87,94,117,0.5); } -.input-row input:focus { +.input-row input.ok-input:focus { outline: none; border: 1px solid $motion-primary; border-radius: 0.25rem } +.input-row input.bad-input:focus { + outline: none; + border: 1px solid $data-primary; + border-radius: 0.25rem +} + .input-row button { padding: 0.5rem 2rem; font-weight: bold; diff --git a/src/components/import-modal/import-modal.jsx b/src/components/import-modal/import-modal.jsx index 9a9ad872db5e6c94aef6be36c122cbb1af9a495b..9df475f12652bae94555228d18afa6ffa5095dd0 100644 --- a/src/components/import-modal/import-modal.jsx +++ b/src/components/import-modal/import-modal.jsx @@ -6,6 +6,7 @@ import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-int import classNames from 'classnames'; import CloseButton from '../close-button/close-button.jsx'; +import ImportInput from '../import-input/import-input.jsx'; import styles from './import-modal.css'; @@ -64,12 +65,16 @@ const ImportModal = ({intl, ...props}) => ( </p> <Box className={styles.inputRow}> - <input - autoFocus - className={styles.input} + <ImportInput placeholder={props.placeholder} onChange={props.onChange} onKeyPress={props.onKeyPress} + errorMessage={props.errorMessage} + hasValidationError={props.hasValidationError} + inputValue={props.inputValue} + okClassName={styles.okInput} + badClassName={styles.badInput} + errorDivClassName={styles.errorDiv} /> <button className={styles.okButton} @@ -127,6 +132,8 @@ ImportModal.propTypes = { onKeyPress: PropTypes.func.isRequired, onViewProject: PropTypes.func.isRequired, placeholder: PropTypes.string, + hasValidationError: PropTypes.bool.isRequired, + errorMessage: PropTypes.string.isRequired }; export default injectIntl(ImportModal); diff --git a/src/containers/import-modal.jsx b/src/containers/import-modal.jsx index 9d808b6e3b178e9a0a57c60e1e08adba4fbdea4e..7351c103747146a29e8d6a93124e3ba69be6c050 100644 --- a/src/containers/import-modal.jsx +++ b/src/containers/import-modal.jsx @@ -23,7 +23,9 @@ class ImportModal extends React.Component { ]); this.state = { - inputValue: '' + inputValue: '', + hasValidationError: false, + errorMessage: '' }; } handleKeyPress (event) { @@ -41,12 +43,10 @@ class ImportModal extends React.Component { document.body.removeChild(projectLink); this.handleCancel(); } else { - console.log("Error") + this.setState({ + hasValidationError: true, + errorMessage: `Uh oh, that link doesn't look quite right.`}); } - - // window. - // this.setState({previewing: true}); - // this.props.onViewProject(); } handleChange (e) { this.setState({inputValue: e.target.value}); @@ -82,6 +82,9 @@ class ImportModal extends React.Component { placeholder='scratch.mit.edu/projects/123456789' onKeyPress={this.handleKeyPress} onChange={this.handleChange} + errorMessage={this.state.errorMessage} + hasValidationError={this.state.hasValidationError} + inputValue={this.state.inputValue} /> : <BrowserModalComponent onBack={this.handleGoBack}