shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
74.42k stars 4.6k forks source link

Dropdown opening causing whole page to freeze #5350

Open mehdikhan55 opened 1 month ago

mehdikhan55 commented 1 month ago

Describe the bug

Hi. I am trying to use Dropdown component inside of data table. I am using Next.js. Opening this dropdown causes whole page to freeze.

                <DropdownMenuTrigger asChild>
                    <Button variant="ghost" className="h-8 w-8 p-0">
                        <MoreVertical className="h-4 w-4" />
                    </Button>
                </DropdownMenuTrigger>
                <DropdownMenuContent align="end">
                    <DropdownMenuItem onClick={() => navigator.clipboard.writeText(expense.id)}>
                        Copy Expense ID
                    </DropdownMenuItem>
                </DropdownMenuContent>
            </DropdownMenu>

The whole code is given below:

"use client";

import { ColumnDef } from "@tanstack/react-table";
import Image from "next/image";

import { Doctors } from "@/constants";
import { formatDateTime } from "@/lib/utils";
import { Appointment } from "@/types/appwrite.types";
import { AppointmentModal } from "../AppointmentModal";
import { StatusBadge } from "../StatusBadge";

export const Columns: ColumnDef<Appointment>[] = [
  {
    header: "#",
    cell: ({ row }) => {
      return <p className="text-14-medium ">{row.index + 1}</p>;
    },
  },
  {
    accessorKey: "patient",
    header: "Patient",
    cell: ({ row }) => {
      const appointment = row.original;
      return <p className="text-14-medium ">{appointment.patient.name}</p>;
    },
  },
  {
    accessorKey: "status",
    header: "Status",
    cell: ({ row }) => {
      const appointment = row.original;
      return (
        <div className="min-w-[115px]">
          <StatusBadge status={appointment.status} />
        </div>
      );
    },
  },
  {
    accessorKey: "schedule",
    header: "Appointment",
    cell: ({ row }) => {
      const appointment = row.original;
      return (
        <p className="text-14-regular min-w-[100px]">
          {formatDateTime(appointment.schedule).dateTime}
        </p>
      );
    },
  },
  {
    accessorKey: "primaryPhysician",
    header: "Doctor",
    cell: ({ row }) => {
      const appointment = row.original;

      const doctor = Doctors.find(
        (doctor) => doctor.name === appointment.primaryPhysician
      );

      return (
        <div className="flex items-center gap-3">
          <Image
            src={doctor?.image!}
            alt="doctor"
            width={100}
            height={100}
            className="size-8"
          />
          <p className="whitespace-nowrap">Dr. {doctor?.name}</p>
        </div>
      );
    },
  },
  {
    id: "actions",
    header: () => <div className="pl-4">Actions</div>,
    cell: ({ row }) => {
      const appointment = row.original;

      return (
        <div className="flex gap-1">
          <AppointmentModal
            patientId={appointment.patient.$id}
            userId={appointment.userId}
            appointment={appointment}
            type="schedule"
            title="Schedule Appointment"
            description="Please confirm the following details to schedule."
          />
          <AppointmentModal
            patientId={appointment.patient.$id}
            userId={appointment.userId}
            appointment={appointment}
            type="cancel"
            title="Cancel Appointment"
            description="Are you sure you want to cancel your appointment?"
          />
        </div>
      );
    },
  },
];

Affected component/components

Drop-down and Data-table

karanBRAVO commented 1 month ago

Hi @mehdikhan55, can you assign this issue to me. I love to fix this issue.

abdelbassetbabeker commented 4 weeks ago

I am countering same issue any solution

Dropdown Menu Pointer Events Not Resetting

The dropdown menu component is not resetting the pointer-events style on the after interacting with the dropdown. This makes the body unresponsive.

Steps to Reproduce Open the dropdown menu. Select an item that triggers a dialog. Cancel the dialog.

Expected Behavior The should allow interactions after the dialog is canceled.

Actual Behavior The retains the style pointer-events: none;, preventing any interactions.

Proof:

Before opening the dropdown:

After canceling the dialog: Temporary Solution Manually setting pointer-events to auto resolves the issue. Suggested Fix Implement a mechanism to reset pointer-events to auto on the when the dropdown is closed or when the dialog is canceled.
harrisrobin commented 3 weeks ago

As @abdelbassetbabeker suggested, i simply do the following for now:

  const handleOpenChange = (open: boolean) => {
    if (!open) {
      //remove the style="pointer-events: none;" from the body
      document.body.style.pointerEvents = "auto"
    }
  }

Use this on the DropdownMenu root's onOpenChange handler

This seems to be a bug in radix-ui.

luchillo17 commented 1 week ago

As @abdelbassetbabeker suggested, i simply do the following for now:

  const handleOpenChange = (open: boolean) => {
    if (!open) {
      //remove the style="pointer-events: none;" from the body
      document.body.style.pointerEvents = "auto"
    }
  }

Use this on the DropdownMenu root's onOpenChange handler

This seems to be a bug in radix-ui.

I'm questioning the sanity of my own solution, I just give time for the current dialog to close (finish the animation) before closing the parent (timeout with 300ms before calling my parent's onSuccess?.())...

  const [open, setOpen] = useState(false);

  const justificationRef = useRef<HTMLDivElement>(null);
  const code = row.original.type;

  const copyToClipboard = async () => {
    if (justificationRef.current === null) {
      return;
    }

    try {
      await copyElementContent(justificationRef.current);

      setOpen(false);

      if (!isDropdownItem) {
        row.toggleSelected(true);
      }

      toast.success('Justification copied to clipboard.');

      setTimeout(() => {
        // Trigger parent success after the dialog close animation is done
        // to prevent page level unresponsiveness
        onSuccess?.();
      }, 300);
    } catch (err) {
      toast.error('Failed to copy text.', {
        description: (err as Error)?.message,
      });
    }
  };

  return (
    <Dialog open={open} onOpenChange={setOpen}>