Skip to content
Snippets Groups Projects
Commit 28666d1c authored by Karishma Chadha's avatar Karishma Chadha
Browse files

Improvements to error state: input outline shows with or without focus, and...

Improvements to error state: input outline shows with or without focus, and added new error div which shows if there's an error, but goes away if the user starts changing what they had submitted in the input. Removing a lot of code surrounding the tooltip which has now become unnecessary.
parent e4756b15
No related branches found
No related tags found
No related merge requests found
/*
* 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;
}
import bindAll from 'lodash.bindall';
import classNames from 'classnames';
import {defineMessages, injectIntl, FormattedMessage} from 'react-intl';
import PropTypes from 'prop-types';
import React from 'react';
import ReactTooltip from 'react-tooltip';
import styles from './import-error.css';
// TODO store different error messages depending on the situation (?) and
// needs to use intl lib for localization support
// TODO error tooltip should be always visible in the error state,
// instead of popping up hen hovering over the input
const messages = defineMessages({
invalidLink: {
id: 'gui.importError.invalidLink',
defaultMessage: 'Uh oh, that link doesn\'t look quite right.',
description: 'Invalid link error message'
}
});
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 () {
const messageId = this.props.errorMessage;
return (
<p>
<FormattedMessage
{...messages[`${messageId}`]}
/>
</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,
errorMessage: PropTypes.string.isRequired,
place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
tooltipId: 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}
errorMessage={props.errorMessage}
place={props.place}
tooltipId={props.tooltipId}
/>
</div>
);
ImportErrorTooltip.propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
delayHide: PropTypes.number,
delayShow: PropTypes.number,
errorMessage: PropTypes.string.isRequired,
place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
tooltipClassName: PropTypes.string,
tooltipId: PropTypes.string.isRequired
};
ImportErrorTooltip.defaultProps = {
delayHide: 0,
delayShow: 0
};
export {
ImportError as ImportErrorComponent,
ImportErrorTooltip
};
import PropTypes from 'prop-types';
import React from 'react';
import {ImportErrorTooltip} from '../import-error/import-error.jsx';
// TODO error tooltip needs to go around both the input and the button
// error tooltip should also be always visible
class ImportInput extends React.Component {
render () {
let input = null;
if (this.props.hasValidationError) {
input = (
<ImportErrorTooltip
className={this.props.errorDivClassName}
errorMessage={this.props.errorMessage}
place="bottom"
tooltipId="import-input-error"
>
<input
autoFocus
className={this.props.badClassName}
placeholder={this.props.placeholder}
value={this.props.inputValue}
onChange={this.props.onChange}
onKeyPress={this.props.onKeyPress}
/>
</ImportErrorTooltip>
);
} else {
input = (
<input
autoFocus
className={this.props.okClassName}
placeholder={this.props.placeholder}
value={this.props.inputValue}
onChange={this.props.onChange}
onKeyPress={this.props.onKeyPress}
/>
);
}
return input;
}
}
ImportInput.propTypes = {
badClassName: PropTypes.string,
errorDivClassName: PropTypes.string,
errorMessage: PropTypes.string.isRequired,
hasValidationError: PropTypes.bool.isRequired,
inputValue: PropTypes.string.isRequired,
okClassName: PropTypes.string,
onChange: PropTypes.func.isRequired,
onKeyPress: PropTypes.func.isRequired,
placeholder: PropTypes.string
};
export default ImportInput;
......@@ -98,12 +98,6 @@ $sides: 20rem;
justify-content: center;
}
.input-row div.error-div {
margin: 0;
width: 100%;
padding: 0;
}
.input-row input {
width: 100%;
padding: 0 1rem;
......@@ -117,13 +111,13 @@ $sides: 20rem;
color: rgba(87,94,117,0.5);
}
.input-row input.ok-input:focus {
.input-row input.ok-input {
outline: none;
border: 1px solid $motion-primary;
border-radius: 0.25rem
}
.input-row input.bad-input:focus {
.input-row input.bad-input {
outline: none;
border: 1px solid $data-primary;
border-radius: 0.25rem
......@@ -136,6 +130,7 @@ $sides: 20rem;
cursor: pointer;
border: 1px solid $import-primary;
border-radius: 0.25rem;
outline: none;
}
.input-row button.ok-button {
......@@ -143,6 +138,26 @@ $sides: 20rem;
color: white;
}
.empty-row {
margin: 0;
}
.error-row {
margin: 1.5rem 0;
text-align: center;
display: flex;
justify-content: center;
}
.error-row div {
background: $data-primary;
opacity: 0.8;
color: white;
padding: 0.5rem 1rem;
font-size: .5rem;
border: 1px solid $data-primary;
border-radius: 0.25rem;
}
/* Confirmation buttons at the bottom of the modal */
.button-row {
margin: 1.5rem 0;
......
......@@ -6,7 +6,6 @@ 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';
......@@ -21,6 +20,11 @@ const messages = defineMessages({
'Enter a link to one of your shared Scratch projects. Changes made in this 3.0 Preview will not be saved.',
description: 'Import project message',
id: 'gui.importInfo.message'
},
invalidFormatError: {
id: 'gui.importInfo.invalidFormatError',
defaultMessage: 'Uh oh, that project link or id doesn\'t look quite right.',
description: 'Invalid project link or id message'
}
});
......@@ -65,16 +69,12 @@ const ImportModal = ({intl, ...props}) => (
<p>
{intl.formatMessage({...messages.formDescription})}
</p>
<Box className={styles.inputRow}>
<ImportInput
badClassName={styles.badInput}
errorDivClassName={styles.errorDiv}
errorMessage={props.errorMessage}
hasValidationError={props.hasValidationError}
inputValue={props.inputValue}
okClassName={styles.okInput}
<input
autoFocus
className={props.hasValidationError ? styles.badInput : styles.okInput}
placeholder={props.placeholder}
value={props.inputValue}
onChange={props.onChange}
onKeyPress={props.onKeyPress}
/>
......@@ -90,6 +90,17 @@ const ImportModal = ({intl, ...props}) => (
/>
</button>
</Box>
<Box className={props.hasValidationError ? styles.errorRow : styles.emptyRow}>
{props.hasValidationError ?
<div className={styles.importErrorDiv}>
<p>
{/* intl.formatMessage({...messages.invalidLink})*/}
<FormattedMessage
{...messages[`${props.errorMessage}`]}
/>
</p>
</div> : null}
</Box>
<Box className={styles.buttonRow}>
<button
className={styles.noButton}
......
......@@ -43,11 +43,11 @@ class ImportModal extends React.Component {
// TODO handle error messages and error states
this.setState({
hasValidationError: true,
errorMessage: `invalidLink`});
errorMessage: `invalidFormatError`});
}
}
handleChange (e) {
this.setState({inputValue: e.target.value});
this.setState({inputValue: e.target.value, hasValidationError: false});
}
validate (input) {
const urlMatches = input.match(/^(https:\/\/)?scratch\.mit\.edu\/projects\/(\d+)(\/?|(\/#editor)?)$/);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment