Merge pull request #3178 from benjiwheeler/join-flow-gender-step

Join flow gender step
This commit is contained in:
Benjamin Wheeler 2019-08-03 00:15:53 -04:00 committed by GitHub
commit 76473c97fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 374 additions and 3 deletions

View file

@ -12,9 +12,17 @@ const FormikInput = ({
className,
error,
validationClassName,
wrapperClassName,
...props
}) => (
<div className="col-sm-9 row row-with-tooltip">
<div
className={classNames(
'col-sm-9',
'row',
'row-with-tooltip',
wrapperClassName
)}
>
<Field
className={classNames(
'input',
@ -36,7 +44,8 @@ FormikInput.propTypes = {
className: PropTypes.string,
error: PropTypes.string,
type: PropTypes.string,
validationClassName: PropTypes.string
validationClassName: PropTypes.string,
wrapperClassName: PropTypes.string
};
module.exports = FormikInput;

View file

@ -0,0 +1,107 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
import {Field} from 'formik';
const FormikInput = require('./formik-input.jsx');
require('./formik-radio-button.scss');
require('../forms/row.scss');
const FormikRadioButtonSubComponent = ({
buttonValue,
children,
className,
field,
label,
labelClassName,
...props
}) => (
<React.Fragment>
<input
checked={buttonValue === field.value}
className={classNames(
'formik-radio-button',
className
)}
name={field.name}
type="radio"
value={buttonValue}
onBlur={field.onBlur} /* eslint-disable-line react/jsx-handler-names */
onChange={field.onChange} /* eslint-disable-line react/jsx-handler-names */
{...props}
/>
{label && (
<label
className={classNames(
'formik-radio-label',
labelClassName
)}
htmlFor={buttonValue}
>
{label}
</label>
)}
{children}
</React.Fragment>
);
FormikRadioButtonSubComponent.propTypes = {
buttonValue: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string,
field: PropTypes.shape({
name: PropTypes.string,
onBlur: PropTypes.function,
onChange: PropTypes.function,
value: PropTypes.string
}),
label: PropTypes.string,
labelClassName: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
const FormikRadioButton = ({
buttonValue,
className,
isCustomInput,
label,
name,
onSetCustom,
...props
}) => (
<Field
buttonValue={buttonValue}
className={className}
component={FormikRadioButtonSubComponent}
label={label}
labelClassName={isCustomInput ? 'formik-radio-label-other' : ''}
name={name}
{...props}
>
{isCustomInput && (
<FormikInput
className="formik-radio-input"
name="custom"
wrapperClassName="formik-radio-input-wrapper"
/* eslint-disable react/jsx-no-bind */
onChange={event => onSetCustom(event.target.value)}
onFocus={event => onSetCustom(event.target.value)}
/* eslint-enable react/jsx-no-bind */
/>
)}
</Field>
);
FormikRadioButton.propTypes = {
buttonValue: PropTypes.string,
className: PropTypes.string,
isCustomInput: PropTypes.bool,
label: PropTypes.string,
name: PropTypes.string,
onSetCustom: PropTypes.func,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
module.exports = FormikRadioButton;

View file

@ -0,0 +1,61 @@
@import "../../colors";
.formik-radio-label {
font-weight: 300;
margin-left: 1rem;
}
input[type="radio"].formik-radio-button {
margin-top: 1px;
border: 1px solid $box-shadow-light-gray;
border-radius: 50%;
width: 1rem;
min-width: 1rem; /* necessary to prevent width from being too small in 'other' case */
height: 1rem;
appearance: none;
background-color: $ui-white;
&:checked,
&:focus {
outline: none;
}
&:checked {
transition: all .25s ease;
box-shadow: 0 0 0 2px $ui-blue-25percent;
border: 1px solid $ui-blue;
background-color: $ui-white;
&:after {
display: block;
transform: translate(.125rem, .125rem);
border-radius: 50%;
background-color: $ui-blue;
width: .625rem;
height: .625rem;
content: "";
}
}
}
input.formik-radio-input, .formik-radio-input input {
height: 2.1875rem;
width: 10.25rem;
margin-bottom: 0;
border-radius: .5rem;
background-color: $ui-white;
&:focus {
box-shadow: 0 0 0 .25rem $ui-blue-25percent;
}
}
.formik-radio-input-wrapper {
margin-left: auto;
margin-right: .25rem;
}
.formik-radio-label-other {
max-width: 7rem;
margin-right: .25rem;
}

View file

@ -30,3 +30,13 @@
margin-bottom: .75rem;
line-height: 1.7rem;
}
.row-inline {
display: flex;
}
/* override margin-bottom so placing a label next to a radio button does not
mess up vertical alignment */
.row-inline label {
margin-bottom: 0;
}

View file

@ -0,0 +1,148 @@
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const React = require('react');
const PropTypes = require('prop-types');
import {Formik} from 'formik';
const {injectIntl, intlShape} = require('react-intl');
const FormikRadioButton = require('../../components/formik-forms/formik-radio-button.jsx');
const JoinFlowStep = require('./join-flow-step.jsx');
require('./join-flow-steps.scss');
const GenderOption = ({
label,
onSetFieldValue,
selectedValue,
value
}) => (
<div
className={classNames(
'col-sm-9',
'row',
'row-inline',
'gender-radio-row',
{'gender-radio-row-selected': (selectedValue === value)}
)}
/* eslint-disable react/jsx-no-bind */
onClick={() => onSetFieldValue('gender', value, false)}
/* eslint-enable react/jsx-no-bind */
>
<FormikRadioButton
buttonValue={value}
className={classNames(
'join-flow-radio'
)}
label={label}
name="gender"
/>
</div>
);
GenderOption.propTypes = {
label: PropTypes.string,
onSetFieldValue: PropTypes.func,
selectedValue: PropTypes.string,
value: PropTypes.string
};
class GenderStep extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleValidSubmit'
]);
}
handleValidSubmit (formData, formikBag) {
formikBag.setSubmitting(false);
if (!formData.gender || formData.gender === 'null') {
formData.gender = 'Prefer not to say';
}
delete formData.custom;
this.props.onNextStep(formData);
}
render () {
return (
<Formik
initialValues={{
gender: 'null',
custom: ''
}}
onSubmit={this.handleValidSubmit}
>
{props => {
const {
handleSubmit,
isSubmitting,
setFieldValue,
setValues,
values
} = props;
return (
<JoinFlowStep
className="join-flow-gender-step"
description={this.props.intl.formatMessage({id: 'registration.genderStepDescription'})}
title={this.props.intl.formatMessage({id: 'registration.genderStepTitle'})}
waiting={isSubmitting}
onSubmit={handleSubmit}
>
<GenderOption
label={this.props.intl.formatMessage({id: 'general.female'})}
selectedValue={values.gender}
value="Female"
onSetFieldValue={setFieldValue}
/>
<GenderOption
label={this.props.intl.formatMessage({id: 'general.male'})}
selectedValue={values.gender}
value="Male"
onSetFieldValue={setFieldValue}
/>
<div
className={classNames(
'col-sm-9',
'row',
'row-inline',
'gender-radio-row',
{'gender-radio-row-selected': (values.gender === values.custom)}
)}
/* eslint-disable react/jsx-no-bind */
onClick={() => setFieldValue('gender', values.custom, false)}
/* eslint-enable react/jsx-no-bind */
>
<FormikRadioButton
isCustomInput
buttonValue={values.custom}
className={classNames(
'join-flow-radio'
)}
label={this.props.intl.formatMessage({id: 'registration.genderOptionAnother'})}
name="gender"
/* eslint-disable react/jsx-no-bind */
onSetCustom={newCustomVal => setValues({
gender: newCustomVal,
custom: newCustomVal
})}
/* eslint-enable react/jsx-no-bind */
/>
</div>
<GenderOption
label={this.props.intl.formatMessage({id: 'registration.genderOptionPreferNotToSay'})}
selectedValue={values.gender}
value="Prefer not to say"
onSetFieldValue={setFieldValue}
/>
</JoinFlowStep>
);
}}
</Formik>
);
}
}
GenderStep.propTypes = {
intl: intlShape,
onNextStep: PropTypes.func
};
module.exports = injectIntl(GenderStep);

