feat(ui): add Drawer content transition

- Animate drawer overlay and directional content states
- Preserve reduced-motion behavior
- Cover Drawer motion with focused unit tests
This commit is contained in:
Alan Buscaglia
2026-06-05 14:54:45 +02:00
parent ccae4afe68
commit 87bd2e78a1
2 changed files with 94 additions and 6 deletions
+88
View File
@@ -0,0 +1,88 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerTitle,
DrawerTrigger,
} from "./drawer";
function renderOpenDrawer() {
return render(
<Drawer open>
<DrawerTrigger>Open drawer</DrawerTrigger>
<DrawerContent>
<DrawerTitle>Resource details</DrawerTitle>
<DrawerDescription>Review resource metadata</DrawerDescription>
</DrawerContent>
</Drawer>,
);
}
describe("Drawer", () => {
it("renders controlled content through the Vaul Drawer API", () => {
// Given
renderOpenDrawer();
// When
const drawer = screen.getByRole("dialog", { name: "Resource details" });
// Then
expect(drawer).toBeVisible();
expect(drawer).toHaveAttribute("data-slot", "drawer-content");
expect(screen.getByText("Review resource metadata")).toBeVisible();
});
it("uses an intentional overlay motion contract", () => {
// Given
renderOpenDrawer();
// When
const overlay = document.querySelector("[data-slot='drawer-overlay']");
// Then
expect(overlay).toHaveClass(
"duration-200",
"ease-out",
"data-[state=open]:animate-in",
"data-[state=open]:fade-in-0",
"data-[state=closed]:animate-out",
"data-[state=closed]:fade-out-0",
"data-[state=closed]:duration-100",
"data-[state=closed]:ease-in",
"motion-reduce:animate-none",
"motion-reduce:transition-none",
);
});
it("uses direction-aware drawer content motion", () => {
// Given
renderOpenDrawer();
// When
const drawer = screen.getByRole("dialog", { name: "Resource details" });
// Then
expect(drawer).toHaveClass(
"duration-200",
"ease-out",
"data-[state=open]:animate-in",
"data-[state=closed]:animate-out",
"data-[state=closed]:duration-100",
"data-[state=closed]:ease-in",
"data-[vaul-drawer-direction=bottom]:slide-in-from-bottom-full",
"data-[vaul-drawer-direction=bottom]:data-[state=closed]:slide-out-to-bottom-full",
"data-[vaul-drawer-direction=top]:slide-in-from-top-full",
"data-[vaul-drawer-direction=top]:data-[state=closed]:slide-out-to-top-full",
"data-[vaul-drawer-direction=right]:slide-in-from-right-full",
"data-[vaul-drawer-direction=right]:data-[state=closed]:slide-out-to-right-full",
"data-[vaul-drawer-direction=left]:slide-in-from-left-full",
"data-[vaul-drawer-direction=left]:data-[state=closed]:slide-out-to-left-full",
"motion-reduce:animate-none",
"motion-reduce:transform-none",
"motion-reduce:transition-none",
);
});
});
+6 -6
View File
@@ -35,7 +35,7 @@ function DrawerOverlay({
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 duration-200 ease-out data-[state=closed]:duration-100 data-[state=closed]:ease-in motion-reduce:animate-none motion-reduce:transition-none",
className,
)}
{...props}
@@ -54,11 +54,11 @@ function DrawerContent({
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=right]:border-l-border-neutral-secondary data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:border-l",
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:border-r",
"group/drawer-content bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex h-auto flex-col 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",
"data-[vaul-drawer-direction=top]:slide-in-from-top-full data-[vaul-drawer-direction=top]:data-[state=closed]:slide-out-to-top-full data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
"data-[vaul-drawer-direction=bottom]:slide-in-from-bottom-full data-[vaul-drawer-direction=bottom]:data-[state=closed]:slide-out-to-bottom-full data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=right]:border-l-border-neutral-secondary data-[vaul-drawer-direction=right]:slide-in-from-right-full data-[vaul-drawer-direction=right]:data-[state=closed]:slide-out-to-right-full data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:border-l",
"data-[vaul-drawer-direction=left]:slide-in-from-left-full data-[vaul-drawer-direction=left]:data-[state=closed]:slide-out-to-left-full data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:border-r",
className,
)}
{...props}