2 changed files with 296 additions and 0 deletions
@ -0,0 +1,295 @@ |
|||
/* eslint-disable react/no-multi-comp */ |
|||
|
|||
import React, { createContext } from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import { Transition, config } from 'react-spring' |
|||
import { Box, Flex } from 'rebass' |
|||
import ArrowLeft from 'components/Icon/ArrowLeft' |
|||
import ArrowRight from 'components/Icon/ArrowRight' |
|||
import { Button } from 'components/UI' |
|||
|
|||
export const WizardContext = createContext() |
|||
|
|||
class Steps extends React.Component { |
|||
render() { |
|||
return ( |
|||
<WizardContext.Consumer> |
|||
{({ steps, wizardState }) => { |
|||
const fade = { |
|||
from: { opacity: 0, height: '100%' }, |
|||
enter: { opacity: 1 }, |
|||
leave: { opacity: 0, height: 0 } |
|||
} |
|||
const animationProps = fade |
|||
|
|||
return ( |
|||
<Transition |
|||
initial={null} |
|||
items={steps[wizardState.currentStep]} |
|||
keys={item => item.key} |
|||
config={config.gentle} |
|||
{...animationProps} |
|||
> |
|||
{item => props => <Box style={props}>{item}</Box>} |
|||
</Transition> |
|||
) |
|||
}} |
|||
</WizardContext.Consumer> |
|||
) |
|||
} |
|||
} |
|||
|
|||
class NextButton extends React.Component { |
|||
static propTypes = { |
|||
children: PropTypes.node, |
|||
navigateTo: PropTypes.number |
|||
} |
|||
|
|||
static defaultProps = { |
|||
children: 'Next', |
|||
navigateTo: null |
|||
} |
|||
|
|||
render() { |
|||
const { children, navigateTo } = this.props |
|||
|
|||
return ( |
|||
<WizardContext.Consumer> |
|||
{({ wizardApi, wizardState }) => { |
|||
const { formState } = wizardState |
|||
return ( |
|||
<Button |
|||
type="submit" |
|||
onClick={() => { |
|||
if (navigateTo !== null) { |
|||
wizardApi.navigateTo(navigateTo) |
|||
} else { |
|||
wizardApi.next() |
|||
} |
|||
}} |
|||
disabled={ |
|||
wizardState.isSubmitting || |
|||
(formState && formState.invalid && formState.submits > 0) |
|||
} |
|||
processing={wizardState.isSubmitting} |
|||
> |
|||
<Flex> |
|||
<Box mr={1}>{children}</Box> |
|||
<Box> |
|||
<ArrowRight /> |
|||
</Box> |
|||
</Flex> |
|||
</Button> |
|||
) |
|||
}} |
|||
</WizardContext.Consumer> |
|||
) |
|||
} |
|||
} |
|||
|
|||
class BackButton extends React.Component { |
|||
static propTypes = { |
|||
children: PropTypes.node, |
|||
navigateTo: PropTypes.number |
|||
} |
|||
|
|||
static defaultProps = { |
|||
children: 'Back', |
|||
navigateTo: null |
|||
} |
|||
|
|||
render() { |
|||
const { children, navigateTo } = this.props |
|||
return ( |
|||
<WizardContext.Consumer> |
|||
{({ wizardApi, wizardState }) => { |
|||
if (!wizardState.currentStep) { |
|||
return null |
|||
} |
|||
return ( |
|||
<Button |
|||
type="button" |
|||
variant="secondary" |
|||
onClick={() => { |
|||
if (navigateTo !== null) { |
|||
wizardApi.navigateTo(navigateTo) |
|||
} else { |
|||
wizardApi.previous() |
|||
} |
|||
}} |
|||
disabled={wizardState.isSubmitting} |
|||
> |
|||
<Flex> |
|||
<Box> |
|||
<ArrowLeft /> |
|||
</Box> |
|||
<Box ml={1}>{children}</Box> |
|||
</Flex> |
|||
</Button> |
|||
) |
|||
}} |
|||
</WizardContext.Consumer> |
|||
) |
|||
} |
|||
} |
|||
|
|||
class Debug extends React.Component { |
|||
render() { |
|||
return ( |
|||
<WizardContext.Consumer> |
|||
{({ wizardApi }) => <pre>{JSON.stringify(wizardApi.getState(), null, 2)}</pre>} |
|||
</WizardContext.Consumer> |
|||
) |
|||
} |
|||
} |
|||
|
|||
class Step extends React.Component { |
|||
static propTypes = { |
|||
component: PropTypes.func.isRequired |
|||
} |
|||
|
|||
render() { |
|||
const { component, ...rest } = this.props |
|||
return ( |
|||
<WizardContext.Consumer> |
|||
{({ wizardApi, wizardState }) => |
|||
React.createElement(component, { ...rest, wizardApi, wizardState }) |
|||
} |
|||
</WizardContext.Consumer> |
|||
) |
|||
} |
|||
} |
|||
|
|||
class Wizard extends React.Component { |
|||
/* eslint react/no-unused-state: 0 */ |
|||
static Steps = Steps |
|||
static Step = Step |
|||
static NextButton = NextButton |
|||
static BackButton = BackButton |
|||
static Debug = Debug |
|||
|
|||
static propTypes = { |
|||
children: PropTypes.node, |
|||
steps: PropTypes.array |
|||
} |
|||
|
|||
constructor(props) { |
|||
super(props) |
|||
const { steps } = this.props |
|||
this.state = { |
|||
currentItem: steps && steps[0] && steps[0].key, |
|||
currentStep: 0, |
|||
direction: 'next', |
|||
isSubmitting: false, |
|||
formState: {} |
|||
} |
|||
} |
|||
|
|||
previousStep = () => { |
|||
const { steps } = this.props |
|||
const { currentStep } = this.state |
|||
const nextStep = Math.max(currentStep - 1, 0) |
|||
if (currentStep !== nextStep) { |
|||
this.setState({ |
|||
currentItem: steps[nextStep].key, |
|||
currentStep: nextStep, |
|||
direction: 'previous', |
|||
formState: {} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
nextStep = () => { |
|||
const { steps } = this.props |
|||
const { currentStep } = this.state |
|||
const nextStep = Math.min(currentStep + 1, steps.length - 1) |
|||
if (currentStep !== nextStep) { |
|||
this.setState({ |
|||
currentItem: steps[nextStep].key, |
|||
currentStep: nextStep, |
|||
direction: 'next', |
|||
formState: {} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
handlePrevious = () => { |
|||
this.previousStep() |
|||
} |
|||
|
|||
handleNext = () => { |
|||
if (this.formApi) { |
|||
this.setState({ isSubmitting: true }) |
|||
this.formApi.submitForm() |
|||
} else { |
|||
this.nextStep() |
|||
} |
|||
} |
|||
|
|||
navigateTo = stepId => { |
|||
const { steps } = this.props |
|||
this.setState({ |
|||
currentItem: steps[stepId].key, |
|||
currentStep: stepId, |
|||
direction: 'previous', |
|||
formState: {} |
|||
}) |
|||
} |
|||
|
|||
onSubmit = () => { |
|||
this.setState({ isSubmitting: false }) |
|||
this.nextStep() |
|||
} |
|||
|
|||
onSubmitFailure = () => { |
|||
this.setState({ isSubmitting: false }) |
|||
} |
|||
|
|||
setFormApi = formApi => { |
|||
this.formApi = formApi |
|||
this.setFormState(formApi.getState()) |
|||
} |
|||
|
|||
setFormState = formState => { |
|||
this.setState({ formState }) |
|||
} |
|||
|
|||
getApi = formApi => { |
|||
this.setFormApi(formApi) |
|||
} |
|||
|
|||
onChange = (formState, stepId) => { |
|||
const { currentItem } = this.state |
|||
if (currentItem && stepId === currentItem) { |
|||
this.setFormState(formState) |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
const { children, steps } = this.props |
|||
|
|||
return ( |
|||
<WizardContext.Provider |
|||
value={{ |
|||
wizardApi: { |
|||
previous: this.handlePrevious, |
|||
next: this.handleNext, |
|||
navigateTo: this.navigateTo, |
|||
preSubmit: this.preSubmit, |
|||
onSubmit: this.onSubmit, |
|||
onSubmitFailure: this.onSubmitFailure, |
|||
onChange: this.onChange, |
|||
getApi: this.getApi, |
|||
getState: () => this.state |
|||
}, |
|||
wizardState: this.state, |
|||
steps |
|||
}} |
|||
> |
|||
{children} |
|||
</WizardContext.Provider> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default Wizard |
Loading…
Reference in new issue