diff --git a/ui/components/shadcn/combobox/combobox.test.tsx b/ui/components/shadcn/combobox/combobox.test.tsx
index f5ea9e492d..928472f0eb 100644
--- a/ui/components/shadcn/combobox/combobox.test.tsx
+++ b/ui/components/shadcn/combobox/combobox.test.tsx
@@ -68,6 +68,39 @@ describe("Combobox", () => {
);
});
+ it("animates option rows and selected check indicators as internal feedback", async () => {
+ // Given
+ const user = userEvent.setup();
+ render(
+ ,
+ );
+
+ // When
+ await user.click(screen.getByRole("combobox", { name: /aws/i }));
+ const selectedItem = screen.getByRole("option", { name: /aws/i });
+ const unselectedItem = screen.getByRole("option", { name: /azure/i });
+ const selectedCheck = selectedItem.querySelector("svg");
+ const unselectedCheck = unselectedItem.querySelector("svg");
+
+ // Then
+ expect(selectedItem).toHaveClass(
+ "transition-[background-color,color]",
+ "duration-150",
+ "ease-out",
+ "motion-reduce:transition-none",
+ );
+ expect(selectedCheck).toHaveClass(
+ "transition-[opacity,scale]",
+ "duration-150",
+ "ease-out",
+ "scale-100",
+ "opacity-100",
+ "motion-reduce:scale-100",
+ "motion-reduce:transition-none",
+ );
+ expect(unselectedCheck).toHaveClass("scale-95", "opacity-0");
+ });
+
it("opens with the shared Popover content motion contract", async () => {
// Given
const user = userEvent.setup();
diff --git a/ui/components/shadcn/combobox/combobox.tsx b/ui/components/shadcn/combobox/combobox.tsx
index 8279a4147b..90493f62aa 100644
--- a/ui/components/shadcn/combobox/combobox.tsx
+++ b/ui/components/shadcn/combobox/combobox.tsx
@@ -152,7 +152,7 @@ export function Combobox({
value={selectedOption.value}
onSelect={handleSelect}
>
-
+
{selectedOption.label}
@@ -177,10 +177,10 @@ export function Combobox({
>
{option.label}
@@ -205,8 +205,10 @@ export function Combobox({
>
{option.label}
diff --git a/ui/components/shadcn/command.tsx b/ui/components/shadcn/command.tsx
index b7d9067f96..7afbc4eef1 100644
--- a/ui/components/shadcn/command.tsx
+++ b/ui/components/shadcn/command.tsx
@@ -152,7 +152,7 @@ function CommandItem({
{
);
});
+ it("animates the close affordance as internal dialog feedback", () => {
+ // Given
+ renderOpenDialog();
+
+ // When
+ const closeButton = screen.getByRole("button", { name: /close/i });
+ const closeIcon = closeButton.querySelector("svg");
+
+ // Then
+ expect(closeButton).toHaveClass(
+ "transition-[opacity,scale,background-color,color]",
+ "duration-150",
+ "ease-out",
+ "active:scale-95",
+ "motion-reduce:active:scale-100",
+ "motion-reduce:transition-none",
+ );
+ expect(closeIcon).toHaveClass(
+ "transition-transform",
+ "duration-150",
+ "ease-out",
+ "group-hover/dialog-close:rotate-90",
+ "motion-reduce:rotate-0",
+ "motion-reduce:transition-none",
+ );
+ });
+
it("uses an intentional content motion contract", () => {
// Given
renderOpenDialog();
diff --git a/ui/components/shadcn/dialog.tsx b/ui/components/shadcn/dialog.tsx
index ee275f5c5f..24379960b1 100644
--- a/ui/components/shadcn/dialog.tsx
+++ b/ui/components/shadcn/dialog.tsx
@@ -70,9 +70,9 @@ function DialogContent({
{showCloseButton && (
-
+
Close
)}
diff --git a/ui/components/shadcn/dropdown/dropdown.test.tsx b/ui/components/shadcn/dropdown/dropdown.test.tsx
index 72f8f13410..484e48384e 100644
--- a/ui/components/shadcn/dropdown/dropdown.test.tsx
+++ b/ui/components/shadcn/dropdown/dropdown.test.tsx
@@ -3,8 +3,11 @@ import { describe, expect, it } from "vitest";
import {
DropdownMenu,
+ DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
@@ -76,6 +79,108 @@ describe("DropdownMenu", () => {
);
});
+ it("animates checkbox and radio item indicators as internal state feedback", () => {
+ // Given
+ render(
+
+ Open filters
+
+
+ Show muted
+
+
+ Failed
+
+
+ ,
+ );
+
+ // When
+ const checkboxItem = screen.getByRole("menuitemcheckbox", {
+ name: "Show muted",
+ });
+ const radioItem = screen.getByRole("menuitemradio", { name: "Failed" });
+ const checkboxIndicator = checkboxItem.querySelector("span");
+ const checkboxIcon = checkboxItem.querySelector("svg");
+ const radioIndicator = radioItem.querySelector("span");
+ const radioIcon = radioItem.querySelector("svg");
+
+ // Then
+ expect(checkboxItem).toHaveClass(
+ "transition-colors",
+ "duration-150",
+ "ease-out",
+ "motion-reduce:transition-none",
+ );
+ expect(checkboxIndicator).toHaveClass(
+ "transition-[opacity,scale]",
+ "duration-150",
+ "ease-out",
+ "motion-reduce:scale-100",
+ "motion-reduce:transition-none",
+ );
+ expect(checkboxIcon).toHaveClass(
+ "animate-in",
+ "zoom-in-75",
+ "motion-reduce:animate-none",
+ );
+ expect(radioItem).toHaveClass(
+ "transition-colors",
+ "duration-150",
+ "ease-out",
+ "motion-reduce:transition-none",
+ );
+ expect(radioIndicator).toHaveClass(
+ "transition-[opacity,scale]",
+ "duration-150",
+ "ease-out",
+ "motion-reduce:scale-100",
+ "motion-reduce:transition-none",
+ );
+ expect(radioIcon).toHaveClass(
+ "animate-in",
+ "zoom-in-75",
+ "motion-reduce:animate-none",
+ );
+ });
+
+ it("animates submenu chevrons as internal open-state feedback", () => {
+ // Given
+ render(
+
+ Open actions
+
+
+ More actions
+
+ Archive
+
+
+
+ ,
+ );
+
+ // When
+ const subTrigger = screen.getByRole("menuitem", { name: "More actions" });
+ const chevron = subTrigger.querySelector("svg");
+
+ // Then
+ expect(subTrigger).toHaveClass(
+ "transition-colors",
+ "duration-150",
+ "ease-out",
+ "motion-reduce:transition-none",
+ );
+ expect(chevron).toHaveClass(
+ "transition-[translate,opacity]",
+ "duration-150",
+ "ease-out",
+ "group-data-[state=open]:translate-x-0.5",
+ "motion-reduce:translate-x-0",
+ "motion-reduce:transition-none",
+ );
+ });
+
it("applies the same motion contract to submenu content", () => {
// Given
render(
diff --git a/ui/components/shadcn/dropdown/dropdown.tsx b/ui/components/shadcn/dropdown/dropdown.tsx
index 13b43522c6..a30de7c57c 100644
--- a/ui/components/shadcn/dropdown/dropdown.tsx
+++ b/ui/components/shadcn/dropdown/dropdown.tsx
@@ -92,15 +92,15 @@ function DropdownMenuCheckboxItem({
-
+
-
+
{children}
@@ -128,14 +128,14 @@ function DropdownMenuRadioItem({
-
+
-
+
{children}
@@ -208,13 +208,13 @@ function DropdownMenuSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
- "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground group flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors duration-150 ease-out select-none data-[inset]:pl-8 motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
-
+
);
}
diff --git a/ui/components/shadcn/select/select.test.tsx b/ui/components/shadcn/select/select.test.tsx
index 366466f425..3c77931dcb 100644
--- a/ui/components/shadcn/select/select.test.tsx
+++ b/ui/components/shadcn/select/select.test.tsx
@@ -179,6 +179,31 @@ describe("Select", () => {
expect(trigger).toHaveAttribute("data-closing", "true");
});
+ it("animates option rows and selected check indicators as internal feedback", () => {
+ // Given
+ renderTypeSelect({ open: true });
+
+ // When
+ const selectedOption = screen.getByRole("option", { name: "All Types" });
+ const checkIcon = selectedOption.querySelector("svg");
+
+ // Then
+ expect(selectedOption).toHaveClass(
+ "transition-colors",
+ "duration-150",
+ "ease-out",
+ "motion-reduce:transition-none",
+ );
+ expect(checkIcon).toHaveClass(
+ "animate-in",
+ "fade-in-0",
+ "zoom-in-75",
+ "duration-150",
+ "ease-out",
+ "motion-reduce:animate-none",
+ );
+ });
+
it("uses explicit open and close motion classes", () => {
// Given
renderTypeSelect({ open: true });
diff --git a/ui/components/shadcn/select/select.tsx b/ui/components/shadcn/select/select.tsx
index b0d64bd5c1..6f8b49f5e8 100644
--- a/ui/components/shadcn/select/select.tsx
+++ b/ui/components/shadcn/select/select.tsx
@@ -245,7 +245,7 @@ function SelectItem({
-
+
);