cal.pub0.org/packages/features/timezone-buddy/components/HoverOverview.tsx

150 lines
3.7 KiB
TypeScript

import { useState, useRef, useEffect, useMemo } from "react";
import { DAY_CELL_WIDTH } from "../constants";
function rounded(x: number, dayCellWidth: number) {
let n = Math.round(x / dayCellWidth);
n = Math.max(0, n);
n = Math.min(24, n);
return n * dayCellWidth;
}
function useElementBounding<T extends HTMLDivElement>(ref: React.RefObject<T>): DOMRect | null {
const [boundingRect, setBoundingRect] = useState<DOMRect | null>(null);
const observer = useRef<ResizeObserver | null>(null);
useEffect(() => {
const target = ref.current;
function updateBoundingRect() {
if (target) {
const rect = target.getBoundingClientRect();
setBoundingRect(rect);
}
}
updateBoundingRect();
observer.current = new ResizeObserver(updateBoundingRect);
observer.current.observe(target as Element);
return () => {
if (observer.current) {
observer.current.disconnect();
observer.current = null;
}
};
}, [ref]);
return boundingRect;
}
function useMouse() {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const [isPressed, setIsPressed] = useState(false);
useEffect(() => {
function handleMouseMove(event: MouseEvent) {
setX(event.clientX);
setY(event.clientY);
}
function handleMouseDown() {
setIsPressed(true);
}
function handleMouseUp() {
setIsPressed(false);
}
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mousedown", handleMouseDown);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mousedown", handleMouseDown);
document.removeEventListener("mouseup", handleMouseUp);
};
}, []);
return { x, y, isPressed };
}
export function HoverOverview() {
const top = useRef(0);
const bottom = useRef(0);
const edgeStart = useRef(0);
const edgeEnd = useRef(0);
const [leftEdge, setLeftEdge] = useState(0);
const [rightEdge, setRightEdge] = useState(0);
const [width, setWidth] = useState(0);
const { x, y, isPressed: pressed } = useMouse();
const el = useRef<HTMLDivElement | null>(null);
const box = useElementBounding(el);
useEffect(() => {
function updateEdge() {
const roundedX = rounded(x - (box?.left || 0), DAY_CELL_WIDTH);
edgeStart.current = roundedX;
edgeEnd.current = roundedX;
setLeftEdge(roundedX);
}
if (pressed) {
updateEdge();
}
}, [pressed, x, box]);
useEffect(() => {
if (pressed) {
const roundedX = rounded(x - (box?.left || 0), DAY_CELL_WIDTH);
edgeEnd.current = roundedX;
setRightEdge(roundedX);
}
}, [pressed, x, box]);
useEffect(() => {
setWidth(Math.abs(edgeStart.current - edgeEnd.current));
}, [edgeStart, edgeEnd]);
const position = useMemo(
() => ({
left: `${leftEdge}px`,
top: `${top}px`,
bottom: `${bottom}px`,
width: `${width}px`,
}),
[leftEdge, top, bottom, width]
);
const leftWhiteout = useMemo(
() => ({
left: "0",
top: `${top}px`,
bottom: `${bottom}px`,
width: `${leftEdge}px`,
}),
[leftEdge, top, bottom]
);
const rightWhiteout = useMemo(
() => ({
right: "0",
top: `${top}px`,
bottom: `${bottom}px`,
left: `${rightEdge}px`,
}),
[rightEdge, top, bottom]
);
return (
<div ref={el} className="absoulte inset-0 w-full">
<div className="bg-default/80 absolute" style={leftWhiteout} />
<div className="bg-default/80 absolute" style={rightWhiteout} />
<div className="border-emphasis border border-dashed" style={position} />
</div>
);
}