Button Handle

A handle component with a button attached.

UI Component: button-handle

index.tsx
import { Position, type HandleProps } from "@xyflow/react";
import { BaseHandle } from "@/registry/components/base-handle";
 
const wrapperClassNames: Record<Position, string> = {
  [Position.Top]:
    "flex-col-reverse left-1/2 -translate-y-full -translate-x-1/2",
  [Position.Bottom]: "flex-col left-1/2 translate-y-[10px] -translate-x-1/2",
  [Position.Left]:
    "flex-row-reverse top-1/2 -translate-x-full -translate-y-1/2",
  [Position.Right]: "top-1/2 -translate-y-1/2 translate-x-[10px]",
};
 
export function ButtonHandle({
  showButton = true,
  position = Position.Bottom,
  children,
  ...props
}: HandleProps & { showButton?: boolean }) {
  const wrapperClassName = wrapperClassNames[position || Position.Bottom];
  const vertical = position === Position.Top || position === Position.Bottom;
 
  return (
    <BaseHandle position={position} id={props.id} {...props}>
      {showButton && (
        <div
          className={`absolute flex items-center ${wrapperClassName} pointer-events-none`}
        >
          <div
            className={`bg-gray-300 ${vertical ? "h-10 w-px" : "h-px w-10"}`}
          />
          <div className="nodrag nopan pointer-events-auto">{children}</div>
        </div>
      )}
    </BaseHandle>
  );
}
component-example.tsx
import { Plus } from "lucide-react";
import { ConnectionState, Position, useConnection } from "@xyflow/react";
 
import { ButtonHandle } from "@/registry/components/button-handle";
import { BaseNode, BaseNodeContent } from "@/registry/components/base-node";
 
import { Button } from "@/components/ui/button";
 
const onClick = () => {
  window.alert(`Handle button has been clicked!`);
};
 
const selector = (connection: ConnectionState) => {
  return connection.inProgress;
};
 
const ButtonHandleDemo = () => {
  const connectionInProgress = useConnection(selector);
 
  return (
    <BaseNode>
      <BaseNodeContent>
        Node with a handle button
        <ButtonHandle
          type="target"
          position={Position.Bottom}
          showButton={!connectionInProgress}
        >
          <Button
            onClick={onClick}
            size="sm"
            variant="secondary"
            className="rounded-full"
          >
            <Plus size={10} />
          </Button>
        </ButtonHandle>
      </BaseNodeContent>
    </BaseNode>
  );
};
 
export default ButtonHandleDemo;