All files / src/features/Merchants/MerchantSidePanel/Modals/Signature SignatureCanvas.tsx

79.48% Statements 31/39
75% Branches 12/16
72.72% Functions 8/11
81.25% Lines 26/32

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 110 111 112 113 114 115 116 117                          3x 4x 4x 4x 4x   4x 1x 1x 1x       4x   4x             4x         4x 4x       4x     4x   4x                                                                       3x 4x   3x 3x 3x 3x     4x         3x           3x                    
import useDrawOnCanvas from "@features/Merchants/MerchantSidePanel/hooks/useDrawOnCanvas";
import { Box } from "@mui/material";
import GiveButton from "@shared/Button/GiveButton";
import GiveText from "@shared/Text/GiveText";
import { styled, useAppTheme } from "@theme/v2/Provider";
import { useEffect, useState } from "react";
 
type Props = {
  height: number;
  width: number;
  setDataURL: React.Dispatch<React.SetStateAction<string>>;
};
 
const SignatureCanvas = ({ height, width, setDataURL }: Props) => {
  const { palette } = useAppTheme();
  const [isCleared, setIsCleared] = useState(false);
  const containerHeight = height + 4; // 4 is for border left + border right width
  const containerWidth = width + 4; // 4 is for border left + border right width
 
  const onSave = (canvas: HTMLCanvasElement) => {
    const dataURL = canvas.toDataURL("image/png");
    setDataURL(dataURL || "");
    Iif (isCleared) setIsCleared(false);
  };
 
  const { canvasRef, onDrawStart, onDraw, onDrawEnd, isDrawing, clear } =
    useDrawOnCanvas({ height, width, onSave });
 
  const clearCanvas = () => {
    setIsCleared(true);
    clear();
    setDataURL("");
  };
 
  // While drawing, if the user hovers over the clear button, we want to stop drawing so that the drawing doesn't overlap with the button
  const handleMouseEnter = () => {
    if (!isDrawing) return;
    onDrawEnd();
  };
 
  useEffect(() => {
    const t = setTimeout(() => {
      setIsCleared(false);
    }, 50);
 
    return () => clearTimeout(t);
  });
 
  const isEmpty = isCanvasBlank(canvasRef.current);
 
  return (
    <Container
      sx={{
        height: containerHeight,
        width: containerWidth,
        border: `2px solid ${
          isDrawing ? palette.primitive?.blue[100] : palette.border?.primary
        }`,
      }}
    >
      <canvas
        onMouseDown={onDrawStart}
        onMouseUp={onDrawEnd}
        onMouseMove={onDraw}
        onTouchStart={onDrawStart}
        onTouchEnd={onDrawEnd}
        onTouchMove={onDraw}
        ref={canvasRef}
        data-testid="signature-canvas"
      />
      {!isDrawing && isEmpty && (
        <BeginText variant="bodyL">Click to begin</BeginText>
      )}
      {(isDrawing || !isEmpty) && (
        <ClearButton
          variant="filled"
          color="light"
          label="Clear"
          onClick={clearCanvas}
          onMouseEnter={handleMouseEnter}
        />
      )}
    </Container>
  );
};
 
const isCanvasBlank = (canvas?: HTMLCanvasElement | null) => {
  if (!canvas) return true;
 
  const blank = document.createElement("canvas");
  blank.width = canvas.width;
  blank.height = canvas.height;
  return canvas.toDataURL() === blank.toDataURL();
};
 
const Container = styled(Box)(() => ({
  position: "relative",
  borderRadius: "12px",
}));
 
const ClearButton = styled(GiveButton)(() => ({
  position: "absolute",
  right: "16px",
  bottom: "16px",
}));
 
const BeginText = styled(GiveText)(({ theme }) => ({
  color: theme.palette.text.secondary,
  position: "absolute",
  top: "50%",
  left: "50%",
  transform: "translate(-50%, -50%)",
  pointerEvents: "none",
}));
 
export default SignatureCanvas;