mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-16 16:19:48 -05:00
Merge pull request #3178 from benjiwheeler/join-flow-gender-step
Join flow gender step
This commit is contained in:
commit
76473c97fe
9 changed files with 374 additions and 3 deletions
|
@ -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;
|
||||
|
|
107
src/components/formik-forms/formik-radio-button.jsx
Normal file
107
src/components/formik-forms/formik-radio-button.jsx
Normal 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;
|
61
src/components/formik-forms/formik-radio-button.scss
Normal file
61
src/components/formik-forms/formik-radio-button.scss
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
148
src/components/join-flow/gender-step.jsx
Normal file
148
src/components/join-flow/gender-step.jsx
Normal 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);
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue