Open signature in a landscape overlay and fix mobile touch

The inline signature pad on the confirm page was unusable on phones:
the canvas had a fixed 480px internal width but shrank via max-width
on small screens, so pointer coordinates landed in only the top-left
fraction of the drawing buffer. Edge-of-screen strokes also collided
with iOS Safari back-swipe.

Tapping the new signature trigger now opens a full-screen overlay
that rotates to landscape (via 100dvh/100dvw + CSS rotate on portrait
phones, plus an opportunistic screen.orientation.lock) so customers
get the widest possible signing area, away from the system edge.

Also swap the hand-rolled drawing logic for signature_pad, with a
ResizeObserver-driven resize that preserves strokes via
toData/fromData and scales the canvas to devicePixelRatio.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Frudrax Cheng
2026-05-27 09:11:05 +08:00
parent 1d944b0fd3
commit ab5acbc452
7 changed files with 310 additions and 120 deletions
+74
View File
@@ -0,0 +1,74 @@
import { useEffect, useRef, useState } from 'react';
import { Button } from 'antd';
import SignaturePad, { type SignaturePadHandle } from './SignaturePad';
import './SignatureOverlay.css';
interface SignatureOverlayProps {
open: boolean;
onCancel: () => void;
onConfirm: (dataUrl: string) => void;
}
function SignatureOverlay({ open, onCancel, onConfirm }: SignatureOverlayProps) {
const padRef = useRef<SignaturePadHandle>(null);
const [data, setData] = useState('');
useEffect(() => {
if (!open) return;
const orientation = (screen as Screen & {
orientation?: { lock?: (o: string) => Promise<void> };
}).orientation;
if (orientation?.lock) {
orientation.lock('landscape').catch(() => {
// ignore; iOS Safari etc. don't permit lock outside fullscreen
});
}
const prevOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = prevOverflow;
};
}, [open]);
if (!open) return null;
const handleClear = () => {
padRef.current?.clear();
setData('');
};
const handleConfirm = () => {
if (padRef.current?.isEmpty()) return;
const url = padRef.current?.getDataURL() || data;
if (!url) return;
onConfirm(url);
};
return (
<div className="signature-overlay" role="dialog" aria-modal="true">
<div className="signature-overlay-stage">
<div className="signature-overlay-bar">
<Button type="text" onClick={onCancel}>
</Button>
<span className="signature-overlay-title"></span>
<Button type="text" onClick={handleClear}>
</Button>
</div>
<div className="signature-overlay-pad">
<SignaturePad ref={padRef} onChange={setData} />
</div>
<div className="signature-overlay-actions">
<Button size="large" type="primary" block onClick={handleConfirm}>
</Button>
</div>
</div>
</div>
);
}
export default SignatureOverlay;