nonszy-space/src/components/client-windows.tsx
2025-09-02 22:56:05 +07:00

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>
)
}