image-converter-client/app/routes/DragableBox.tsx
christian 86ba372227
All checks were successful
Deploy to Cloudflare Pages / deploy (push) Successful in 32s
less js more css
2024-11-03 22:39:34 +01:00

131 lines
3.5 KiB
TypeScript

import { useState, useEffect, MouseEvent, useRef, Dispatch, SetStateAction } from 'react';
type Coordinates = {
x: number;
y: number;
}
interface DraggableBoxProps {
position: Coordinates;
className?: string;
children?: React.ReactNode;
coordSetter: Dispatch<SetStateAction<Coordinates>>;
mode?: 'handle' | 'box';
width?: number;
height?: number;
onDrag?: (delta: Coordinates) => void;
}
const DraggableBox = ({
position,
className = "",
children = "",
coordSetter,
mode = 'handle',
width = 0,
height = 0,
onDrag
}: DraggableBoxProps) => {
const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const dragRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isDragging) return;
const handleMouseMove = (e: globalThis.MouseEvent) => {
if (!dragRef.current?.parentElement) return;
const parent = dragRef.current.parentElement;
const parentRect = parent.getBoundingClientRect();
if (mode === 'handle') {
const x = ((e.clientX - parentRect.left - dragOffset.x) / parentRect.width) * 100;
const y = ((e.clientY - parentRect.top - dragOffset.y) / parentRect.height) * 100;
const newX = Math.min(Math.max(0, x), 100);
const newY = Math.min(Math.max(0, y), 100);
coordSetter({
x: Number(newX.toFixed(1)),
y: Number(newY.toFixed(1))
});
} else {
const x = ((e.clientX - parentRect.left - dragOffset.x) / parentRect.width) * 100;
const y = ((e.clientY - parentRect.top - dragOffset.y) / parentRect.height) * 100;
const maxX = 100 - width;
const maxY = 100 - height;
const newX = Math.min(Math.max(0, x), maxX);
const newY = Math.min(Math.max(0, y), maxY);
coordSetter({
x: Number(newX.toFixed(1)),
y: Number(newY.toFixed(1))
});
onDrag?.({ x: newX, y: newY });
}
};
const handleMouseUp = () => setIsDragging(false);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, dragOffset, mode, width, height, onDrag]);
const handleMouseDown = (e: MouseEvent) => {
if (!dragRef.current?.parentElement) return;
const parentRect = dragRef.current.parentElement.getBoundingClientRect();
const elementRect = dragRef.current.getBoundingClientRect();
if (mode === 'handle') {
setDragOffset({
x: e.clientX - elementRect.left,
y: e.clientY - elementRect.top
});
} else {
setDragOffset({
x: e.clientX - parentRect.left - (position.x * parentRect.width / 100),
y: e.clientY - parentRect.top - (position.y * parentRect.height / 100)
});
}
setIsDragging(true);
};
const style = mode === 'handle' ? {
left: `${position.x}%`,
top: `${position.y}%`,
transform: 'translate(-50%, -50%)', // Center the handle
} : {
left: `${position.x}%`,
top: `${position.y}%`,
width: `${width}%`,
height: `${height}%`,
};
return (
<div
ref={dragRef}
className={`absolute cursor-pointer transform-gpu ${mode === 'box' ? 'cursor-move' : ''} ${className}`}
style={{
...style,
userSelect: 'none',
}}
onMouseDown={handleMouseDown}
>
{children}
</div>
);
};
export default DraggableBox;