"use client";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { Info } from "lucide-react";
// Hilfetext-Bubble.
//
// Der Text wird über ein PORTAL an den
gehängt und `fixed` positioniert. Vorher lag er
// als `absolute` Kind im Fluss -- in einem scrollenden Dialog (Verteil-Dialoge, Assistent)
// schnitt dessen `overflow` die Box ab, teilweise nach wenigen Zeilen. Fix positioniert
// bezieht sich auf den Viewport und entkommt damit jedem Overflow-Container.
//
// Zusätzlich: Die Box klappt nach OBEN, wenn unten kein Platz mehr ist, und wird an den
// Bildschirmrändern geklemmt, damit sie nie halb ausserhalb steht.
const WIDTH = 256; // entspricht w-64
const MARGIN = 8;
export function InfoBubble({ text }: { text: string }) {
const [open, setOpen] = useState(false);
const [pos, setPos] = useState<{ top: number; left: number } | null>(null);
const btnRef = useRef(null);
const boxRef = useRef(null);
// Position aus der Lage des Knopfes berechnen -- erst wenn geöffnet, und erneut, sobald die
// tatsächliche Höhe der Box bekannt ist (für das Hochklappen).
useEffect(() => {
if (!open) {
// eslint-disable-next-line react-hooks/set-state-in-effect -- Position gilt nur im offenen Zustand
setPos(null);
return;
}
const place = () => {
const btn = btnRef.current;
if (!btn) return;
const r = btn.getBoundingClientRect();
const height = boxRef.current?.offsetHeight ?? 0;
const spaceBelow = window.innerHeight - r.bottom;
const above = height > 0 && spaceBelow < height + MARGIN && r.top > height + MARGIN;
const left = Math.min(
Math.max(MARGIN, r.left + r.width / 2 - WIDTH / 2),
Math.max(MARGIN, window.innerWidth - WIDTH - MARGIN)
);
setPos({ top: above ? r.top - height - 6 : r.bottom + 6, left });
};
place();
// Nach dem ersten Zeichnen erneut -- dann steht die Höhe fest.
const raf = requestAnimationFrame(place);
window.addEventListener("scroll", place, true);
window.addEventListener("resize", place);
return () => {
cancelAnimationFrame(raf);
window.removeEventListener("scroll", place, true);
window.removeEventListener("resize", place);
};
}, [open]);
return (
{open &&
typeof document !== "undefined" &&
createPortal(