import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useState } from 'react' import classNames from 'classnames' import { useRouter } from 'next/router' const MultiStepFormContext = createContext() export function MultiStepForm ({ children, initial, steps }) { const [stepIndex, setStepIndex] = useState(0) const [formState, setFormState] = useState({}) const router = useRouter() useEffect(() => { // initial state might not be available on first render so we sync changes if (initial) setFormState(initial) }, [initial]) useEffect(() => { const idx = Math.max(0, steps.indexOf(router.query.step)) setStepIndex(idx) router.replace({ pathname: router.pathname, query: { type: router.query.type, step: steps[idx] } }, null, { shallow: true }) }, [router.query.step, steps]) const next = useCallback(() => { const idx = Math.min(stepIndex + 1, steps.length - 1) router.push( { pathname: router.pathname, query: { type: router.query.type, step: steps[idx] } }, null, { shallow: true } ) }, [stepIndex, steps, router]) const prev = useCallback(() => router.back(), [router]) const updateFormState = useCallback((id, state) => { setFormState(formState => { return id ? { ...formState, [id]: state } : state }) }, []) const value = useMemo( () => ({ stepIndex, steps, next, prev, formState, updateFormState }), [stepIndex, steps, next, prev, formState, updateFormState]) return ( {children[stepIndex]} ) } function Progress () { const steps = useSteps() const maxSteps = useMaxSteps() const stepIndex = useStepIndex() const style = (index) => { switch (index) { case 0: return maxSteps === 2 ? { marginLeft: '-13px', marginRight: '-15px' } : { marginLeft: '-5px', marginRight: '-13px' } case 1: return { marginLeft: '-13px', marginRight: '-15px' } default: return {} } } return (
{ steps.map((label, i) => { const last = i === steps.length - 1 return ( = i} /> {!last && = i + 1} />} ) }) }
) } function ProgressNumber ({ number, label, active }) { return (
{label}
) } const NUMBER_SVG_WIDTH = 24 const NUMBER_SVG_HEIGHT = 24 function NumberSVG ({ number, active }) { const width = NUMBER_SVG_WIDTH const height = NUMBER_SVG_HEIGHT const Wrapper = ({ children }) => (
{children}
) const Circle = () => { const circleProps = { fill: active ? 'var(--bs-info)' : 'var(--bs-body-bg)', stroke: active ? 'var(--bs-info)' : 'var(--theme-grey)' } return ( ) } const Number = () => { const svgProps = { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', // we scale the number down and render it in the center of the circle width: 0.5 * width, height: 0.5 * height, style: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)' } } const numberColor = active ? 'var(--bs-white)' : 'var(--theme-grey)' // svgs are from https://remixicon.com/icon/number-1 etc. switch (number) { case 1: return ( ) case 2: return ( ) case 3: return ( ) default: return null } } return ( ) } function ProgressLine ({ style, active }) { const svgStyle = { display: 'block', position: 'relative', top: `${NUMBER_SVG_HEIGHT / 2}px` } return (
) } function useSteps () { const { steps } = useContext(MultiStepFormContext) return steps } export function useStepIndex () { const { stepIndex } = useContext(MultiStepFormContext) return stepIndex } export function useMaxSteps () { const steps = useSteps() return steps.length } export function useStep () { const stepIndex = useStepIndex() const steps = useSteps() return steps[stepIndex] } export function useNext () { const { next } = useContext(MultiStepFormContext) return next } export function usePrev () { const { prev } = useContext(MultiStepFormContext) return prev } export function useFormState (id) { const { formState, updateFormState } = useContext(MultiStepFormContext) const setFormState = useCallback(state => updateFormState(id, state), [id, updateFormState]) return useMemo( () => [ id ? formState[id] : formState, setFormState ], [formState, id, setFormState]) }