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;