"use client"; import { useState } from "react"; import { createPortal } from "react-dom"; import { useTheme } from "next-themes"; import { MarkdownPreview } from "@/components/markdown/markdown-widgets"; const OFFSET = 16; // Rough size assumptions used to keep the tooltip on-screen -- cheaper than // measuring the rendered node and repositioning after the fact, and close // enough for a small hover preview. const ASSUMED_WIDTH = 288; const ASSUMED_HEIGHT = 180; /** * Wraps its child in a tooltip that tracks the cursor instead of anchoring * to the trigger element -- rendered via a portal to `document.body` so it * isn't caught by an ancestor's CSS `transform` (dnd-kit positions dragged * cards with one, which would otherwise turn `position: fixed` here into * "fixed to that card" instead of the viewport). Content is rendered as * markdown, same as the editor it was written in, so links etc. actually * show up as links in the preview too. */ export function MouseFollowTooltip({ content, children, }: { content: string; children: React.ReactNode; }) { const { resolvedTheme } = useTheme(); const colorMode = resolvedTheme === "dark" ? "dark" : "light"; const [pos, setPos] = useState<{ x: number; y: number } | null>(null); function handleMove(e: React.MouseEvent) { setPos({ x: e.clientX, y: e.clientY }); } let left = 0; let top = 0; if (pos) { left = pos.x + OFFSET; top = pos.y + OFFSET; if (typeof window !== "undefined") { if (left + ASSUMED_WIDTH > window.innerWidth) left = pos.x - ASSUMED_WIDTH - OFFSET; if (top + ASSUMED_HEIGHT > window.innerHeight) top = pos.y - ASSUMED_HEIGHT - OFFSET; } } return ( setPos(null)} > {children} {pos && typeof document !== "undefined" && createPortal(
, document.body )}
); }