mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-05-17 16:21:41 -04: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
src
|
@ -12,9 +12,17 @@ const FormikInput = ({
|
||||||
className,
|
className,
|
||||||
error,
|
error,
|
||||||
validationClassName,
|
validationClassName,
|
||||||
|
wrapperClassName,
|
||||||
...props
|
...props
|
||||||
}) => (
|
}) => (
|
||||||
<div className="col-sm-9 row row-with-tooltip">
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'col-sm-9',
|
||||||
|
'row',
|
||||||
|
'row-with-tooltip',
|
||||||
|
wrapperClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Field
|
<Field
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'input',
|
'input',
|
||||||
|
@ -36,7 +44,8 @@ FormikInput.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
validationClassName: PropTypes.string
|
validationClassName: PropTypes.string,
|
||||||
|
wrapperClassName: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = FormikInput;
|
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;
|
margin-bottom: .75rem;
|
||||||
line-height: 1.7rem;
|
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 React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ require('./join-flow-step.scss');
|
||||||
|
|
||||||
const JoinFlowStep = ({
|
const JoinFlowStep = ({
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
description,
|
description,
|
||||||
headerImgSrc,
|
headerImgSrc,
|
||||||
nextButton,
|
nextButton,
|
||||||
|
@ -23,7 +25,12 @@ const JoinFlowStep = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<ModalInnerContent className="join-flow-inner-content">
|
<ModalInnerContent
|
||||||
|
className={classNames(
|
||||||
|
'join-flow-inner-content',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{title && (
|
{title && (
|
||||||
<ModalTitle
|
<ModalTitle
|
||||||
className="join-flow-title"
|
className="join-flow-title"
|
||||||
|
@ -47,6 +54,7 @@ const JoinFlowStep = ({
|
||||||
|
|
||||||
JoinFlowStep.propTypes = {
|
JoinFlowStep.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
className: PropTypes.string,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
headerImgSrc: PropTypes.string,
|
headerImgSrc: PropTypes.string,
|
||||||
nextButton: PropTypes.node,
|
nextButton: PropTypes.node,
|
||||||
|
|
|
@ -64,6 +64,28 @@
|
||||||
margin: 0 auto;
|
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 {
|
.join-flow-next-button-arrow {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
|
|
|
@ -9,6 +9,7 @@ const intlShape = require('../../lib/intl.jsx').intlShape;
|
||||||
const Progression = require('../progression/progression.jsx');
|
const Progression = require('../progression/progression.jsx');
|
||||||
const UsernameStep = require('./username-step.jsx');
|
const UsernameStep = require('./username-step.jsx');
|
||||||
const BirthDateStep = require('./birthdate-step.jsx');
|
const BirthDateStep = require('./birthdate-step.jsx');
|
||||||
|
const GenderStep = require('./gender-step.jsx');
|
||||||
const EmailStep = require('./email-step.jsx');
|
const EmailStep = require('./email-step.jsx');
|
||||||
const WelcomeStep = require('./welcome-step.jsx');
|
const WelcomeStep = require('./welcome-step.jsx');
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ class JoinFlow extends React.Component {
|
||||||
<Progression step={this.state.step}>
|
<Progression step={this.state.step}>
|
||||||
<UsernameStep onNextStep={this.handleAdvanceStep} />
|
<UsernameStep onNextStep={this.handleAdvanceStep} />
|
||||||
<BirthDateStep onNextStep={this.handleAdvanceStep} />
|
<BirthDateStep onNextStep={this.handleAdvanceStep} />
|
||||||
|
<GenderStep onNextStep={this.handleAdvanceStep} />
|
||||||
<EmailStep onNextStep={this.handleAdvanceStep} />
|
<EmailStep onNextStep={this.handleAdvanceStep} />
|
||||||
<WelcomeStep
|
<WelcomeStep
|
||||||
email={this.state.formData.email}
|
email={this.state.formData.email}
|
||||||
|
|
|
@ -153,6 +153,10 @@
|
||||||
"registration.confirmYourEmail": "Confirm Your Email",
|
"registration.confirmYourEmail": "Confirm Your Email",
|
||||||
"registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:",
|
"registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:",
|
||||||
"registration.createUsername": "Create a username",
|
"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.emailStepTitle": "What's your email?",
|
||||||
"registration.emailStepDescription": "We need this to finish creating your account. Scratch will always keep this information private.",
|
"registration.emailStepDescription": "We need this to finish creating your account. Scratch will always keep this information private.",
|
||||||
"registration.goToClass": "Go to Class",
|
"registration.goToClass": "Go to Class",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue