diff --git a/frontend/src/components/ui/button-variants.ts b/frontend/src/components/ui/button-variants.ts index 4464702..ec7fed7 100644 --- a/frontend/src/components/ui/button-variants.ts +++ b/frontend/src/components/ui/button-variants.ts @@ -1,7 +1,7 @@ import { cva } from "class-variance-authority" export const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-default [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { diff --git a/frontend/src/components/ui/button.test.tsx b/frontend/src/components/ui/button.test.tsx index b390aea..2fc7c1b 100644 --- a/frontend/src/components/ui/button.test.tsx +++ b/frontend/src/components/ui/button.test.tsx @@ -18,4 +18,14 @@ describe("Button component", () => { button = screen.getByRole("button", { name: /destructive/i }) expect(button).toHaveClass("cursor-pointer") }) + + it("renders with a default cursor when disabled", () => { + render() + const button = screen.getByRole("button", { name: /disabled button/i }) + expect(button).toBeDisabled() + // It should have cursor-pointer but also disabled:pointer-events-none + // which prevents the cursor from changing. + // We can also add disabled:cursor-default for clarity. + expect(button).toHaveClass("disabled:pointer-events-none") + }) }) diff --git a/frontend/src/components/ui/dropdown-menu.test.tsx b/frontend/src/components/ui/dropdown-menu.test.tsx new file mode 100644 index 0000000..347832c --- /dev/null +++ b/frontend/src/components/ui/dropdown-menu.test.tsx @@ -0,0 +1,59 @@ +import { render, screen } from "@testing-library/react" +import { describe, expect, it } from "vitest" +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, + DropdownMenuPortal +} from "./dropdown-menu" + +describe("DropdownMenu components", () => { + it("DropdownMenuItem should have cursor-pointer when enabled", () => { + render( + + + Enabled Item + + + ) + const item = screen.getByText("Enabled Item") + expect(item).toHaveClass("cursor-pointer") + }) + + it("DropdownMenuSubTrigger should have cursor-pointer when enabled", () => { + render( + + + + Sub Trigger + + + + + + + ) + const trigger = screen.getByText("Sub Trigger") + expect(trigger).toHaveClass("cursor-pointer") + }) + + it("DropdownMenuItem should not have cursor-pointer behavior when disabled", () => { + render( + + + Disabled Item + + + ) + const item = screen.getByText("Disabled Item") + // Radix sets data-disabled attribute + expect(item).toHaveAttribute("data-disabled") + // If pointer-events-none is missing, we check for cursor class + // We expect the final class to have disabled:cursor-default or similar, + // or rely on pointer-events-none to block interactions. + }) +}) diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx index f3f0103..449e558 100644 --- a/frontend/src/components/ui/dropdown-menu.tsx +++ b/frontend/src/components/ui/dropdown-menu.tsx @@ -27,7 +27,7 @@ const DropdownMenuSubTrigger = React.forwardRef< { const action = screen.getByRole("button", { name: /group action/i }) expect(action).toHaveClass("cursor-pointer") }) + + it("SidebarGroupAction should have cursor-default when disabled", () => { + render( + + + Disabled Group Action + + + ) + const action = screen.getByRole("button", { name: /disabled group action/i }) + expect(action).toHaveClass("disabled:cursor-default") + expect(action).toBeDisabled() + }) + + it("SidebarMenuAction should not show pointer when disabled", () => { + render( + + + + Button + Disabled Action + + + + ) + const action = screen.getByRole("button", { name: /disabled action/i }) + expect(action).toHaveClass("disabled:cursor-default") + expect(action).toBeDisabled() + }) }) diff --git a/frontend/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx index 19e5365..c7c9272 100644 --- a/frontend/src/components/ui/sidebar.tsx +++ b/frontend/src/components/ui/sidebar.tsx @@ -405,7 +405,7 @@ function SidebarGroupAction({ data-slot="sidebar-group-action" data-sidebar="group-action" className={cn( - "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 cursor-pointer", + "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 cursor-pointer disabled:cursor-default disabled:pointer-events-none disabled:opacity-50", // Increases the hit area of the button on mobile. "after:absolute after:-inset-2 md:after:hidden", "group-data-[collapsible=icon]:hidden", @@ -477,7 +477,7 @@ function SidebarMenuButton({ data-size={size} data-active={isActive} className={cn( - "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 cursor-pointer", + "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 cursor-pointer disabled:cursor-default", variant === "default" && "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", variant === "outline" && "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", size === "default" && "h-8 text-sm", @@ -528,7 +528,7 @@ function SidebarMenuAction({ data-slot="sidebar-menu-action" data-sidebar="menu-action" className={cn( - "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 cursor-pointer", + "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 cursor-pointer disabled:cursor-default disabled:pointer-events-none disabled:opacity-50", // Increases the hit area of the button on mobile. "after:absolute after:-inset-2 md:after:hidden", "peer-data-[size=sm]/menu-button:top-1",