101 lines
2.9 KiB
TypeScript

"use client";
import {
FC,
MouseEventHandler,
PropsWithChildren,
useEffect,
useRef,
useState,
} from "react";
import { Portal } from "../portal/components";
export const Sticky: FC<
PropsWithChildren<{ sidedness: 1 | -1; initialX?: number; initialY?: number }>
> = (
{ children, sidedness, initialX, initialY },
) => {
const [position, setPosition] = useState({
x: initialX ?? 10,
y: initialY ?? 10,
});
const divRef = useRef<HTMLDivElement>(null);
const [dragging, setDragging] = useState(false);
const [offset, setOffset] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (dragging) {
setPosition({
x: ((sidedness === -1 ? document.body.clientWidth : 0) -
(e.pageX - offset.x * sidedness)) * -sidedness,
y: e.pageY - offset.y,
});
}
};
const handleMouseUp = () => {
if (dragging) {
setDragging(false);
}
};
if (dragging) {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
}
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [dragging, offset, sidedness]);
const handleMouseDown: MouseEventHandler = (e) => {
e.preventDefault();
const rect = divRef.current!.getBoundingClientRect();
const offsetX = e.pageX - rect.left;
const offsetY = e.pageY - rect.top;
setOffset({ x: offsetX, y: offsetY });
setDragging(true);
};
return (
<Portal>
<div
className="fixed card p-0 overflow-clip"
style={{
top: position.y,
left: sidedness === 1 ? position.x : "unset",
right: sidedness === -1 ? position.x : "unset",
}}
>
<div
ref={divRef}
className="cursor-move p-1 bg-black/20 flex justify-center"
onMouseDown={handleMouseDown}
draggable="false"
style={{ position: "relative" }}
>
<svg width="70" height="30" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="1" fill="grey" />
<circle cx="20" cy="10" r="1" fill="grey" />
<circle cx="30" cy="10" r="1" fill="grey" />
<circle cx="40" cy="10" r="1" fill="grey" />
<circle cx="50" cy="10" r="1" fill="grey" />
<circle cx="60" cy="10" r="1" fill="grey" />
<circle cx="10" cy="20" r="1" fill="grey" />
<circle cx="20" cy="20" r="1" fill="grey" />
<circle cx="30" cy="20" r="1" fill="grey" />
<circle cx="40" cy="20" r="1" fill="grey" />
<circle cx="50" cy="20" r="1" fill="grey" />
<circle cx="60" cy="20" r="1" fill="grey" />
</svg>
</div>
<div className="p-4">{children}</div>
</div>
</Portal>
);
};