chore(repo): initialize planarchy workspace

This commit is contained in:
2026-03-14 14:31:09 +01:00
commit dd55d0e78b
769 changed files with 166461 additions and 0 deletions
@@ -0,0 +1,96 @@
"use client";
import { useState, useRef, useEffect } from "react";
import { createPortal } from "react-dom";
interface InfoTooltipProps {
content: React.ReactNode;
/** Position relative to the trigger icon. Default: "top" */
position?: "top" | "bottom";
/** Extra width class, e.g. "w-72". Default: "w-60" */
width?: string;
}
/**
* Small icon that shows a tooltip on hover / focus.
* Rendered via a portal into document.body so it's never clipped by
* ancestor overflow:hidden containers (table cells, widget cards, etc.).
*/
export function InfoTooltip({ content, position = "top", width = "w-60" }: InfoTooltipProps) {
const [show, setShow] = useState(false);
const [coords, setCoords] = useState({ top: 0, left: 0 });
const btnRef = useRef<HTMLButtonElement>(null);
function computeCoords() {
if (!btnRef.current) return;
const rect = btnRef.current.getBoundingClientRect();
if (position === "top") {
setCoords({
top: rect.top + window.scrollY - 8, // 8px gap + arrow
left: rect.left + window.scrollX + rect.width / 2,
});
} else {
setCoords({
top: rect.bottom + window.scrollY + 8,
left: rect.left + window.scrollX + rect.width / 2,
});
}
}
function handleShow() {
computeCoords();
setShow(true);
}
// Recompute on scroll/resize while shown so tooltip follows the trigger
useEffect(() => {
if (!show) return;
const update = () => computeCoords();
window.addEventListener("scroll", update, true);
window.addEventListener("resize", update);
return () => {
window.removeEventListener("scroll", update, true);
window.removeEventListener("resize", update);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [show]);
const tooltipStyle: React.CSSProperties =
position === "top"
? { position: "fixed", top: coords.top, left: coords.left, transform: "translate(-50%, -100%)" }
: { position: "fixed", top: coords.top, left: coords.left, transform: "translateX(-50%)" };
const arrowClass =
position === "top"
? "top-full border-t-gray-900 border-l-transparent border-r-transparent border-b-transparent border-l-4 border-r-4 border-t-4 border-b-0"
: "bottom-full border-b-gray-900 border-l-transparent border-r-transparent border-t-transparent border-l-4 border-r-4 border-b-4 border-t-0";
return (
<span className="relative inline-flex items-center">
<button
ref={btnRef}
type="button"
onMouseEnter={handleShow}
onMouseLeave={() => setShow(false)}
onFocus={handleShow}
onBlur={() => setShow(false)}
className="ml-1 w-3.5 h-3.5 rounded-full bg-gray-200 dark:bg-gray-600 text-gray-500 dark:text-gray-300 text-[9px] font-bold flex items-center justify-center hover:bg-gray-300 dark:hover:bg-gray-500 cursor-help flex-shrink-0 leading-none"
aria-label="More information"
>
i
</button>
{show &&
createPortal(
<div
style={tooltipStyle}
className={`z-[9999] ${width} bg-gray-900 text-white text-xs rounded-lg px-3 py-2 shadow-xl pointer-events-none`}
>
{content}
<span className={`absolute left-1/2 -translate-x-1/2 w-0 h-0 ${arrowClass}`} />
</div>,
document.body,
)}
</span>
);
}