mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-15 13:43:39 +01:00
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import classNames from 'classnames';
|
|
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
|
import { useCallback, useRef, useState } from 'react';
|
|
|
|
const START_DISTANCE = 7;
|
|
|
|
export interface ResizeHandleEvent {
|
|
x: number;
|
|
y: number;
|
|
xStart: number;
|
|
yStart: number;
|
|
}
|
|
|
|
interface Props {
|
|
style?: CSSProperties;
|
|
className?: string;
|
|
onResizeStart?: () => void;
|
|
onResizeEnd?: () => void;
|
|
onResizeMove?: (e: ResizeHandleEvent) => void;
|
|
onReset?: () => void;
|
|
side: 'left' | 'right' | 'top';
|
|
justify: 'center' | 'end' | 'start';
|
|
}
|
|
|
|
export function ResizeHandle({
|
|
style,
|
|
justify,
|
|
className,
|
|
onResizeStart,
|
|
onResizeEnd,
|
|
onResizeMove,
|
|
onReset,
|
|
side,
|
|
}: Props) {
|
|
const vertical = side === 'top';
|
|
const [isResizing, setIsResizing] = useState<boolean>(false);
|
|
const moveState = useRef<{
|
|
move: (e: MouseEvent) => void;
|
|
up: (e: MouseEvent) => void;
|
|
calledStart: boolean;
|
|
xStart: number;
|
|
yStart: number;
|
|
} | null>(null);
|
|
|
|
const handlePointerDown = useCallback(
|
|
(e: ReactMouseEvent<HTMLDivElement>) => {
|
|
function move(e: MouseEvent) {
|
|
if (moveState.current == null) return;
|
|
|
|
const xDistance = moveState.current.xStart - e.clientX;
|
|
const yDistance = moveState.current.yStart - e.clientY;
|
|
const distance = Math.abs(vertical ? yDistance : xDistance);
|
|
if (moveState.current.calledStart) {
|
|
onResizeMove?.({
|
|
x: e.clientX,
|
|
y: e.clientY,
|
|
xStart: moveState.current.xStart,
|
|
yStart: moveState.current.yStart,
|
|
});
|
|
} else if (distance > START_DISTANCE) {
|
|
onResizeStart?.();
|
|
moveState.current.calledStart = true;
|
|
setIsResizing(true);
|
|
}
|
|
}
|
|
|
|
function up() {
|
|
setIsResizing(false);
|
|
moveState.current = null;
|
|
document.documentElement.removeEventListener('mousemove', move);
|
|
document.documentElement.removeEventListener('mouseup', up);
|
|
onResizeEnd?.();
|
|
}
|
|
|
|
moveState.current = { calledStart: false, xStart: e.clientX, yStart: e.clientY, move, up };
|
|
|
|
document.documentElement.addEventListener('mousemove', move);
|
|
document.documentElement.addEventListener('mouseup', up);
|
|
},
|
|
[onResizeEnd, onResizeMove, onResizeStart, vertical],
|
|
);
|
|
|
|
return (
|
|
<div
|
|
aria-hidden
|
|
style={style}
|
|
onDoubleClick={onReset}
|
|
onPointerDown={handlePointerDown}
|
|
className={classNames(
|
|
className,
|
|
'group z-10 flex select-none transition-colors hover:bg-surface-active rounded-full',
|
|
// 'bg-info', // For debugging
|
|
vertical ? 'w-full h-1.5 cursor-row-resize' : 'h-full w-1.5 cursor-col-resize',
|
|
justify === 'center' && 'justify-center',
|
|
justify === 'end' && 'justify-end',
|
|
justify === 'start' && 'justify-start',
|
|
side === 'right' && 'right-0',
|
|
side === 'left' && 'left-0',
|
|
side === 'top' && 'top-0',
|
|
)}
|
|
>
|
|
{/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */}
|
|
{isResizing && (
|
|
<div
|
|
className={classNames(
|
|
// 'bg-[rgba(255,0,0,0.1)]', // For debugging
|
|
'fixed -left-[100vw] -right-[100vw] -top-[100vh] -bottom-[100vh]',
|
|
vertical && 'cursor-row-resize',
|
|
!vertical && 'cursor-col-resize',
|
|
)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|