From 6adcd95358fc8f0f04c71ae55fc7b12621b541f9 Mon Sep 17 00:00:00 2001 From: BinBandit Date: Wed, 11 Mar 2026 19:47:53 +1100 Subject: [PATCH] fix(web): restore pointer cursors for sidebar actions --- apps/web/src/components/Sidebar.tsx | 2 +- apps/web/src/components/ui/sidebar.test.tsx | 53 +++++++++++++++++++++ apps/web/src/components/ui/sidebar.tsx | 6 +-- 3 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/components/ui/sidebar.test.tsx diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 8b68c3b80..c9faceb4e 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -1531,7 +1531,7 @@ export default function Sidebar() { render={
} size="sm" isActive={isActive} - className={`h-7 w-full translate-x-0 cursor-default justify-start px-2 text-left select-none hover:bg-accent hover:text-foreground focus-visible:ring-0 ${ + className={`h-7 w-full translate-x-0 justify-start px-2 text-left select-none hover:bg-accent hover:text-foreground focus-visible:ring-0 ${ isSelected ? "bg-primary/15 text-foreground dark:bg-primary/10" : isActive diff --git a/apps/web/src/components/ui/sidebar.test.tsx b/apps/web/src/components/ui/sidebar.test.tsx new file mode 100644 index 000000000..124649236 --- /dev/null +++ b/apps/web/src/components/ui/sidebar.test.tsx @@ -0,0 +1,53 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; + +import { + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuSubButton, + SidebarProvider, +} from "./sidebar"; + +function renderSidebarButton(className?: string) { + return renderToStaticMarkup( + + Projects + , + ); +} + +describe("sidebar interactive cursors", () => { + it("uses a pointer cursor for menu buttons by default", () => { + const html = renderSidebarButton(); + + expect(html).toContain('data-slot="sidebar-menu-button"'); + expect(html).toContain("cursor-pointer"); + }); + + it("lets project drag handles override the default pointer cursor", () => { + const html = renderSidebarButton("cursor-grab"); + + expect(html).toContain("cursor-grab"); + expect(html).not.toContain("cursor-pointer"); + }); + + it("uses a pointer cursor for menu actions", () => { + const html = renderToStaticMarkup( + + + + , + ); + + expect(html).toContain('data-slot="sidebar-menu-action"'); + expect(html).toContain("cursor-pointer"); + }); + + it("uses a pointer cursor for submenu buttons", () => { + const html = renderToStaticMarkup( + }>Show more, + ); + + expect(html).toContain('data-slot="sidebar-menu-sub-button"'); + expect(html).toContain("cursor-pointer"); + }); +}); diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index 3876d3513..3d83d7472 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -748,7 +748,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { } const sidebarMenuButtonVariants = cva( - "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-lg 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:pe-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:not([class*='size-'])]:size-4 [&>svg]:shrink-0", + "peer/menu-button flex w-full cursor-pointer items-center gap-2 overflow-hidden rounded-lg 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:pe-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:not([class*='size-'])]:size-4 [&>svg]:shrink-0", { defaultVariants: { size: "default", @@ -832,7 +832,7 @@ function SidebarMenuAction({ }) { const defaultProps = { className: cn( - "absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-lg p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0", + "absolute top-1.5 right-1 flex aspect-square w-5 cursor-pointer items-center justify-center rounded-lg p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0", // Increases the hit area of the button on mobile. "after:-inset-2 after:absolute md:after:hidden", "peer-data-[size=sm]/menu-button:top-1", @@ -944,7 +944,7 @@ function SidebarMenuSubButton({ }) { const defaultProps = { className: cn( - "-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-lg px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring 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 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground", + "-translate-x-px flex h-7 min-w-0 cursor-pointer items-center gap-2 overflow-hidden rounded-lg px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring 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 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground", "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", size === "sm" && "text-xs", size === "md" && "text-sm",