View file

@ -1,3 +1,4 @@
const classNames = require('classnames');
const React = require('react');
const PropTypes = require('prop-types');
@ -9,6 +10,7 @@ require('./join-flow-step.scss');
const JoinFlowStep = ({
children,
className,
description,
headerImgSrc,
nextButton,
@ -23,7 +25,12 @@ const JoinFlowStep = ({
</div>
)}
<div>
<ModalInnerContent className="join-flow-inner-content">
<ModalInnerContent
className={classNames(
'join-flow-inner-content',
className
)}
>
{title && (
<ModalTitle
className="join-flow-title"
@ -47,6 +54,7 @@ const JoinFlowStep = ({
JoinFlowStep.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
description: PropTypes.string,
headerImgSrc: PropTypes.string,
nextButton: PropTypes.node,

View file

@ -64,6 +64,28 @@
margin: 0 auto;
}
.join-flow-gender-step {
height: 27.375rem;
padding-top: 3rem;
}
.gender-radio-row {
transition: all .125s ease;
width: 20.875rem;
height: 2.85rem;
background-color: $ui-gray;
border-radius: .5rem;
margin-bottom: 0.375rem;
padding-left: 0.8125rem;
display: flex;
align-items: center;
}
.gender-radio-row-selected {
transition: all .125s ease;
background-color: $ui-blue-25percent;
}
.join-flow-next-button-arrow {
width: 2rem;
height: 2rem;

View file

@ -9,6 +9,7 @@ const intlShape = require('../../lib/intl.jsx').intlShape;
const Progression = require('../progression/progression.jsx');
const UsernameStep = require('./username-step.jsx');
const BirthDateStep = require('./birthdate-step.jsx');
const GenderStep = require('./gender-step.jsx');
const EmailStep = require('./email-step.jsx');
const WelcomeStep = require('./welcome-step.jsx');
@ -40,6 +41,7 @@ class JoinFlow extends React.Component {
<Progression step={this.state.step}>
<UsernameStep onNextStep={this.handleAdvanceStep} />
<BirthDateStep onNextStep={this.handleAdvanceStep} />
<GenderStep onNextStep={this.handleAdvanceStep} />
<EmailStep onNextStep={this.handleAdvanceStep} />
<WelcomeStep
email={this.state.formData.email}

View file

@ -153,6 +153,10 @@
"registration.confirmYourEmail": "Confirm Your Email",
"registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:",
"registration.createUsername": "Create a username",
"registration.genderStepTitle": "What's your gender?",
"registration.genderStepDescription": "Scratch welcomes people of all genders. We will always keep this information private.",
"registration.genderOptionAnother": "Another gender:",
"registration.genderOptionPreferNotToSay": "Prefer not to say",
"registration.emailStepTitle": "What's your email?",
"registration.emailStepDescription": "We need this to finish creating your account. Scratch will always keep this information private.",
"registration.goToClass": "Go to Class",