mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 04:50:54 +00:00
108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
import { Eraser } from "lucide-react";
|
|
|
|
import { Button } from "@edr/ui-common";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface ContractSignaturePadProps {
|
|
onChange: (dataUrl: string | null) => void;
|
|
className?: string;
|
|
}
|
|
|
|
export function ContractSignaturePad({
|
|
onChange,
|
|
className,
|
|
}: ContractSignaturePadProps) {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
const drawing = useRef(false);
|
|
const [empty, setEmpty] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return;
|
|
const dpr = window.devicePixelRatio || 1;
|
|
const w = canvas.offsetWidth;
|
|
const h = canvas.offsetHeight;
|
|
canvas.width = w * dpr;
|
|
canvas.height = h * dpr;
|
|
ctx.scale(dpr, dpr);
|
|
ctx.strokeStyle = "#111";
|
|
ctx.lineWidth = 2;
|
|
ctx.lineCap = "round";
|
|
}, []);
|
|
|
|
const getPos = (e: React.MouseEvent | React.TouchEvent) => {
|
|
const canvas = canvasRef.current!;
|
|
const rect = canvas.getBoundingClientRect();
|
|
if ("touches" in e) {
|
|
const t = e.touches[0];
|
|
return { x: t.clientX - rect.left, y: t.clientY - rect.top };
|
|
}
|
|
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
};
|
|
|
|
const start = (e: React.MouseEvent | React.TouchEvent) => {
|
|
drawing.current = true;
|
|
const ctx = canvasRef.current?.getContext("2d");
|
|
const { x, y } = getPos(e);
|
|
ctx?.beginPath();
|
|
ctx?.moveTo(x, y);
|
|
};
|
|
|
|
const move = (e: React.MouseEvent | React.TouchEvent) => {
|
|
if (!drawing.current) return;
|
|
const ctx = canvasRef.current?.getContext("2d");
|
|
const { x, y } = getPos(e);
|
|
ctx?.lineTo(x, y);
|
|
ctx?.stroke();
|
|
setEmpty(false);
|
|
onChange(canvasRef.current?.toDataURL("image/png") ?? null);
|
|
};
|
|
|
|
const end = () => {
|
|
drawing.current = false;
|
|
};
|
|
|
|
const clear = () => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return;
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
setEmpty(true);
|
|
onChange(null);
|
|
};
|
|
|
|
return (
|
|
<div className={cn("space-y-2", className)}>
|
|
<div className="overflow-hidden rounded-lg border-2 border-dashed border-border bg-white">
|
|
<canvas
|
|
ref={canvasRef}
|
|
className="h-36 w-full touch-none cursor-crosshair"
|
|
onMouseDown={start}
|
|
onMouseMove={move}
|
|
onMouseUp={end}
|
|
onMouseLeave={end}
|
|
onTouchStart={start}
|
|
onTouchMove={move}
|
|
onTouchEnd={end}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-xs text-muted-foreground">
|
|
Draw your signature above
|
|
</p>
|
|
<Button type="button" variant="ghost" size="sm" className="gap-1" onClick={clear}>
|
|
<Eraser className="size-3.5" />
|
|
Clear
|
|
</Button>
|
|
</div>
|
|
{empty && (
|
|
<p className="text-xs text-amber-700">Signature is required before confirming.</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|