From 606aaf1f78ab4b491f1302c38fb6ab0e2294589b Mon Sep 17 00:00:00 2001 From: Tom Kirkpatrick Date: Sat, 17 Nov 2018 19:11:54 +0100 Subject: [PATCH] feat(ui): add Wizard component --- app/components/UI/Wizard.js | 295 ++++++++++++++++++++++++++++++++++++ app/components/UI/index.js | 1 + 2 files changed, 296 insertions(+) create mode 100644 app/components/UI/Wizard.js diff --git a/app/components/UI/Wizard.js b/app/components/UI/Wizard.js new file mode 100644 index 00000000..2e00f692 --- /dev/null +++ b/app/components/UI/Wizard.js @@ -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 ( + + {({ steps, wizardState }) => { + const fade = { + from: { opacity: 0, height: '100%' }, + enter: { opacity: 1 }, + leave: { opacity: 0, height: 0 } + } + const animationProps = fade + + return ( + item.key} + config={config.gentle} + {...animationProps} + > + {item => props => {item}} + + ) + }} + + ) + } +} + +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 ( + + {({ wizardApi, wizardState }) => { + const { formState } = wizardState + return ( + + ) + }} + + ) + } +} + +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 ( + + {({ wizardApi, wizardState }) => { + if (!wizardState.currentStep) { + return null + } + return ( + + ) + }} + + ) + } +} + +class Debug extends React.Component { + render() { + return ( + + {({ wizardApi }) =>
{JSON.stringify(wizardApi.getState(), null, 2)}
} +
+ ) + } +} + +class Step extends React.Component { + static propTypes = { + component: PropTypes.func.isRequired + } + + render() { + const { component, ...rest } = this.props + return ( + + {({ wizardApi, wizardState }) => + React.createElement(component, { ...rest, wizardApi, wizardState }) + } + + ) + } +} + +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 ( + this.state + }, + wizardState: this.state, + steps + }} + > + {children} + + ) + } +} + +export default Wizard diff --git a/app/components/UI/index.js b/app/components/UI/index.js index f626e5a8..1ef8e3b2 100644 --- a/app/components/UI/index.js +++ b/app/components/UI/index.js @@ -40,3 +40,4 @@ export Titlebar from './Titlebar' export Toggle from './Toggle' export Truncate from './Truncate' export Value from './Value' +export Wizard from './Wizard'