All files / app/components/Button Button.tsx

91.66% Statements 22/24
93.75% Branches 15/16
100% Functions 3/3
90.9% Lines 20/22

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 97 98 99 100 101 102 103 104 105 106 107 108 109 11044x 44x 44x           44x 44x 44x   44x   170x                                                                                                   1361x 1361x 1361x 1361x   107x 107x   107x 107x         107x     1361x                                                   44x 2652x  
import { Button as MuiButton, ButtonProps, useTheme } from "@mui/material";
import classNames from "classnames";
import Sentry from "platform/sentry";
import React, {
  ElementType,
  ForwardedRef,
  forwardRef,
  MouseEventHandler,
} from "react";
import { useIsMounted, useSafeState } from "utils/hooks";
import makeStyles from "utils/makeStyles";
 
import CircularProgress from "../CircularProgress";
 
const useStyles = makeStyles((theme) => ({
  contained: {
    borderRadius: theme.shape.borderRadius,
    boxShadow: "0px 0px 5px rgba(0, 0, 0, 0.25)",
  },
  loading: {
    height: theme.typography.button.fontSize,
  },
  spinner: {
    position: "absolute",
    bottom: 0,
    left: 0,
    right: 0,
    top: 0,
    margin: "auto",
  },
  root: {
    minHeight: `calc(calc(${theme.typography.button.lineHeight} * ${
      theme.typography.button.fontSize
    }) + ${theme.typography.pxToRem(12)})`, //from padding
  },
}));
 
type ButtonClasses = {
  [key: string]: string;
};
 
//type generics required to allow component prop
//see https://github.com/mui-org/material-ui/issues/15827
// @TODO This is fixed now, can refactor: https://github.com/mui/material-ui/pull/35924
export type AppButtonProps<D extends ElementType = "button"> =
  ButtonProps<D> & {
    loading?: boolean;
    onClick?: MouseEventHandler<HTMLButtonElement>; // Dynamic type for different component types
    classes?: Partial<ButtonClasses>; // Use the flexible ButtonClasses type here
  };
 
function InternalButton<D extends ElementType = "button">(
  {
    children,
    disabled,
    className,
    loading,
    onClick,
    variant = "contained",
    color = "primary",
    ...otherProps
  }: AppButtonProps<D>,
  ref: ForwardedRef<any> // eslint-disable-line
) {
  const isMounted = useIsMounted();
  const [waiting, setWaiting] = useSafeState(isMounted, false);
  const classes = useStyles();
  const theme = useTheme();
  async function asyncOnClick(event: React.MouseEvent<HTMLButtonElement>) {
    try {
      setWaiting(true);
 
      if (onClick) {
        await onClick(event);
      }
    } catch (e) {
      Sentry.captureException(e);
    } finally {
      setWaiting(false);
    }
  }
  Iif (variant !== "contained" && color !== "primary") {
    throw new Error("Only contained buttons should have color.");
  }
  return (
    <MuiButton
      {...otherProps}
      ref={ref}
      onClick={onClick && asyncOnClick}
      disabled={disabled ? true : loading || waiting}
      className={classNames(classes.root, className, {
        [classes.contained]: variant === "contained",
      })}
      variant={variant}
      color={variant === "contained" ? color : undefined}
    >
      {(loading || waiting) && (
        <CircularProgress
          size={theme.typography.button.fontSize}
          className={classes.spinner}
        />
      )}
      {children}
    </MuiButton>
  );
}
 
const Button = forwardRef(InternalButton);
export default Button;