components: sticky component (draggable positionable box)
This commit is contained in:
parent
5654b5e15d
commit
50e5ff0663
100
lib/sticky/index.tsx
Normal file
100
lib/sticky/index.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
"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>
|
||||||
|
);
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user