Node Tooltip
A wrapper for node components that displays a tooltip when hovered. Built on top of the NodeToolbar component that comes with React Flow.
UI Component: node-tooltip
index.tsx
"use client";
import React, {
createContext,
useCallback,
useContext,
useState,
type ComponentProps,
} from "react";
import { NodeToolbar, type NodeToolbarProps } from "@xyflow/react";
import { cn } from "@/lib/utils";
/* TOOLTIP CONTEXT ---------------------------------------------------------- */
type TooltipContextType = {
isVisible: boolean;
showTooltip: () => void;
hideTooltip: () => void;
};
const TooltipContext = createContext<TooltipContextType | null>(null);
/* TOOLTIP NODE ------------------------------------------------------------- */
export function NodeTooltip({ children }: ComponentProps<"div">) {
const [isVisible, setIsVisible] = useState(false);
const showTooltip = useCallback(() => setIsVisible(true), []);
const hideTooltip = useCallback(() => setIsVisible(false), []);
return (
<TooltipContext.Provider value={{ isVisible, showTooltip, hideTooltip }}>
<div>{children}</div>
</TooltipContext.Provider>
);
}
/* TOOLTIP TRIGGER ---------------------------------------------------------- */
export function NodeTooltipTrigger(props: ComponentProps<"div">) {
const tooltipContext = useContext(TooltipContext);
if (!tooltipContext) {
throw new Error("NodeTooltipTrigger must be used within NodeTooltip");
}
const { showTooltip, hideTooltip } = tooltipContext;
const onMouseEnter = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
props.onMouseEnter?.(e);
showTooltip();
},
[props, showTooltip],
);
const onMouseLeave = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
props.onMouseLeave?.(e);
hideTooltip();
},
[props, hideTooltip],
);
return (
<div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} />
);
}
/* TOOLTIP CONTENT ---------------------------------------------------------- */
// /**
// * A component that displays the tooltip content based on visibility context.
// */
export function NodeTooltipContent({
children,
position,
className,
...props
}: NodeToolbarProps) {
const tooltipContext = useContext(TooltipContext);
if (!tooltipContext) {
throw new Error("NodeTooltipContent must be used within NodeTooltip");
}
const { isVisible } = tooltipContext;
return (
<div>
<NodeToolbar
isVisible={isVisible}
className={cn(
"bg-primary text-primary-foreground rounded-sm p-2",
className,
)}
tabIndex={1}
position={position}
{...props}
>
{children}
</NodeToolbar>
</div>
);
}component-example.tsx
import React, { memo } from "react";
import { Position } from "@xyflow/react";
import {
NodeTooltip,
NodeTooltipContent,
NodeTooltipTrigger,
} from "@/registry/components/node-tooltip";
import { BaseNode, BaseNodeContent } from "../base-node";
const NodeTooltipDemo = memo(() => {
return (
<NodeTooltip>
<NodeTooltipContent position={Position.Top} className="text-center">
You can display any content here, like text, images, or even components.{" "}
<br />
The tooltip will appear when you hover over the trigger.
</NodeTooltipContent>
<BaseNode className="w-64 text-center">
<BaseNodeContent className="flex flex-col items-center">
<NodeTooltipTrigger className="rounded border border-gray-300 p-2 text-lg font-bold">
Hover me! ⭐️
</NodeTooltipTrigger>
<span>
You can add more content that does not trigger the tooltip.
</span>
</BaseNodeContent>
</BaseNode>
</NodeTooltip>
);
});
NodeTooltipDemo.displayName = "NodeTooltipDemo";
export default NodeTooltipDemo;