diff --git a/ui/components/shadcn/dropdown/dropdown.test.tsx b/ui/components/shadcn/dropdown/dropdown.test.tsx new file mode 100644 index 0000000000..72f8f13410 --- /dev/null +++ b/ui/components/shadcn/dropdown/dropdown.test.tsx @@ -0,0 +1,114 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "./dropdown"; + +function renderActionsDropdown() { + return render( + + Open actions + + Edit + Delete + + , + ); +} + +describe("DropdownMenu", () => { + it("renders open menu content through the Radix DropdownMenu API", () => { + // Given + renderActionsDropdown(); + + // When + const menu = screen.getByRole("menu"); + + // Then + expect(menu).toBeVisible(); + expect(menu).toHaveAttribute("data-slot", "dropdown-menu-content"); + expect(screen.getByRole("menuitem", { name: "Edit" })).toBeVisible(); + expect(screen.getByRole("menuitem", { name: "Delete" })).toBeVisible(); + }); + + it("uses an intentional open and close motion contract", () => { + // Given + renderActionsDropdown(); + + // When + const menu = screen.getByRole("menu"); + + // Then + expect(menu).toHaveClass( + "origin-(--radix-dropdown-menu-content-transform-origin)", + "duration-200", + "ease-out", + "data-[state=open]:animate-in", + "data-[state=open]:fade-in-0", + "data-[state=open]:zoom-in-95", + "data-[state=closed]:animate-out", + "data-[state=closed]:fade-out-0", + "data-[state=closed]:zoom-out-95", + "data-[state=closed]:duration-100", + "data-[state=closed]:ease-in", + ); + }); + + it("removes transform-heavy menu motion for reduced-motion users", () => { + // Given + renderActionsDropdown(); + + // When + const menu = screen.getByRole("menu"); + + // Then + expect(menu).toHaveClass( + "motion-reduce:animate-none", + "motion-reduce:transform-none", + "motion-reduce:transition-none", + ); + }); + + it("applies the same motion contract to submenu content", () => { + // Given + render( + + Open actions + + + More actions + + Archive + + + + , + ); + + // When + const submenuContent = screen + .getByRole("menuitem", { name: "Archive" }) + .closest("[data-slot='dropdown-menu-sub-content']"); + + // Then + expect(submenuContent).toHaveClass( + "origin-(--radix-dropdown-menu-content-transform-origin)", + "duration-200", + "ease-out", + "data-[state=open]:animate-in", + "data-[state=closed]:animate-out", + "data-[state=closed]:duration-100", + "data-[state=closed]:ease-in", + "motion-reduce:animate-none", + "motion-reduce:transform-none", + "motion-reduce:transition-none", + ); + }); +}); diff --git a/ui/components/shadcn/dropdown/dropdown.tsx b/ui/components/shadcn/dropdown/dropdown.tsx index c6cf066250..13b43522c6 100644 --- a/ui/components/shadcn/dropdown/dropdown.tsx +++ b/ui/components/shadcn/dropdown/dropdown.tsx @@ -42,7 +42,7 @@ function DropdownMenuContent({ data-slot="dropdown-menu-content" sideOffset={sideOffset} className={cn( - "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md duration-200 ease-out data-[state=closed]:duration-100 data-[state=closed]:ease-in motion-reduce:transform-none motion-reduce:animate-none motion-reduce:transition-none", className, )} {...props} @@ -227,7 +227,7 @@ function DropdownMenuSubContent({