mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-23 10:00:44 -05:00
Add .oxfmtignore to skip generated bindings and wasm-pack output. Add npm format script, update DEVELOPMENT.md for Vite+ toolchain, and format all non-generated files with oxfmt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
|
|
);
|
|
}
|