reactour icon indicating copy to clipboard operation
reactour copied to clipboard

Feature Request : Making popovers draggable

Open sakarozone opened this issue 1 year ago • 1 comments

I think it would be a good enhancement if the popover could be made draggable. In one of the steps of the onboarding process that I have done, the entire screen is highlighted, and allowing users to move the popover would provide greater flexibility and convenience.

Let me know what you think!

sakarozone avatar Oct 10 '24 09:10 sakarozone

You can implement this by yourself actually, just create a customContentComponent and wrap that with a draggable div.

Here is an example code

import { cn } from "@/lib/utils";
import { Grip } from "lucide-react";
import React, { useEffect, useState } from "react";

interface DraggablePopoverProps {
  children: React.ReactNode;
  currentStep: number;
  draggable: boolean | undefined;
}

export const DraggablePopover = ({
  children,
  currentStep,
  draggable,
}: DraggablePopoverProps) => {
  const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);
  const [dragStart, setDragStart] = useState({ x: 0, y: 0 });

  // Reset position when step changes
  useEffect(() => {
    setDragOffset({ x: 0, y: 0 });
  }, [currentStep]);

  const handleMouseDown = (e: React.MouseEvent) => {
    if (e.button !== 0) return; // Only handle left click
    e.stopPropagation();
    setIsDragging(true);
    setDragStart({
      x: e.clientX - dragOffset.x,
      y: e.clientY - dragOffset.y,
    });
  };

  useEffect(() => {
    if (!draggable) return;

    const handleMouseMove = (e: MouseEvent) => {
      if (!isDragging) return;
      e.stopPropagation();

      setDragOffset({
        x: e.clientX - dragStart.x,
        y: e.clientY - dragStart.y,
      });
    };

    const handleMouseUp = (e: MouseEvent) => {
      if (!isDragging) return;
      e.stopPropagation();
      setIsDragging(false);
    };

    if (isDragging) {
      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
    }

    return () => {
      document.removeEventListener("mousemove", handleMouseMove);
      document.removeEventListener("mouseup", handleMouseUp);
    };
  }, [isDragging, dragStart, draggable]);

  return (
    <div
      data-tour="wrapper-popover"
      style={{
        transform: `translate(${dragOffset.x}px, ${dragOffset.y}px)`,
        position: "relative",
        zIndex: 9999,
        pointerEvents: "auto",
      }}
    >
      <div className="bg-popover shadow-primary rounded-lg p-4 dark:border dark:shadow-none">
        {draggable && (
          <div
            className="bg-muted/10 absolute left-0 right-0 top-0 flex h-8 cursor-grab items-center justify-center rounded-t-lg active:cursor-grabbing"
            onMouseDown={handleMouseDown}
          >
            <Grip className="text-muted-foreground/40 h-4 w-4" />
          </div>
        )}
        <div className={cn(draggable ? "mt-6" : "mt-0")}>{children}</div>
      </div>
    </div>
  );
};

yektas avatar Apr 09 '25 00:04 yektas