feat: improve VIN camera crop overlay-to-crop alignment and touch targets (refs #123)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m20s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 51s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m20s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 51s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Bridge guidance overlay position to crop tool initial coordinates so the crop box appears centered matching the viewfinder guide. Increase handle touch targets to 44px (32px on compact viewports) for mobile usability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState, useRef, useEffect } from 'react';
|
||||
import { Box, IconButton, Button, Typography, CircularProgress } from '@mui/material';
|
||||
import { Box, IconButton, Button, Typography, CircularProgress, useMediaQuery } from '@mui/material';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import ReplayIcon from '@mui/icons-material/Replay';
|
||||
@@ -15,6 +15,7 @@ import { useImageCrop, type CropHandle } from './useImageCrop';
|
||||
export const CropTool: React.FC<CropToolProps> = ({
|
||||
imageSrc,
|
||||
lockAspectRatio = false,
|
||||
initialCrop,
|
||||
aspectRatio,
|
||||
onConfirm,
|
||||
onReset,
|
||||
@@ -25,9 +26,14 @@ export const CropTool: React.FC<CropToolProps> = ({
|
||||
const imageAreaRef = useRef<HTMLDivElement>(null);
|
||||
const [imageMaxHeight, setImageMaxHeight] = useState(0);
|
||||
|
||||
// Responsive handle sizing: 44px standard, 32px on compact viewports
|
||||
const isCompactViewport = useMediaQuery('(max-height: 400px)');
|
||||
const handleConfig = { size: isCompactViewport ? 32 : 44, visual: isCompactViewport ? 12 : 16 };
|
||||
|
||||
const { cropArea, cropDrawn, isDragging, resetCrop, executeCrop, handleDragStart, handleDrawStart } =
|
||||
useImageCrop({
|
||||
aspectRatio: lockAspectRatio ? aspectRatio : undefined,
|
||||
initialCrop,
|
||||
});
|
||||
|
||||
const showCropArea = cropDrawn || (isDragging && cropArea.width > 1 && cropArea.height > 1);
|
||||
@@ -203,22 +209,22 @@ export const CropTool: React.FC<CropToolProps> = ({
|
||||
onDragStart={handleDragStart}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 8,
|
||||
inset: handleConfig.size / 2,
|
||||
cursor: 'move',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Corner handles */}
|
||||
<CropHandle handle="nw" onDragStart={handleDragStart} position="top-left" />
|
||||
<CropHandle handle="ne" onDragStart={handleDragStart} position="top-right" />
|
||||
<CropHandle handle="sw" onDragStart={handleDragStart} position="bottom-left" />
|
||||
<CropHandle handle="se" onDragStart={handleDragStart} position="bottom-right" />
|
||||
<CropHandle handle="nw" onDragStart={handleDragStart} position="top-left" config={handleConfig} />
|
||||
<CropHandle handle="ne" onDragStart={handleDragStart} position="top-right" config={handleConfig} />
|
||||
<CropHandle handle="sw" onDragStart={handleDragStart} position="bottom-left" config={handleConfig} />
|
||||
<CropHandle handle="se" onDragStart={handleDragStart} position="bottom-right" config={handleConfig} />
|
||||
|
||||
{/* Edge handles */}
|
||||
<CropHandle handle="n" onDragStart={handleDragStart} position="top" />
|
||||
<CropHandle handle="s" onDragStart={handleDragStart} position="bottom" />
|
||||
<CropHandle handle="w" onDragStart={handleDragStart} position="left" />
|
||||
<CropHandle handle="e" onDragStart={handleDragStart} position="right" />
|
||||
<CropHandle handle="n" onDragStart={handleDragStart} position="top" config={handleConfig} />
|
||||
<CropHandle handle="s" onDragStart={handleDragStart} position="bottom" config={handleConfig} />
|
||||
<CropHandle handle="w" onDragStart={handleDragStart} position="left" config={handleConfig} />
|
||||
<CropHandle handle="e" onDragStart={handleDragStart} position="right" config={handleConfig} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -326,6 +332,7 @@ export const CropTool: React.FC<CropToolProps> = ({
|
||||
interface CropHandleProps {
|
||||
handle: CropHandle;
|
||||
onDragStart: (handle: CropHandle, event: React.MouseEvent | React.TouchEvent) => void;
|
||||
config: { size: number; visual: number };
|
||||
position:
|
||||
| 'top-left'
|
||||
| 'top-right'
|
||||
@@ -337,9 +344,9 @@ interface CropHandleProps {
|
||||
| 'right';
|
||||
}
|
||||
|
||||
const CropHandle: React.FC<CropHandleProps> = ({ handle, onDragStart, position }) => {
|
||||
const handleSize = 24;
|
||||
const handleVisualSize = 12;
|
||||
const CropHandle: React.FC<CropHandleProps> = ({ handle, onDragStart, position, config }) => {
|
||||
const handleSize = config.size;
|
||||
const handleVisualSize = config.visual;
|
||||
|
||||
const positionStyles: Record<string, React.CSSProperties> = {
|
||||
'top-left': {
|
||||
@@ -407,8 +414,9 @@ const CropHandle: React.FC<CropHandleProps> = ({ handle, onDragStart, position }
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: isCorner ? handleVisualSize : position === 'top' || position === 'bottom' ? 24 : 8,
|
||||
height: isCorner ? handleVisualSize : position === 'left' || position === 'right' ? 24 : 8,
|
||||
// Edge handles: 70% of touch target for long dimension, 75% of visual for short dimension
|
||||
width: isCorner ? handleVisualSize : position === 'top' || position === 'bottom' ? Math.round(handleSize * 0.7) : Math.round(handleVisualSize * 0.75),
|
||||
height: isCorner ? handleVisualSize : position === 'left' || position === 'right' ? Math.round(handleSize * 0.7) : Math.round(handleVisualSize * 0.75),
|
||||
backgroundColor: 'white',
|
||||
borderRadius: isCorner ? '50%' : 1,
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.3)',
|
||||
|
||||
Reference in New Issue
Block a user