frontend/app/multistep/common/CommonMultistepContainer.tsx (163 lines of code) (raw):

import React from "react"; import { Helmet } from "react-helmet"; import { Button, Grid, Step, StepLabel, Stepper, Tooltip, makeStyles, useMediaQuery, useTheme, } from "@material-ui/core"; import StepContent from "./StepContent"; import { CheckCircle, Cancel } from "@material-ui/icons"; import { useGuardianStyles } from "~/misc/utils"; interface CommonMultistepContainerProps { activeStep: number; title: string; id: string | undefined; setActiveStep: (newValue: number | ((prevValue: number) => number)) => void; steps: string[]; creationInProgress: boolean | undefined; creationFailed: string | undefined; canComplete: () => boolean | 0 | undefined; createClicked: () => Promise<void>; createButtonLabel?: string; isObituary?: boolean; obituaryName?: string | null; } const CommonMultistepContainer: React.FC<CommonMultistepContainerProps> = ( props ) => { const [skipped, setSkipped] = React.useState(new Set<number>()); const { activeStep, setActiveStep, steps, creationInProgress, creationFailed, canComplete, createClicked, isObituary, obituaryName, } = props; const classes = useGuardianStyles(); const isStepSkipped = (step: number) => { return skipped.has(step); }; const handleNext = () => { let newSkipped = skipped; if (isStepSkipped(activeStep)) { newSkipped = new Set(newSkipped.values()); newSkipped.delete(activeStep); } setActiveStep((prevActiveStep) => prevActiveStep + 1); setSkipped(newSkipped); }; const handleBack = () => { setActiveStep((prevActiveStep) => prevActiveStep - 1); }; const theme = useTheme(); const isSmallScreen = useMediaQuery(theme.breakpoints.down("xs")); return ( <div id={props.id}> <Helmet> <title>{props.title}</title> </Helmet> <Stepper activeStep={activeStep} className={classes.stepper}> {steps.map((label, index) => { const stepProps: { completed?: boolean } = {}; const labelProps: { optional?: React.ReactNode } = {}; if (isStepSkipped(index)) { stepProps.completed = false; } return ( <Step key={label} {...stepProps}> <StepLabel {...labelProps}>{label}</StepLabel> </Step> ); })} </Stepper> <StepContent activeStep={activeStep} className={classes.stepContainer}> {props.children} <hr /> <Grid justifyContent="space-between" container wrap="wrap"> {activeStep < 5 && ( <> <Grid item> <Button size={isSmallScreen ? "small" : "medium"} variant="outlined" disabled={ activeStep == 0 || creationInProgress || activeStep > 4 } onClick={handleBack} > Back </Button> </Grid> <Grid item> {activeStep >= steps.length - 1 ? ( <Tooltip title={ canComplete() ? "Go ahead and create" : "You need to supply some more information, check above for details" } > <span> {/* the <span> wrapper is required to get mouseover events when the button is in a "disabled" state*/} <Button className={classes.createButtonHover} size={isSmallScreen ? "small" : "medium"} variant="contained" color={ !canComplete() || creationInProgress || creationFailed !== undefined || activeStep > 4 || (isObituary && !obituaryName) ? "default" : "primary" } disabled={ !canComplete() || creationInProgress || creationFailed !== undefined || activeStep > 4 || (isObituary && !obituaryName) } endIcon={ !canComplete() || creationInProgress || creationFailed !== undefined || activeStep > 4 || (isObituary && !obituaryName) ? ( <Cancel /> ) : ( <CheckCircle /> ) } onClick={createClicked} > {props.createButtonLabel ?? "Create"} </Button> </span> </Tooltip> ) : ( <Button variant="contained" onClick={handleNext} size={isSmallScreen ? "small" : "medium"} > Next </Button> )} </Grid> </> )} </Grid> </StepContent> </div> ); }; export default CommonMultistepContainer;