All files / components/Stepper index.tsx

100% Statements 14/14
87.5% Branches 14/16
100% Functions 4/4
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96                                                                    1x   1x 6x 6x   6x 17x 6x   11x 2x   9x     17x                           6x             17x   17x                                            
import React, { type JSX } from 'react';
 
import { Stepper as MuiStepper, Step, StepLabel, SxProps, Theme, Typography, useTheme } from '@mui/material';
 
import { useStrings } from '../../strings';
 
export type StepperStep = {
  label: string;
  /**
   * Marks the step as optional. Whether an optional step shows as completed comes from `completed` here rather than
   * from `completedSteps` or the active step, since the user may have skipped it.
   */
  optional?: { completed: boolean };
};
 
export type StepperProps = {
  activeStep: number;
  className?: string;
  /**
   * Indexes of the steps that are completed. When provided, only these steps show as completed, so a step stays
   * completed when the user goes back to an earlier one. The active step always uses the active styling, even if it is
   * in this list. When omitted, every step before the active one shows as completed.
   */
  completedSteps?: number[];
  /**
   * Styles applied to each step label, after the defaults. MUI styles active and completed labels with a more specific
   * selector, so label text overrides should target `.MuiStepLabel-label.Mui-active` and
   * `.MuiStepLabel-label.Mui-completed` as well as `.MuiStepLabel-label`.
   */
  stepLabelSx?: SxProps<Theme>;
  steps: (string | StepperStep)[];
  sx?: SxProps<Theme>;
};
 
const LABEL_SELECTOR = '.MuiStepLabel-label, .MuiStepLabel-label.Mui-active, .MuiStepLabel-label.Mui-completed';
 
const Stepper = ({ activeStep, className, completedSteps, stepLabelSx, steps, sx }: StepperProps): JSX.Element => {
  const theme = useTheme();
  const strings = useStrings();
 
  const isCompleted = (step: StepperStep, index: number): boolean | undefined => {
    if (index === activeStep) {
      return false;
    }
    if (step.optional) {
      return step.optional.completed;
    }
    return completedSteps ? completedSteps.includes(index) : undefined;
  };
 
  const getStepLabelStyles = (index: number): SxProps<Theme> => [
    {
      '.MuiStepIcon-root': { fill: theme.palette.TwClrBgTertiary as string },
      '.MuiStepIcon-root.Mui-active': { fill: theme.palette.TwClrIcnSecondary as string },
      '.MuiStepIcon-root.Mui-completed': { fill: theme.palette.TwClrTxtBrand as string },
      [LABEL_SELECTOR]: {
        fontSize: '14px',
        fontWeight: 400,
        color: (index === activeStep ? theme.palette.TwClrTxt : theme.palette.TwClrTxtSecondary) as string,
      },
    },
    ...(Array.isArray(stepLabelSx) ? stepLabelSx : [stepLabelSx]),
  ];
 
  return (
    <MuiStepper
      activeStep={activeStep}
      className={className}
      sx={[{ margin: theme.spacing(1, 0, 3) }, ...(Array.isArray(sx) ? sx : [sx])]}
    >
      {steps.map((stepOrLabel, index) => {
        const step = typeof stepOrLabel === 'string' ? { label: stepOrLabel } : stepOrLabel;
 
        return (
          <Step key={index} completed={isCompleted(step, index)}>
            <StepLabel
              optional={
                step.optional && (
                  <Typography variant='caption' color={theme.palette.TwClrTxtSecondary as string}>
                    {strings.OPTIONAL}
                  </Typography>
                )
              }
              sx={getStepLabelStyles(index)}
            >
              {step.label}
            </StepLabel>
          </Step>
        );
      })}
    </MuiStepper>
  );
};
 
export default Stepper;