Clear field error on focus (#298)

Clear field related error messages for all forms on focus in event

VAN-505
This commit is contained in:
Mubbshar Anwar
2021-06-03 18:21:18 +05:00
committed by Waheed Ahmed
parent 92163ac7dc
commit e1546acb14
12 changed files with 94 additions and 217 deletions

View File

@@ -1,166 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import {
Form,
Input,
ValidationFormGroup,
} from '@edx/paragon';
const AuthnCustomValidationFormGroup = (props) => {
const {
onBlur, onChange, onClick, onFocus,
} = props;
const [showHelpText, setShowHelpText] = useState(false);
const [showLabelText, setShowLabelText] = useState(false);
// handler code that need to be invoked via input
const onClickHandler = (e, clickCb) => {
setShowHelpText(true);
setShowLabelText(true);
if (clickCb) {
clickCb(e);
}
};
const onBlurHandler = (e, blurCb) => {
setShowHelpText(false);
setShowLabelText(false);
if (blurCb) {
blurCb(e);
}
};
const onChangeHandler = (e, changeCb) => {
if (changeCb) {
changeCb(e);
}
};
const onFocusHandler = (e, focusCb) => {
if (focusCb) {
focusCb(e);
}
};
const onOptionalHandler = (e, clickCb) => { clickCb(e); };
const showLabel = () => {
let className;
if (props.optionalFieldCheckbox || (!showLabelText && (props.value !== '' || props.type === 'select'))) {
className = 'sr-only';
} else if (showLabelText) {
className = 'pt-10 h6';
} else {
className = 'pt-10 focus-out';
}
return (
<Form.Label htmlFor={props.for} className={className}>{props.label}</Form.Label>
);
};
const showOptional = () => {
const additionalField = props.optionalFieldCheckbox ? (
<p role="presentation" id="additionalFields" className="mb-1 small" onClick={(e) => onOptionalHandler(e, onClick)}>
{props.checkboxMessage}
</p>
) : <span />;
return additionalField;
};
const inputProps = {
name: props.name,
id: props.for,
type: props.type,
value: props.value,
className: props.inputFieldStyle,
'aria-invalid': props.ariaInvalid,
autoComplete: 'on',
};
inputProps.onChange = (e) => onChangeHandler(e, onChange);
inputProps.onClick = (e) => onClickHandler(e, onClick);
inputProps.onBlur = (e) => onBlurHandler(e, onBlur);
inputProps.onFocus = (e) => onFocusHandler(e, onFocus);
if (props.type === 'select') {
inputProps.options = props.selectOptions;
inputProps.className = props.value === '' ? `${props.inputFieldStyle} text-muted` : props.inputFieldStyle;
}
if (props.type === 'checkbox') {
inputProps.checked = props.isChecked;
}
const validationGroupProps = {
for: props.for,
};
if (!props.optionalFieldCheckbox) {
validationGroupProps.invalid = props.invalid;
validationGroupProps.invalidMessage = props.invalidMessage;
validationGroupProps.helpText = showHelpText ? props.helpText : '';
} else {
validationGroupProps.className = props.optionalFieldCheckbox ? 'custom-control pt-10 mb-0' : '';
}
if (props.className) {
validationGroupProps.className = props.className;
}
return (
<ValidationFormGroup
{...validationGroupProps}
>
{showLabel()}
<Input
{...inputProps}
required
/>
{showOptional()}
</ValidationFormGroup>
);
};
AuthnCustomValidationFormGroup.defaultProps = {
name: '',
for: '',
label: '',
optionalFieldCheckbox: false,
type: '',
value: '',
invalid: false,
ariaInvalid: false,
invalidMessage: '',
inputFieldStyle: '',
helpText: '',
className: '',
onClick: null,
onBlur: null,
onChange: null,
onFocus: null,
isChecked: false,
checkboxMessage: '',
selectOptions: null,
};
AuthnCustomValidationFormGroup.propTypes = {
name: PropTypes.string,
for: PropTypes.string,
label: PropTypes.string,
type: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]),
invalid: PropTypes.bool,
ariaInvalid: PropTypes.bool,
invalidMessage: PropTypes.string,
helpText: PropTypes.string,
className: PropTypes.string,
inputFieldStyle: PropTypes.string,
isChecked: PropTypes.bool,
optionalFieldCheckbox: PropTypes.bool,
onClick: PropTypes.func,
onBlur: PropTypes.func,
onChange: PropTypes.func,
onFocus: PropTypes.func,
checkboxMessage: PropTypes.string,
selectOptions: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string,
value: PropTypes.string,
})),
};
export default AuthnCustomValidationFormGroup;

