101 lines
2.9 KiB
TypeScript
Executable File
101 lines
2.9 KiB
TypeScript
Executable File
"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>
|
|
);
|
|
};
|