116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
'use client'
|
|
|
|
import { Icon } from "@iconify/react";
|
|
import clsx from "clsx";
|
|
import { useState, useRef, useEffect } from "react";
|
|
|
|
export const FakeRelativeWindow = ({
|
|
windowText,
|
|
className,
|
|
draggable,
|
|
withAnim,
|
|
children
|
|
}: {
|
|
windowText: string,
|
|
className?: string,
|
|
draggable?: boolean,
|
|
withAnim?: boolean,
|
|
children: React.ReactNode
|
|
}) => {
|
|
const [isMinimized, setMinimize] = useState(false);
|
|
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
|
const [animation, setAnimation] = useState({
|
|
window: "animate-window-popup",
|
|
content: "animate-[fade-in-half_1s]"
|
|
});
|
|
const popupRef = useRef<HTMLDivElement>(null);
|
|
const pos = useRef({
|
|
dragging: false,
|
|
mouseX: 0,
|
|
mouseY: 0,
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!withAnim) return;
|
|
const node = popupRef.current;
|
|
if (!node) return;
|
|
const observer = new window.IntersectionObserver(
|
|
([entry]) => {
|
|
if (entry.isIntersecting) {
|
|
setAnimation({
|
|
window: "animate-window-popup",
|
|
content: "animate-[fade-in-half_1s]"
|
|
});
|
|
} else {
|
|
setAnimation({
|
|
window: "scale-x-0 scale-y-[0.03] animate-window-popdown",
|
|
content: "animate-[fade-out-half_1s]"
|
|
});
|
|
}
|
|
},
|
|
{ threshold: 0.3 }
|
|
);
|
|
observer.observe(node);
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
const toggleMinimize = () => setMinimize(!isMinimized);
|
|
|
|
const onMouseDown = (e: React.MouseEvent) => {
|
|
if (!draggable) return;
|
|
if (popupRef.current) {
|
|
pos.current.dragging = true;
|
|
pos.current.mouseX = e.clientX;
|
|
pos.current.mouseY = e.clientY;
|
|
pos.current.x = offset.x;
|
|
pos.current.y = offset.y;
|
|
document.body.style.userSelect = "none";
|
|
}
|
|
window.addEventListener("mousemove", onMouseMove);
|
|
window.addEventListener("mouseup", onMouseUp);
|
|
};
|
|
|
|
const onMouseUp = () => {
|
|
pos.current.dragging = false;
|
|
document.body.style.userSelect = "";
|
|
window.removeEventListener("mousemove", onMouseMove);
|
|
window.removeEventListener("mouseup", onMouseUp);
|
|
};
|
|
|
|
const onMouseMove = (e: MouseEvent) => {
|
|
if (pos.current.dragging && popupRef.current) {
|
|
const dx = e.clientX - pos.current.mouseX;
|
|
const dy = e.clientY - pos.current.mouseY;
|
|
const newX = pos.current.x + dx;
|
|
const newY = pos.current.y + dy;
|
|
popupRef.current.style.transform = `translate(${newX}px, ${newY}px)`;
|
|
setOffset({ x: newX, y: newY });
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={clsx("absolute hidden lg:block", className)}>
|
|
<div className={clsx("mx-auto md:border bg-background border-primary", withAnim && animation.window)} ref={popupRef}>
|
|
<div
|
|
className="hidden md:flex bg-primary p-2 justify-between text-background windowbar"
|
|
onMouseDown={onMouseDown}
|
|
>
|
|
<div className="ms-1 pointer-events-none">
|
|
{windowText}
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<button className="bg-primary border border-[#FFA826] border-outset p-1" onClick={toggleMinimize}><Icon icon="lucide:minus"/></button>
|
|
<button className="bg-primary border border-[#FFA826] border-outset p-1"><Icon icon="lucide:x"/></button>
|
|
</div>
|
|
</div>
|
|
<div className={clsx("m-1 border border-primary", isMinimized ? "h-0 overflow-y-clip" : "h-fit")}>
|
|
<div className={clsx("md:p-4", withAnim && animation.content)}>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
} |