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:
committed by
Waheed Ahmed
parent
92163ac7dc
commit
e1546acb14
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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(),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user