View File

@@ -21,6 +21,13 @@ const PasswordField = (props) => {
setShowTooltip(props.showRequirements && false);
};
const handleFocus = (e) => {
if (props.handleFocus) {
props.handleFocus(e);
}
setTimeout(() => setShowTooltip(props.showRequirements && true), 150);
};
const HideButton = (
<IconButton icon={faEyeSlash} onClick={setHiddenTrue} alt={formatMessage(messages['hide.password'])} aria-label={formatMessage(messages['hide.password'])} />
);
@@ -54,7 +61,7 @@ const PasswordField = (props) => {
type={isPasswordHidden ? 'password' : 'text'}
name={props.name}
value={props.value}
onFocus={() => setTimeout(() => setShowTooltip(props.showRequirements && true), 150)}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={props.handleChange}
controlClassName={props.borderClass}
@@ -73,6 +80,7 @@ PasswordField.defaultProps = {
borderClass: '',
errorMessage: '',
handleBlur: null,
handleFocus: null,
handleChange: () => {},
showRequirements: true,
};
@@ -82,6 +90,7 @@ PasswordField.propTypes = {
errorMessage: PropTypes.string,
floatingLabel: PropTypes.string.isRequired,
handleBlur: PropTypes.func,
handleFocus: PropTypes.func,
handleChange: PropTypes.func,
intl: intlShape.isRequired,
name: PropTypes.string.isRequired,

View File

@@ -7,7 +7,6 @@ export { default as SocialAuthProviders } from './SocialAuthProviders';
export { default as ThirdPartyAuthAlert } from './ThirdPartyAuthAlert';
export { default as InstitutionLogistration } from './InstitutionLogistration';
export { RenderInstitutionButton } from './InstitutionLogistration';
export { default as AuthnValidationFormGroup } from './AuthnValidationFormGroup';
export { default as APIFailureMessage } from './APIFailureMessage';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

View File

@@ -1,46 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import AuthnCustomValidationFormGroup from '../AuthnValidationFormGroup';
describe('AuthnCustomValidationFormGroup', () => {
let props = {
label: 'Email Label',
for: 'email',
name: 'email',
type: 'email',
value: '',
helpText: 'Email field help text',
};
it('should show label in place of placeholder when field is empty', () => {
const validationFormGroup = mount(<AuthnCustomValidationFormGroup {...props} />);
expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label pt-10 focus-out');
});
it('should show label on top of field when field is focused in', () => {
const validationFormGroup = mount(<AuthnCustomValidationFormGroup {...props} />);
validationFormGroup.find('input').simulate('focus');
expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label pt-10 h6');
});
it('should keep label hidden for checkbox field', () => {
props = {
...props,
type: 'checkbox',
optionalFieldCheckbox: true,
};
const validationFormGroup = mount(<AuthnCustomValidationFormGroup {...props} />);
expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label sr-only');
});
it('should keep label hidden when input field is not empty', () => {
props = {
...props,
value: 'test',
};
const validationFormGroup = mount(<AuthnCustomValidationFormGroup {...props} />);
expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label sr-only');
});
});

View File

@@ -13,6 +13,7 @@ describe('FormGroup', () => {
helpText: ['Email field help text'],
name: 'email',
value: '',
handleFocus: jest.fn(),
};
it('should show help text on field focus', () => {
@@ -33,6 +34,7 @@ describe('PasswordField', () => {
floatingLabel: 'Password',
name: 'password',
value: 'password123',
handleFocus: jest.fn(),
};
});