From f1729b79dbcdfe0232f321cf1c587def4d821842 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 26 Feb 2026 13:36:21 +0000 Subject: [PATCH 1/7] add shadcn ui primitives for website Co-authored-by: Aiden Bai --- packages/website/components.json | 2 +- packages/website/components/ui/badge.tsx | 36 ++ packages/website/components/ui/button.tsx | 57 +++ packages/website/components/ui/card.tsx | 73 +++ .../website/components/ui/dropdown-menu.tsx | 170 +++++++ packages/website/components/ui/hover-card.tsx | 30 ++ packages/website/components/ui/input.tsx | 18 + .../website/components/ui/scroll-area.tsx | 41 ++ packages/website/components/ui/separator.tsx | 29 ++ packages/website/components/ui/table.tsx | 80 ++++ packages/website/components/ui/tabs.tsx | 64 +++ packages/website/package.json | 6 + pnpm-lock.yaml | 417 +++++++++++++++++- 13 files changed, 1014 insertions(+), 9 deletions(-) create mode 100644 packages/website/components/ui/badge.tsx create mode 100644 packages/website/components/ui/button.tsx create mode 100644 packages/website/components/ui/card.tsx create mode 100644 packages/website/components/ui/dropdown-menu.tsx create mode 100644 packages/website/components/ui/hover-card.tsx create mode 100644 packages/website/components/ui/input.tsx create mode 100644 packages/website/components/ui/scroll-area.tsx create mode 100644 packages/website/components/ui/separator.tsx create mode 100644 packages/website/components/ui/table.tsx create mode 100644 packages/website/components/ui/tabs.tsx diff --git a/packages/website/components.json b/packages/website/components.json index b7b9791c7..3dfe5ff00 100644 --- a/packages/website/components.json +++ b/packages/website/components.json @@ -13,7 +13,7 @@ "iconLibrary": "lucide", "aliases": { "components": "@/components", - "utils": "@/lib/utils", + "utils": "@/utils/cn", "ui": "@/components/ui", "lib": "@/lib", "hooks": "@/hooks" diff --git a/packages/website/components/ui/badge.tsx b/packages/website/components/ui/badge.tsx new file mode 100644 index 000000000..844cc60c9 --- /dev/null +++ b/packages/website/components/ui/badge.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { cva, type VariantProps } from "class-variance-authority"; +import type { HTMLAttributes, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium transition-colors", + { + variants: { + variant: { + default: "border-white/15 bg-white/5 text-white/85", + secondary: "border-[#ff4fff]/30 bg-[#330039] text-[#ff4fff]", + success: "border-emerald-300/25 bg-emerald-500/10 text-emerald-300", + danger: "border-red-300/25 bg-red-500/10 text-red-300", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +interface BadgeProps + extends HTMLAttributes, + VariantProps {} + +export const Badge = ({ + className, + variant, + ...props +}: BadgeProps): ReactElement => ( +
+); + +Badge.displayName = "Badge"; diff --git a/packages/website/components/ui/button.tsx b/packages/website/components/ui/button.tsx new file mode 100644 index 000000000..78ef3e56a --- /dev/null +++ b/packages/website/components/ui/button.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import type { ButtonHTMLAttributes, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-2 focus-visible:ring-[#ff4fff]/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black", + { + variants: { + variant: { + default: "bg-white text-black hover:bg-white/90", + secondary: "border border-white/20 bg-white/5 text-white hover:bg-white/10", + ghost: "text-white/70 hover:bg-white/10 hover:text-white", + link: "text-white underline underline-offset-4 hover:text-white/80", + destructive: + "bg-red-500 text-white hover:bg-red-500/90 focus-visible:ring-red-400/80", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 px-3 text-xs", + lg: "h-10 px-6 text-base", + icon: "size-9 p-0", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +interface ButtonProps + extends ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +export const Button = ({ + className, + variant, + size, + asChild = false, + ...props +}: ButtonProps): ReactElement => { + const Component = asChild ? Slot : "button"; + + return ( + + ); +}; + +Button.displayName = "Button"; diff --git a/packages/website/components/ui/card.tsx b/packages/website/components/ui/card.tsx new file mode 100644 index 000000000..b171e57d3 --- /dev/null +++ b/packages/website/components/ui/card.tsx @@ -0,0 +1,73 @@ +"use client"; + +import type { ComponentProps, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +interface CardProps extends ComponentProps<"div"> {} + +export const Card = ({ className, ...props }: CardProps): ReactElement => ( +
+); + +Card.displayName = "Card"; + +interface CardHeaderProps extends ComponentProps<"div"> {} + +export const CardHeader = ({ + className, + ...props +}: CardHeaderProps): ReactElement => ( +
+); + +CardHeader.displayName = "CardHeader"; + +interface CardTitleProps extends ComponentProps<"h3"> {} + +export const CardTitle = ({ + className, + ...props +}: CardTitleProps): ReactElement => ( +

+); + +CardTitle.displayName = "CardTitle"; + +interface CardDescriptionProps extends ComponentProps<"p"> {} + +export const CardDescription = ({ + className, + ...props +}: CardDescriptionProps): ReactElement => ( +

+); + +CardDescription.displayName = "CardDescription"; + +interface CardContentProps extends ComponentProps<"div"> {} + +export const CardContent = ({ + className, + ...props +}: CardContentProps): ReactElement => ( +

+); + +CardContent.displayName = "CardContent"; + +interface CardFooterProps extends ComponentProps<"div"> {} + +export const CardFooter = ({ + className, + ...props +}: CardFooterProps): ReactElement => ( +
+); + +CardFooter.displayName = "CardFooter"; diff --git a/packages/website/components/ui/dropdown-menu.tsx b/packages/website/components/ui/dropdown-menu.tsx new file mode 100644 index 000000000..cca4b421f --- /dev/null +++ b/packages/website/components/ui/dropdown-menu.tsx @@ -0,0 +1,170 @@ +"use client"; + +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight } from "lucide-react"; +import type { ComponentProps, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +export const DropdownMenu = DropdownMenuPrimitive.Root; +export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +export const DropdownMenuGroup = DropdownMenuPrimitive.Group; +export const DropdownMenuPortal = DropdownMenuPrimitive.Portal; +export const DropdownMenuSub = DropdownMenuPrimitive.Sub; +export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +interface DropdownMenuSubTriggerProps + extends ComponentProps { + inset?: boolean; +} + +export const DropdownMenuSubTrigger = ({ + className, + inset, + children, + ...props +}: DropdownMenuSubTriggerProps): ReactElement => ( + + {children} + + +); + +DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger"; + +interface DropdownMenuSubContentProps + extends ComponentProps {} + +export const DropdownMenuSubContent = ({ + className, + ...props +}: DropdownMenuSubContentProps): ReactElement => ( + +); + +DropdownMenuSubContent.displayName = "DropdownMenuSubContent"; + +interface DropdownMenuContentProps + extends ComponentProps {} + +export const DropdownMenuContent = ({ + className, + sideOffset = 4, + ...props +}: DropdownMenuContentProps): ReactElement => ( + + + +); + +DropdownMenuContent.displayName = "DropdownMenuContent"; + +interface DropdownMenuItemProps + extends ComponentProps { + inset?: boolean; +} + +export const DropdownMenuItem = ({ + className, + inset, + ...props +}: DropdownMenuItemProps): ReactElement => ( + +); + +DropdownMenuItem.displayName = "DropdownMenuItem"; + +interface DropdownMenuCheckboxItemProps + extends ComponentProps {} + +export const DropdownMenuCheckboxItem = ({ + className, + children, + checked, + ...props +}: DropdownMenuCheckboxItemProps): ReactElement => ( + + + + + + + {children} + +); + +DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem"; + +interface DropdownMenuLabelProps + extends ComponentProps { + inset?: boolean; +} + +export const DropdownMenuLabel = ({ + className, + inset, + ...props +}: DropdownMenuLabelProps): ReactElement => ( + +); + +DropdownMenuLabel.displayName = "DropdownMenuLabel"; + +interface DropdownMenuSeparatorProps + extends ComponentProps {} + +export const DropdownMenuSeparator = ({ + className, + ...props +}: DropdownMenuSeparatorProps): ReactElement => ( + +); + +DropdownMenuSeparator.displayName = "DropdownMenuSeparator"; + +interface DropdownMenuShortcutProps extends ComponentProps<"span"> {} + +export const DropdownMenuShortcut = ({ + className, + ...props +}: DropdownMenuShortcutProps): ReactElement => ( + +); + +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; diff --git a/packages/website/components/ui/hover-card.tsx b/packages/website/components/ui/hover-card.tsx new file mode 100644 index 000000000..77fa673c0 --- /dev/null +++ b/packages/website/components/ui/hover-card.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; +import type { ComponentProps, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +export const HoverCard = HoverCardPrimitive.Root; +export const HoverCardTrigger = HoverCardPrimitive.Trigger; + +interface HoverCardContentProps + extends ComponentProps {} + +export const HoverCardContent = ({ + className, + align = "center", + sideOffset = 4, + ...props +}: HoverCardContentProps): ReactElement => ( + +); + +HoverCardContent.displayName = "HoverCardContent"; diff --git a/packages/website/components/ui/input.tsx b/packages/website/components/ui/input.tsx new file mode 100644 index 000000000..f5d26540a --- /dev/null +++ b/packages/website/components/ui/input.tsx @@ -0,0 +1,18 @@ +"use client"; + +import type { InputHTMLAttributes, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +interface InputProps extends InputHTMLAttributes {} + +export const Input = ({ className, ...props }: InputProps): ReactElement => ( + +); + +Input.displayName = "Input"; diff --git a/packages/website/components/ui/scroll-area.tsx b/packages/website/components/ui/scroll-area.tsx new file mode 100644 index 000000000..5f1c11f5f --- /dev/null +++ b/packages/website/components/ui/scroll-area.tsx @@ -0,0 +1,41 @@ +"use client"; + +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; +import type { ComponentProps, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +interface ScrollAreaProps extends ComponentProps {} + +export const ScrollArea = ({ + className, + children, + ...props +}: ScrollAreaProps): ReactElement => ( + + + {children} + + + + +); + +ScrollArea.displayName = "ScrollArea"; + +interface ScrollBarProps extends ComponentProps {} + +export const ScrollBar = ({ className, orientation = "vertical", ...props }: ScrollBarProps): ReactElement => ( + + + +); + +ScrollBar.displayName = "ScrollBar"; diff --git a/packages/website/components/ui/separator.tsx b/packages/website/components/ui/separator.tsx new file mode 100644 index 000000000..0d0abc28d --- /dev/null +++ b/packages/website/components/ui/separator.tsx @@ -0,0 +1,29 @@ +"use client"; + +import type { HTMLAttributes, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +interface SeparatorProps extends HTMLAttributes { + orientation?: "horizontal" | "vertical"; + decorative?: boolean; +} + +export const Separator = ({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: SeparatorProps): ReactElement => ( +
+); + +Separator.displayName = "Separator"; diff --git a/packages/website/components/ui/table.tsx b/packages/website/components/ui/table.tsx new file mode 100644 index 000000000..b984b5812 --- /dev/null +++ b/packages/website/components/ui/table.tsx @@ -0,0 +1,80 @@ +"use client"; + +import type { ComponentProps, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +interface TableProps extends ComponentProps<"table"> {} + +export const Table = ({ className, ...props }: TableProps): ReactElement => ( +
+ + +); + +Table.displayName = "Table"; + +interface TableHeaderProps extends ComponentProps<"thead"> {} + +export const TableHeader = ({ + className, + ...props +}: TableHeaderProps): ReactElement => ( + +); + +TableHeader.displayName = "TableHeader"; + +interface TableBodyProps extends ComponentProps<"tbody"> {} + +export const TableBody = ({ className, ...props }: TableBodyProps): ReactElement => ( + +); + +TableBody.displayName = "TableBody"; + +interface TableRowProps extends ComponentProps<"tr"> {} + +export const TableRow = ({ className, ...props }: TableRowProps): ReactElement => ( + +); + +TableRow.displayName = "TableRow"; + +interface TableHeadProps extends ComponentProps<"th"> {} + +export const TableHead = ({ className, ...props }: TableHeadProps): ReactElement => ( +
+); + +TableHead.displayName = "TableHead"; + +interface TableCellProps extends ComponentProps<"td"> {} + +export const TableCell = ({ className, ...props }: TableCellProps): ReactElement => ( + +); + +TableCell.displayName = "TableCell"; + +interface TableCaptionProps extends ComponentProps<"caption"> {} + +export const TableCaption = ({ + className, + ...props +}: TableCaptionProps): ReactElement => ( +
+); + +TableCaption.displayName = "TableCaption"; diff --git a/packages/website/components/ui/tabs.tsx b/packages/website/components/ui/tabs.tsx new file mode 100644 index 000000000..118b9df62 --- /dev/null +++ b/packages/website/components/ui/tabs.tsx @@ -0,0 +1,64 @@ +"use client"; + +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import type { ComponentProps, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +interface TabsProps extends ComponentProps {} + +export const Tabs = ({ className, ...props }: TabsProps): ReactElement => ( + +); + +Tabs.displayName = "Tabs"; + +interface TabsListProps extends ComponentProps {} + +export const TabsList = ({ + className, + ...props +}: TabsListProps): ReactElement => ( + +); + +TabsList.displayName = "TabsList"; + +interface TabsTriggerProps extends ComponentProps {} + +export const TabsTrigger = ({ + className, + ...props +}: TabsTriggerProps): ReactElement => ( + +); + +TabsTrigger.displayName = "TabsTrigger"; + +interface TabsContentProps extends ComponentProps {} + +export const TabsContent = ({ + className, + ...props +}: TabsContentProps): ReactElement => ( + +); + +TabsContent.displayName = "TabsContent"; diff --git a/packages/website/package.json b/packages/website/package.json index 0ba9e9084..cd1d39c31 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -9,6 +9,12 @@ "lint": "oxlint" }, "dependencies": { + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@react-grab/design-system": "workspace:*", "@vercel/analytics": "^1.5.0", "@vercel/firewall": "^1.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 130462c85..a1d93880e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -752,6 +752,24 @@ importers: packages/website: dependencies: + '@radix-ui/react-collapsible': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-hover-card': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@react-grab/design-system': specifier: workspace:* version: link:../design-system @@ -2841,6 +2859,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: @@ -2954,6 +2985,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@2.2.6': resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} peerDependencies: @@ -3420,8 +3464,8 @@ packages: engines: {node: '>=20'} hasBin: true - '@sourcegraph/amp@0.0.1771838262-g353b96': - resolution: {integrity: sha512-uFAaUMheC6iSXII1rP9r16DPpkrFhklrXjNj0Wq3poUrAwjjJRE6BtnMcMC8RVfLGfwyc933fQh6QMumMoYkGw==} + '@sourcegraph/amp@0.0.1772107648-gbe4328': + resolution: {integrity: sha512-ekkK4vs/AWgEYVRanF6bZ7T0XrlF2jTEyIzDQaJVU/mugZ6dZsQ/d+8GX6si9DuvPXnelOh6cku74WcN198zmg==} engines: {node: '>=20'} hasBin: true @@ -9132,6 +9176,12 @@ snapshots: react: 19.0.1 react-dom: 19.0.1(react@19.0.1) + '@floating-ui/react-dom@2.1.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + '@floating-ui/utils@0.2.10': {} '@hono/node-server@1.19.9(hono@4.11.7)': @@ -9632,6 +9682,15 @@ snapshots: '@radix-ui/primitive@1.1.3': {} + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1) @@ -9670,6 +9729,22 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -9686,6 +9761,18 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.0.1) @@ -9698,12 +9785,24 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.7)(react@19.0.1)': dependencies: react: 19.0.1 optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-context@1.1.2(@types/react@19.2.7)(react@19.0.1)': dependencies: react: 19.0.1 @@ -9738,12 +9837,31 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-direction@1.1.1(@types/react@19.2.2)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-direction@1.1.1(@types/react@19.2.7)(react@19.0.1)': dependencies: react: 19.0.1 optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -9757,6 +9875,21 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -9772,12 +9905,29 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.7)(react@19.0.1)': dependencies: react: 19.0.1 optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.0.1) @@ -9789,6 +9939,30 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-id@1.1.1(@types/react@19.2.7)(react@19.0.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.0.1) @@ -9805,6 +9979,32 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.1) + aria-hidden: 1.2.6 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.2)(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -9831,6 +10031,24 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/rect': 1.1.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@19.0.1(react@19.0.1))(react@19.0.1) @@ -9849,6 +10067,16 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1) @@ -9859,6 +10087,16 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.0.1) @@ -9869,6 +10107,15 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.0.1) @@ -9887,6 +10134,23 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -9904,6 +10168,23 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/number': 1.1.1 @@ -9942,6 +10223,13 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-slot@1.2.3(@types/react@19.2.7)(react@19.0.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.0.1) @@ -9949,6 +10237,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-slot@1.2.4(@types/react@19.2.2)(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-slot@1.2.4(@types/react@19.2.7)(react@19.0.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.0.1) @@ -9956,6 +10251,22 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.2(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -10018,12 +10329,26 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.2(@types/react@19.2.7) + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.7)(react@19.0.1)': dependencies: react: 19.0.1 optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.7)(react@19.0.1)': dependencies: '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.0.1) @@ -10032,6 +10357,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.7)(react@19.0.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.0.1) @@ -10039,6 +10371,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.2)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.7)(react@19.0.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.0.1) @@ -10053,6 +10392,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.7)(react@19.0.1)': dependencies: react: 19.0.1 @@ -10065,6 +10410,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.2)(react@19.2.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.7)(react@19.0.1)': dependencies: '@radix-ui/rect': 1.1.1 @@ -10072,6 +10424,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.2)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.2 + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.7)(react@19.0.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.0.1) @@ -10107,14 +10466,14 @@ snapshots: '@remotion/media-parser': 4.0.424 '@remotion/studio': 4.0.424(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@remotion/studio-shared': 4.0.424(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - css-loader: 5.2.7(webpack@5.105.0) + css-loader: 5.2.7(webpack@5.105.0(esbuild@0.25.0)) esbuild: 0.25.0 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) react-refresh: 0.9.0 remotion: 4.0.424(react-dom@19.2.3(react@19.2.3))(react@19.2.3) source-map: 0.7.3 - style-loader: 4.0.0(webpack@5.105.0) + style-loader: 4.0.0(webpack@5.105.0(esbuild@0.25.0)) webpack: 5.105.0(esbuild@0.25.0) transitivePeerDependencies: - '@swc/core' @@ -10405,14 +10764,14 @@ snapshots: '@sourcegraph/amp-sdk@0.1.0-20251210081226-g90e3892': dependencies: - '@sourcegraph/amp': 0.0.1771838262-g353b96 + '@sourcegraph/amp': 0.0.1772107648-gbe4328 zod: 3.25.76 '@sourcegraph/amp@0.0.1767830505-ga62310': dependencies: '@napi-rs/keyring': 1.1.9 - '@sourcegraph/amp@0.0.1771838262-g353b96': + '@sourcegraph/amp@0.0.1772107648-gbe4328': dependencies: '@napi-rs/keyring': 1.1.9 @@ -11716,7 +12075,7 @@ snapshots: crypt@0.0.2: {} - css-loader@5.2.7(webpack@5.105.0): + css-loader@5.2.7(webpack@5.105.0(esbuild@0.25.0)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) loader-utils: 2.0.4 @@ -14198,6 +14557,14 @@ snapshots: react-refresh@0.9.0: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.1): + dependencies: + react: 19.2.1 + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.0.1): dependencies: react: 19.0.1 @@ -14206,6 +14573,17 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + react-remove-scroll@2.7.2(@types/react@19.2.2)(react@19.2.1): + dependencies: + react: 19.2.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.2)(react@19.2.1) + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.2)(react@19.2.1) + use-sidecar: 1.1.3(@types/react@19.2.2)(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.2 + react-remove-scroll@2.7.2(@types/react@19.2.7)(react@19.0.1): dependencies: react: 19.0.1 @@ -14225,6 +14603,14 @@ snapshots: react-dom: 19.0.1(react@19.0.1) react-transition-group: 4.4.5(react-dom@19.0.1(react@19.0.1))(react@19.0.1) + react-style-singleton@2.2.3(@types/react@19.2.2)(react@19.2.1): + dependencies: + get-nonce: 1.0.1 + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.0.1): dependencies: get-nonce: 1.0.1 @@ -14842,7 +15228,7 @@ snapshots: stubborn-utils@1.0.2: {} - style-loader@4.0.0(webpack@5.105.0): + style-loader@4.0.0(webpack@5.105.0(esbuild@0.25.0)): dependencies: webpack: 5.105.0(esbuild@0.25.0) @@ -15250,6 +15636,13 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.1): + dependencies: + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.0.1): dependencies: react: 19.0.1 @@ -15257,6 +15650,14 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + use-sidecar@1.1.3(@types/react@19.2.2)(react@19.2.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + use-sidecar@1.1.3(@types/react@19.2.7)(react@19.0.1): dependencies: detect-node-es: 1.1.0 From d2e2ade4210cbaa3a5c1d6410457675882cf6497 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 26 Feb 2026 13:44:52 +0000 Subject: [PATCH 2/7] refactor collapsible and scrollable to shadcn primitives Co-authored-by: Aiden Bai --- .../website/components/ui/collapsible.tsx | 55 ++++----- packages/website/components/ui/scrollable.tsx | 106 ++---------------- 2 files changed, 38 insertions(+), 123 deletions(-) diff --git a/packages/website/components/ui/collapsible.tsx b/packages/website/components/ui/collapsible.tsx index 3fc42b216..208688681 100644 --- a/packages/website/components/ui/collapsible.tsx +++ b/packages/website/components/ui/collapsible.tsx @@ -1,8 +1,10 @@ "use client"; import { useState, useMemo, type ReactElement, type ReactNode } from "react"; +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; import { motion, AnimatePresence } from "motion/react"; import { ChevronDown, ChevronRight } from "lucide-react"; +import { Button } from "@/components/ui/button"; interface CollapsibleProps { header: ReactNode; @@ -31,44 +33,47 @@ export const Collapsible = ({ return defaultExpanded; }, [manualExpanded, isStreaming, defaultExpanded, autoExpandOnStreaming]); - const handleToggle = () => { - setManualExpanded(!isExpanded); - }; - return ( -
-
- + + {isExpanded && ( - - {children} - + + + {children} + + )} - + ); }; diff --git a/packages/website/components/ui/scrollable.tsx b/packages/website/components/ui/scrollable.tsx index 0a3546d60..6b3a301c9 100644 --- a/packages/website/components/ui/scrollable.tsx +++ b/packages/website/components/ui/scrollable.tsx @@ -1,9 +1,11 @@ "use client"; -import { useRef, useState, useEffect, type ReactElement } from "react"; +import type { ReactElement, ReactNode } from "react"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { cn } from "@/utils/cn"; interface ScrollableProps { - children: React.ReactNode; + children: ReactNode; className?: string; maxHeight?: string; } @@ -13,104 +15,12 @@ export const Scrollable = ({ className = "", maxHeight = "200px", }: ScrollableProps): ReactElement => { - const contentRef = useRef(null); - const scrollbarRef = useRef(null); - const [isHovered, setIsHovered] = useState(false); - const [showScrollbar, setShowScrollbar] = useState(false); - const [scrollbarHeight, setScrollbarHeight] = useState(0); - const [scrollbarTop, setScrollbarTop] = useState(0); - - useEffect(() => { - const updateScrollbar = () => { - if (!contentRef.current) return; - - const element = contentRef.current; - const hasScroll = element.scrollHeight > element.clientHeight; - setShowScrollbar(hasScroll); - - if (hasScroll) { - const scrollRatio = element.clientHeight / element.scrollHeight; - const newScrollbarHeight = Math.max( - element.clientHeight * scrollRatio, - 20, - ); - setScrollbarHeight(newScrollbarHeight); - - const scrollPercentage = - element.scrollTop / (element.scrollHeight - element.clientHeight); - const maxScrollbarTop = element.clientHeight - newScrollbarHeight; - setScrollbarTop(scrollPercentage * maxScrollbarTop); - } - }; - - const element = contentRef.current; - if (!element) return; - - updateScrollbar(); - element.addEventListener("scroll", updateScrollbar); - window.addEventListener("resize", updateScrollbar); - - return () => { - element.removeEventListener("scroll", updateScrollbar); - window.removeEventListener("resize", updateScrollbar); - }; - }, [children]); - - const handleScrollbarMouseDown = (event: React.MouseEvent) => { - event.preventDefault(); - const startY = event.clientY; - const startScrollTop = contentRef.current?.scrollTop || 0; - - const handleMouseMove = (moveEvent: MouseEvent) => { - if (!contentRef.current) return; - - const deltaY = moveEvent.clientY - startY; - const scrollRatio = - contentRef.current.scrollHeight / contentRef.current.clientHeight; - contentRef.current.scrollTop = startScrollTop + deltaY * scrollRatio; - }; - - const handleMouseUp = () => { - document.removeEventListener("mousemove", handleMouseMove); - document.removeEventListener("mouseup", handleMouseUp); - }; - - document.addEventListener("mousemove", handleMouseMove); - document.addEventListener("mouseup", handleMouseUp); - }; - return ( -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > -
+
+ {children} -
-
- {showScrollbar && ( -
-
-
- )} + +
); }; From 6f10c523f806a5f1907b1a44e825ad4be53387b3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 26 Feb 2026 13:58:37 +0000 Subject: [PATCH 3/7] migrate reusable website controls to shadcn components Co-authored-by: Aiden Bai --- .../website/components/benchmark-tooltip.tsx | 106 +++++++++--------- .../website/components/blocks/code-block.tsx | 9 +- .../blocks/read-tool-call-block.tsx | 10 +- .../components/blog-article-layout.tsx | 31 +++-- packages/website/components/demo-footer.tsx | 29 +++-- packages/website/components/github-button.tsx | 19 ++-- .../components/grab-element-button.tsx | 19 ++-- packages/website/components/install-tabs.tsx | 74 ++++++------ .../website/components/table-of-contents.tsx | 11 +- .../website/components/view-docs-button.tsx | 19 ++-- 10 files changed, 175 insertions(+), 152 deletions(-) diff --git a/packages/website/components/benchmark-tooltip.tsx b/packages/website/components/benchmark-tooltip.tsx index 4f0cd11a8..eed1571ab 100644 --- a/packages/website/components/benchmark-tooltip.tsx +++ b/packages/website/components/benchmark-tooltip.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef, useEffect, type ReactElement } from "react"; +import { useState, type ReactElement } from "react"; import { motion, AnimatePresence, useReducedMotion } from "motion/react"; import Link from "next/link"; import { @@ -12,6 +12,11 @@ import { BENCHMARK_TOOLTIP_SPEEDUP_FACTOR, TOOLTIP_HOVER_DELAY_MS, } from "@/constants"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card"; interface BenchmarkTooltipProps { href: string; @@ -192,69 +197,58 @@ export const BenchmarkTooltip = ({ const shouldReduceMotion = Boolean(useReducedMotion()); const [isHovered, setIsHovered] = useState(false); const [isVisible, setIsVisible] = useState(false); - const timeoutRef = useRef(null); - - const handleMouseEnter = () => { - timeoutRef.current = setTimeout(() => { - setIsHovered(true); - setIsVisible(true); - }, TOOLTIP_HOVER_DELAY_MS); + const handleOpenChange = (open: boolean) => { + setIsHovered(open); + setIsVisible(open); }; - const handleMouseLeave = () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - setIsHovered(false); - setIsVisible(false); - }; - - useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - }; - }, []); - return ( - - - {children} - - - {isHovered && ( - -
-
+ + + + {children} + + + + + + {isHovered && ( + -
- - )} - - + + )} + + + ); }; diff --git a/packages/website/components/blocks/code-block.tsx b/packages/website/components/blocks/code-block.tsx index 546851591..c19ad7cec 100644 --- a/packages/website/components/blocks/code-block.tsx +++ b/packages/website/components/blocks/code-block.tsx @@ -8,6 +8,7 @@ import { } from "@/constants"; import { type StreamRenderedBlock } from "@/hooks/use-stream"; import { highlightCode } from "@/lib/shiki"; +import { Button } from "@/components/ui/button"; import { StreamingText } from "./streaming-text"; interface CodeBlockProps { @@ -75,17 +76,19 @@ export const CodeBlock = ({ block }: CodeBlockProps): ReactElement => { )} {shouldShowExpandButton && ( - + )}
); diff --git a/packages/website/components/blocks/read-tool-call-block.tsx b/packages/website/components/blocks/read-tool-call-block.tsx index 3498f4699..7b1a3a8f5 100644 --- a/packages/website/components/blocks/read-tool-call-block.tsx +++ b/packages/website/components/blocks/read-tool-call-block.tsx @@ -2,6 +2,7 @@ import { useState, useRef, useEffect, type ReactElement } from "react"; import { CLICK_FEEDBACK_DURATION_MS } from "@/constants"; +import { Button } from "@/components/ui/button"; interface ReadToolCallBlockProps { parameter: string; @@ -38,15 +39,18 @@ export const ReadToolCallBlock = ({ return (
{displayName} - +
); }; diff --git a/packages/website/components/blog-article-layout.tsx b/packages/website/components/blog-article-layout.tsx index 524ee31fd..06fc7c0d4 100644 --- a/packages/website/components/blog-article-layout.tsx +++ b/packages/website/components/blog-article-layout.tsx @@ -5,6 +5,7 @@ import Link from "next/link"; import { ArrowLeft } from "lucide-react"; import ReactGrabLogo from "@/public/logo.svg"; import { TableOfContents } from "@/components/table-of-contents"; +import { Button } from "@/components/ui/button"; interface TocHeading { id: string; @@ -42,20 +43,26 @@ export const BlogArticleLayout = ({
- - - Back to home - + + + Back to home + + · - - Read more posts - + Read more posts +
@@ -78,7 +85,7 @@ export const BlogArticleLayout = ({ href={author.url} target="_blank" rel="noopener noreferrer" - className="text-neutral-300 hover:text-white underline underline-offset-4" + className="rounded-sm text-neutral-300 underline underline-offset-4 transition-colors hover:text-white focus-visible:ring-2 focus-visible:ring-[#ff4fff]/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black" > {author.name} diff --git a/packages/website/components/demo-footer.tsx b/packages/website/components/demo-footer.tsx index bdcd2736c..2e788d1d8 100644 --- a/packages/website/components/demo-footer.tsx +++ b/packages/website/components/demo-footer.tsx @@ -2,6 +2,7 @@ import { type ReactElement } from "react"; import { RotateCcw } from "lucide-react"; +import { Button } from "@/components/ui/button"; export const DemoFooter = (): ReactElement => { const handleRestartClick = () => { @@ -17,28 +18,24 @@ export const DemoFooter = (): ReactElement => { return (
- + · - - blog - {" "} + {" "} ·{" "} - - changelog - +
); }; diff --git a/packages/website/components/github-button.tsx b/packages/website/components/github-button.tsx index 9238e44ad..a345409a4 100644 --- a/packages/website/components/github-button.tsx +++ b/packages/website/components/github-button.tsx @@ -1,17 +1,20 @@ import { type ReactElement } from "react"; import { IconGithub } from "./icons/icon-github"; +import { Button } from "@/components/ui/button"; export const GithubButton = (): ReactElement => { return ( - - - Star on GitHub - + + + Star on GitHub + + ); }; diff --git a/packages/website/components/grab-element-button.tsx b/packages/website/components/grab-element-button.tsx index 921c5a0d9..c67263c55 100644 --- a/packages/website/components/grab-element-button.tsx +++ b/packages/website/components/grab-element-button.tsx @@ -15,6 +15,7 @@ import { cn } from "@/utils/cn"; import { detectMobile } from "@/utils/detect-mobile"; import { getKeyFromCode } from "@/utils/get-key-from-code"; import { hotkeyToString } from "@/utils/hotkey-to-string"; +import { Button } from "@/components/ui/button"; import { useHotkey } from "./hotkey-context"; export interface RecordedHotkey { @@ -398,12 +399,14 @@ export const GrabElementButton = ({ } /> )} - + {!hasAdvanced && !isActivated && ( {!hideSkip && showSkip && ( - + )} ); diff --git a/packages/website/components/install-tabs.tsx b/packages/website/components/install-tabs.tsx index 64269e92c..5d3641221 100644 --- a/packages/website/components/install-tabs.tsx +++ b/packages/website/components/install-tabs.tsx @@ -6,6 +6,8 @@ import { COPY_FEEDBACK_DURATION_MS } from "@/constants"; import { cn } from "@/utils/cn"; import { detectMobile } from "@/utils/detect-mobile"; import { hotkeyToString } from "@/utils/hotkey-to-string"; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import type { RecordedHotkey } from "./grab-element-button"; import { useHotkey } from "./hotkey-context"; import { highlightCode } from "../lib/shiki"; @@ -366,45 +368,45 @@ export const InstallTabs = ({ {headingText} {activeTabId === "cli" && ( - + )} )} -
-
- {installTabsData.map((tab) => { - const isActive = tab.id === activeTab.id; - - return ( - - ); - })} -
+ setActiveTabId(value)} + className="mt-4 overflow-hidden rounded-lg border border-white/10 bg-white/5 text-white shadow-[0_8px_30px_rgb(0,0,0,0.3)]" + > + + {installTabsData.map((tab) => ( + + {tab.label} + + ))} +
{activeTabId === "cli" ? ( - + ) : (
- + {highlightedCode ? (
-
+ {activeTabId !== "cli" && ( {activeTab.description} @@ -452,9 +456,9 @@ export const InstallTabs = ({ {showAgentNote && activeTabId !== "cli" && ( Want to connect directly to your coding agent?{" "} - - See our agent connection guide - + )}
diff --git a/packages/website/components/table-of-contents.tsx b/packages/website/components/table-of-contents.tsx index b72c1ba22..4d6ca8ee5 100644 --- a/packages/website/components/table-of-contents.tsx +++ b/packages/website/components/table-of-contents.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useState } from "react"; +import { cn } from "@/utils/cn"; interface TocHeading { id: string; @@ -77,11 +78,13 @@ export const TableOfContents = ({ headings }: TableOfContentsProps) => { handleClick(event, heading.id)} - className={`block text-sm border-l-2 pl-3 -ml-0.5 ${indentClass} ${ + className={cn( + "block rounded-sm border-l-2 -ml-0.5 pl-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[#ff4fff]/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black", + indentClass, isActive - ? "text-neutral-200 border-[#ff4fff]" - : "text-neutral-500 border-transparent hover:text-neutral-300 hover:border-neutral-700" - }`} + ? "border-[#ff4fff] text-neutral-200" + : "border-transparent text-neutral-500 hover:border-neutral-700 hover:text-neutral-300", + )} > {heading.text} diff --git a/packages/website/components/view-docs-button.tsx b/packages/website/components/view-docs-button.tsx index 121e22184..27634f54d 100644 --- a/packages/website/components/view-docs-button.tsx +++ b/packages/website/components/view-docs-button.tsx @@ -1,16 +1,19 @@ import { type ReactElement } from "react"; import { BookOpen } from "lucide-react"; +import { Button } from "@/components/ui/button"; export const ViewDocsButton = (): ReactElement => ( - - - View docs - + + + View docs + + ); ViewDocsButton.displayName = "ViewDocsButton"; From 2f6fc1b3d06b5aefb66f1944b1123ae0d5a391ec Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 26 Feb 2026 14:43:30 +0000 Subject: [PATCH 4/7] migrate page and benchmark interactions to shadcn ui Co-authored-by: Aiden Bai --- packages/website/app/blog/agent/page.tsx | 9 +- packages/website/app/blog/page.tsx | 21 +- packages/website/app/changelog/page.tsx | 19 +- packages/website/app/not-found.tsx | 19 +- packages/website/app/open-file/page.tsx | 147 +++--- packages/website/app/privacy/page.tsx | 25 +- .../benchmarks/benchmark-charts.tsx | 58 ++- .../benchmarks/benchmark-detailed-table.tsx | 449 +++++++++--------- 8 files changed, 397 insertions(+), 350 deletions(-) diff --git a/packages/website/app/blog/agent/page.tsx b/packages/website/app/blog/agent/page.tsx index 1c5e6f004..585f9663c 100644 --- a/packages/website/app/blog/agent/page.tsx +++ b/packages/website/app/blog/agent/page.tsx @@ -12,6 +12,7 @@ import { GithubButton } from "@/components/github-button"; import { ViewDocsButton } from "@/components/view-docs-button"; import { BlogArticleLayout } from "@/components/blog-article-layout"; import { COPY_FEEDBACK_DURATION_MS } from "@/constants"; +import { Button } from "@/components/ui/button"; interface HighlightedCodeBlockProps { code: string; @@ -43,13 +44,15 @@ const HighlightedCodeBlock = ({ code, lang }: HighlightedCodeBlockProps) => { return (
- + {highlightedHtml ? (
{ return (
- - - Back to home - + + + Back to home + +
- + React Grab { )} {showYear ? post.year : ""} diff --git a/packages/website/app/changelog/page.tsx b/packages/website/app/changelog/page.tsx index f805ef878..ea7cbc013 100644 --- a/packages/website/app/changelog/page.tsx +++ b/packages/website/app/changelog/page.tsx @@ -6,6 +6,7 @@ import { readFileSync } from "fs"; import { join } from "path"; import ReactGrabLogo from "@/public/logo.svg"; import { parseChangelog } from "@/utils/parse-changelog"; +import { Button } from "@/components/ui/button"; const title = "Changelog"; const description = "Release notes and version history for React Grab"; @@ -50,16 +51,20 @@ const ChangelogPage = () => { return (
- - - Back to home - + + + Back to home + +
- + React Grab { return (
- - - Back to home - + + + Back to home + +
- + + EDITORS.some((editorOption) => editorOption.id === value); + const getEditorUrl = ( editor: Editor, filePath: string, @@ -61,8 +74,8 @@ const OpenFileContent = () => { const params = new URLSearchParams(window.location.search); if (params.has("raw")) return { editor: "cursor", hasSaved: false }; const saved = localStorage.getItem(STORAGE_KEY); - if (saved && EDITORS.some((e) => e.id === saved)) { - return { editor: saved as Editor, hasSaved: true }; + if (saved && isEditorOption(saved)) { + return { editor: saved, hasSaved: true }; } return { editor: "cursor", hasSaved: false }; }; @@ -76,21 +89,6 @@ const OpenFileContent = () => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [hasSavedPreference] = useState(() => getInitialEditor().hasSaved); const [isInfoOpen, setIsInfoOpen] = useState(false); - const dropdownRef = useRef(null); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - dropdownRef.current && - !dropdownRef.current.contains(event.target as Node) - ) { - setIsDropdownOpen(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); const handleOpen = useCallback(() => { if (!resolvedFilePath) return; @@ -139,7 +137,7 @@ const OpenFileContent = () => { if (!resolvedFilePath) { return (
-
+
@@ -150,7 +148,7 @@ const OpenFileContent = () => { {" "} to the URL.
-
+
); } @@ -167,18 +165,18 @@ const OpenFileContent = () => {
-
+
Opening - + {fileName} - + {lineNumber && ( <> at line - + {lineNumber} - + )}
@@ -188,79 +186,82 @@ const OpenFileContent = () => {
-
- + + - {selectedEditor?.icon} - {selectedEditor?.name} - - - - {isDropdownOpen && ( -
- {EDITORS.map((editor) => ( - - ))} -
- )} -
- -
- - +

Your preference will be saved for future use.

Only open files from trusted sources.

-
+ - + {isInfoOpen && (

Select any element in your React app and copy its context to AI tools.{" "} - + Learn more

diff --git a/packages/website/app/privacy/page.tsx b/packages/website/app/privacy/page.tsx index 0e8233968..0715275fc 100644 --- a/packages/website/app/privacy/page.tsx +++ b/packages/website/app/privacy/page.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import { ArrowLeft } from "lucide-react"; import { ReactGrabLogo } from "@/components/react-grab-logo"; +import { Button } from "@/components/ui/button"; const title = "Privacy Policy"; const description = @@ -39,16 +40,20 @@ const PrivacyPage = () => { return (
- - - Back to home - + + + Back to home + +
- + { href="https://github.com/aidenybai/react-grab" target="_blank" rel="noopener noreferrer" - className="text-white underline underline-offset-4 hover:opacity-80 transition-opacity" + className="rounded-sm text-white underline underline-offset-4 transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-[#ff4fff]/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black" > GitHub {" "} @@ -192,7 +197,7 @@ const PrivacyPage = () => { href="https://github.com/aidenybai/react-grab/issues" target="_blank" rel="noopener noreferrer" - className="text-white underline underline-offset-4 hover:opacity-80 transition-opacity" + className="rounded-sm text-white underline underline-offset-4 transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-[#ff4fff]/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black" > GitHub repository {" "} @@ -201,7 +206,7 @@ const PrivacyPage = () => { href="https://discord.com/invite/G7zxfUzkm7" target="_blank" rel="noopener noreferrer" - className="text-white underline underline-offset-4 hover:opacity-80 transition-opacity" + className="rounded-sm text-white underline underline-offset-4 transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-[#ff4fff]/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black" > Discord community diff --git a/packages/website/components/benchmarks/benchmark-charts.tsx b/packages/website/components/benchmarks/benchmark-charts.tsx index bebfdc836..58b219f81 100644 --- a/packages/website/components/benchmarks/benchmark-charts.tsx +++ b/packages/website/components/benchmarks/benchmark-charts.tsx @@ -15,6 +15,14 @@ import { BenchmarkResult, Metric } from "./types"; import { calculateStats } from "./utils"; import prettyMs from "pretty-ms"; import Image from "next/image"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; import { BENCHMARK_GRID_INTERVAL_SECONDS, BENCHMARK_CHART_HEIGHT_PX, @@ -260,7 +268,7 @@ export const BenchmarkChartsTweet = ({ results }: BenchmarkChartsProps) => { href="https://ui.shadcn.com" target="_blank" rel="noopener noreferrer" - className="underline underline-offset-2 hover:text-neutral-400" + className="rounded-sm underline underline-offset-2 hover:text-neutral-400 focus-visible:ring-2 focus-visible:ring-[#ff4fff]/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black" > shadcn/ui {" "} @@ -269,7 +277,7 @@ export const BenchmarkChartsTweet = ({ results }: BenchmarkChartsProps) => { href="https://github.com/aidenybai/react-grab/tree/main/packages/benchmarks" target="_blank" rel="noopener noreferrer" - className="underline underline-offset-2 hover:text-neutral-400" + className="rounded-sm underline underline-offset-2 hover:text-neutral-400 focus-visible:ring-2 focus-visible:ring-[#ff4fff]/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black" > More info @@ -566,55 +574,55 @@ export const BenchmarkCharts = ({ results }: BenchmarkChartsProps) => {
- - - -
+ + + + Metric - - - - - - + + + + {metrics.map((metric) => ( - - - - - + + ))} - -
+ + Control - + +
React Grab React Grab
-
+ {metric.name} - + + {metric.control} - + + {metric.treatment} {metric.isImprovement ? "↓" : "↑"} {metric.change} -
+ +
diff --git a/packages/website/components/benchmarks/benchmark-detailed-table.tsx b/packages/website/components/benchmarks/benchmark-detailed-table.tsx index c08dcbaaa..d3dfd03aa 100644 --- a/packages/website/components/benchmarks/benchmark-detailed-table.tsx +++ b/packages/website/components/benchmarks/benchmark-detailed-table.tsx @@ -5,6 +5,16 @@ import { useState, useMemo } from "react"; import { ChevronDown, ChevronUp, Search, ArrowUpDown } from "lucide-react"; import Image from "next/image"; import { BENCHMARK_TREATMENT_COLOR } from "@/constants"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; interface BenchmarkDetailedTableProps { results: BenchmarkResult[]; @@ -39,6 +49,43 @@ const SortIcon = ({ field, sortField, sortDirection }: SortIconProps) => { SortIcon.displayName = "SortIcon"; +interface SortButtonProps { + field: SortField; + label: string; + sortField: SortField; + sortDirection: SortDirection; + onSort: (field: SortField) => void; + className?: string; +} + +const SortButton = ({ + field, + label, + sortField, + sortDirection, + onSort, + className, +}: SortButtonProps) => ( + +); + +SortButton.displayName = "SortButton"; + export const BenchmarkDetailedTable = ({ results, testCaseMap, @@ -143,199 +190,168 @@ export const BenchmarkDetailedTable = ({ size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-500" /> - setSearchQuery(e.target.value)} - className="bg-[#1a1a1a] border border-[#2a2a2a] rounded-md py-1.5 pl-9 pr-3 text-xs text-neutral-200 placeholder:text-neutral-600 focus:outline-none focus:border-[#404040] w-full sm:w-[200px]" + className="h-auto w-full border-[#2a2a2a] bg-[#1a1a1a] py-1.5 pr-3 pl-9 text-xs text-neutral-200 placeholder:text-neutral-600 sm:w-[200px]" />
-
- - - - - - - - - - - - - - - - - - - - - - - - +
handleSort("testName")} - > -
- Test Name - -
-
handleSort("inputTokens")} - > -
- Input Tokens - -
-
handleSort("outputTokens")} - > -
- Output Tokens - -
-
handleSort("cost")} - > -
- Cost - -
-
handleSort("duration")} - > -
- Duration - -
-
handleSort("toolCalls")} - > -
- Tool Calls - -
-
- Control - -
- React Grab - - React Grab - -
-
- Control - -
- React Grab - - React Grab - -
-
- Control - -
- React Grab - - React Grab - -
-
- Control - -
- React Grab - - React Grab - -
-
- Control - -
- React Grab - - React Grab - -
-
+ + + + + + + + + + + + + + + + + + + + + + + + Control + + +
+ + + React Grab + +
+
+ + Control + + +
+ + + React Grab + +
+
+ + Control + + +
+ + + React Grab + +
+
+ + Control + + +
+ + + React Grab + +
+
+ + Control + + +
+ + + React Grab + +
+
+
+
+ {filteredAndSortedResults.length === 0 ? ( - - - + + ) : ( filteredAndSortedResults.map(([testName, results]) => { const control = results.control || ({} as BenchmarkResult); @@ -365,23 +381,23 @@ export const BenchmarkDetailedTable = ({ const prompt = testCaseMap[testName] || ""; return ( - - - - - - - - - - - - - + + ); }) )} - -
+ + No results found matching "{searchQuery}" -
{testName} - + + {control.inputTokens ? control.inputTokens.toLocaleString() : "-"} - + )} - + + {control.outputTokens ? control.outputTokens.toLocaleString() : "-"} - + )} - + + {control.costUsd !== undefined ? "$" + control.costUsd.toFixed(2) : "-"} - + )} - + + {control.durationMs ? prettyMs(control.durationMs) : "-"} - + )} - + + {control.toolCalls !== undefined ? control.toolCalls : "-"} - + )} -
-
+ +
); }; From 92693267ef92e471cce47fa0946192155534c3c6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 26 Feb 2026 14:46:19 +0000 Subject: [PATCH 5/7] add website playwright hover and focus regression suite Co-authored-by: Aiden Bai --- .../e2e/benchmark-table-interactions.spec.ts | 33 ++++++++++ .../website/e2e/blog-interactions.spec.ts | 40 ++++++++++++ .../website/e2e/homepage-interactions.spec.ts | 56 ++++++++++++++++ .../e2e/open-file-interactions.spec.ts | 64 +++++++++++++++++++ packages/website/e2e/style-helpers.ts | 18 ++++++ packages/website/package.json | 5 +- packages/website/playwright.config.ts | 26 ++++++++ pnpm-lock.yaml | 11 ++-- 8 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 packages/website/e2e/benchmark-table-interactions.spec.ts create mode 100644 packages/website/e2e/blog-interactions.spec.ts create mode 100644 packages/website/e2e/homepage-interactions.spec.ts create mode 100644 packages/website/e2e/open-file-interactions.spec.ts create mode 100644 packages/website/e2e/style-helpers.ts create mode 100644 packages/website/playwright.config.ts diff --git a/packages/website/e2e/benchmark-table-interactions.spec.ts b/packages/website/e2e/benchmark-table-interactions.spec.ts new file mode 100644 index 000000000..f4ea5a9bc --- /dev/null +++ b/packages/website/e2e/benchmark-table-interactions.spec.ts @@ -0,0 +1,33 @@ +import { expect, test } from "@playwright/test"; +import { expectVisibleFocusRing, getStyleProperty } from "./style-helpers"; + +test.describe("Benchmark Table Hover & Focus States", () => { + test("filter input and sort controls preserve interactive states", async ({ + page, + }) => { + await page.goto("/blog/intro"); + + const filterInput = page.getByPlaceholder("Filter tests..."); + await filterInput.scrollIntoViewIfNeeded(); + await expect(filterInput).toBeVisible({ timeout: 45_000 }); + await expectVisibleFocusRing(filterInput); + + const inputTokensSortButton = page.getByRole("button", { + name: /input tokens/i, + }); + await expect(inputTokensSortButton).toBeVisible(); + + const sortButtonColorBeforeHover = await getStyleProperty( + inputTokensSortButton, + "color", + ); + await inputTokensSortButton.hover(); + const sortButtonColorAfterHover = await getStyleProperty( + inputTokensSortButton, + "color", + ); + expect(sortButtonColorAfterHover).not.toBe(sortButtonColorBeforeHover); + + await expectVisibleFocusRing(inputTokensSortButton); + }); +}); diff --git a/packages/website/e2e/blog-interactions.spec.ts b/packages/website/e2e/blog-interactions.spec.ts new file mode 100644 index 000000000..bffcc027a --- /dev/null +++ b/packages/website/e2e/blog-interactions.spec.ts @@ -0,0 +1,40 @@ +import { expect, test } from "@playwright/test"; +import { expectVisibleFocusRing, getStyleProperty } from "./style-helpers"; + +test.describe("Blog Hover & Focus States", () => { + test("blog index post links keep hover and focus styles", async ({ page }) => { + await page.goto("/blog"); + + const introPostLink = page.getByRole("link", { + name: /React Grab Is Now 1.0/i, + }); + await expect(introPostLink).toBeVisible(); + + const linkBackgroundBeforeHover = await getStyleProperty( + introPostLink, + "background-color", + ); + await introPostLink.hover(); + const linkBackgroundAfterHover = await getStyleProperty( + introPostLink, + "background-color", + ); + + expect(linkBackgroundAfterHover).not.toBe(linkBackgroundBeforeHover); + await expectVisibleFocusRing(introPostLink); + }); + + test("agent blog copy button and back link keep focus styles", async ({ + page, + }) => { + await page.goto("/blog/agent"); + + const backToHomeLink = page.getByRole("link", { name: /Back to home/i }); + await expect(backToHomeLink).toBeVisible(); + await expectVisibleFocusRing(backToHomeLink); + + const copyButton = page.getByRole("button", { name: "Copy" }).first(); + await expect(copyButton).toBeVisible(); + await expectVisibleFocusRing(copyButton); + }); +}); diff --git a/packages/website/e2e/homepage-interactions.spec.ts b/packages/website/e2e/homepage-interactions.spec.ts new file mode 100644 index 000000000..fb29abbf5 --- /dev/null +++ b/packages/website/e2e/homepage-interactions.spec.ts @@ -0,0 +1,56 @@ +import { expect, test } from "@playwright/test"; +import { expectVisibleFocusRing, getStyleProperty } from "./style-helpers"; + +test.describe("Homepage Hover & Focus States", () => { + test.beforeEach(async ({ page }) => { + await page.addInitScript(() => { + window.localStorage.setItem("stream-completed", "true"); + }); + await page.goto("/"); + }); + + test("primary CTA controls keep hover and focus styles", async ({ page }) => { + const githubCta = page.getByRole("link", { name: "Star on GitHub" }); + await expect(githubCta).toBeVisible(); + + const githubBackgroundBeforeHover = await getStyleProperty( + githubCta, + "background-color", + ); + await githubCta.hover(); + const githubBackgroundAfterHover = await getStyleProperty( + githubCta, + "background-color", + ); + + expect(githubBackgroundAfterHover).not.toBe(githubBackgroundBeforeHover); + await expectVisibleFocusRing(githubCta); + + const holdHotkeyButton = page.getByRole("button", { name: /hold/i }); + await expect(holdHotkeyButton).toBeVisible(); + await expectVisibleFocusRing(holdHotkeyButton); + }); + + test("install tabs keep interactive states", async ({ page }) => { + const cliTabTrigger = page.getByRole("tab", { name: "CLI" }); + const manualInstallTabTrigger = page.getByRole("tab", { + name: "Next.js (App)", + }); + + await expect(cliTabTrigger).toBeVisible(); + await expect(manualInstallTabTrigger).toBeVisible(); + + const tabColorBeforeHover = await getStyleProperty( + manualInstallTabTrigger, + "color", + ); + await manualInstallTabTrigger.hover(); + const tabColorAfterHover = await getStyleProperty( + manualInstallTabTrigger, + "color", + ); + expect(tabColorAfterHover).not.toBe(tabColorBeforeHover); + + await expectVisibleFocusRing(manualInstallTabTrigger); + }); +}); diff --git a/packages/website/e2e/open-file-interactions.spec.ts b/packages/website/e2e/open-file-interactions.spec.ts new file mode 100644 index 000000000..967c2b32e --- /dev/null +++ b/packages/website/e2e/open-file-interactions.spec.ts @@ -0,0 +1,64 @@ +import { expect, test } from "@playwright/test"; +import { expectVisibleFocusRing, getStyleProperty } from "./style-helpers"; + +test.describe("Open File Page Hover & Focus States", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/open-file?url=src/components/button.tsx&line=23"); + }); + + test("editor dropdown and open controls keep hover and focus styles", async ({ + page, + }) => { + const editorDropdownTrigger = page.getByRole("button", { + name: /cursor/i, + }); + const openButton = page.getByRole("button", { name: /open/i }); + + await expect(editorDropdownTrigger).toBeVisible(); + await expect(openButton).toBeVisible(); + + const openButtonColorBeforeHover = await getStyleProperty( + openButton, + "color", + ); + await openButton.hover(); + const openButtonColorAfterHover = await getStyleProperty( + openButton, + "color", + ); + expect(openButtonColorAfterHover).not.toBe(openButtonColorBeforeHover); + + await expectVisibleFocusRing(editorDropdownTrigger); + await expectVisibleFocusRing(openButton); + }); + + test("editor menu options are keyboard focusable and selectable", async ({ + page, + }) => { + const editorDropdownTrigger = page.getByRole("button", { + name: /cursor/i, + }); + await editorDropdownTrigger.click(); + + const vsCodeOption = page.getByRole("menuitem", { name: /vs code/i }); + await expect(vsCodeOption).toBeVisible(); + await expectVisibleFocusRing(vsCodeOption); + await vsCodeOption.click(); + + await expect( + page.getByRole("button", { + name: /vs code/i, + }), + ).toBeVisible(); + }); + + test("info toggle preserves focus style", async ({ page }) => { + const infoToggle = page.getByRole("button", { name: /what is react grab/i }); + await expect(infoToggle).toBeVisible(); + + await expectVisibleFocusRing(infoToggle); + await infoToggle.click(); + + await expect(page.getByRole("link", { name: "Learn more" })).toBeVisible(); + }); +}); diff --git a/packages/website/e2e/style-helpers.ts b/packages/website/e2e/style-helpers.ts new file mode 100644 index 000000000..603288cf0 --- /dev/null +++ b/packages/website/e2e/style-helpers.ts @@ -0,0 +1,18 @@ +import type { Locator } from "@playwright/test"; +import { expect } from "@playwright/test"; + +export const getStyleProperty = async ( + locator: Locator, + propertyName: string, +): Promise => + locator.evaluate( + (element, requestedPropertyName) => + getComputedStyle(element).getPropertyValue(requestedPropertyName), + propertyName, + ); + +export const expectVisibleFocusRing = async (locator: Locator) => { + await locator.focus(); + const boxShadow = await getStyleProperty(locator, "box-shadow"); + expect(boxShadow).not.toBe("none"); +}; diff --git a/packages/website/package.json b/packages/website/package.json index cd1d39c31..df78bd26e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -6,7 +6,9 @@ "dev": "next dev", "build": "pnpm --filter react-grab build && pnpm --filter react-grab exec cp dist/index.global.js ../website/public/script.js && pnpm --filter @react-grab/design-system build && next build", "start": "next start", - "lint": "oxlint" + "lint": "oxlint", + "typecheck": "tsc --noEmit", + "test:e2e": "playwright test -c playwright.config.ts" }, "dependencies": { "@radix-ui/react-collapsible": "^1.1.12", @@ -35,6 +37,7 @@ "tailwind-merge": "^3.4.0" }, "devDependencies": { + "@playwright/test": "^1.57.0", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", diff --git a/packages/website/playwright.config.ts b/packages/website/playwright.config.ts new file mode 100644 index 000000000..1b9815526 --- /dev/null +++ b/packages/website/playwright.config.ts @@ -0,0 +1,26 @@ +import { defineConfig, devices } from "@playwright/test"; + +const PORT = 4200; +const BASE_URL = `http://127.0.0.1:${PORT}`; + +export default defineConfig({ + testDir: "./e2e", + timeout: 60_000, + fullyParallel: true, + use: { + baseURL: BASE_URL, + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: { + command: `pnpm dev -- --port ${PORT}`, + url: BASE_URL, + timeout: 120_000, + reuseExistingServer: true, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1d93880e..ee1c8d13c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -825,6 +825,9 @@ importers: specifier: ^3.4.0 version: 3.4.0 devDependencies: + '@playwright/test': + specifier: ^1.57.0 + version: 1.57.0 '@tailwindcss/postcss': specifier: ^4 version: 4.1.15 @@ -10466,14 +10469,14 @@ snapshots: '@remotion/media-parser': 4.0.424 '@remotion/studio': 4.0.424(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@remotion/studio-shared': 4.0.424(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - css-loader: 5.2.7(webpack@5.105.0(esbuild@0.25.0)) + css-loader: 5.2.7(webpack@5.105.0) esbuild: 0.25.0 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) react-refresh: 0.9.0 remotion: 4.0.424(react-dom@19.2.3(react@19.2.3))(react@19.2.3) source-map: 0.7.3 - style-loader: 4.0.0(webpack@5.105.0(esbuild@0.25.0)) + style-loader: 4.0.0(webpack@5.105.0) webpack: 5.105.0(esbuild@0.25.0) transitivePeerDependencies: - '@swc/core' @@ -12075,7 +12078,7 @@ snapshots: crypt@0.0.2: {} - css-loader@5.2.7(webpack@5.105.0(esbuild@0.25.0)): + css-loader@5.2.7(webpack@5.105.0): dependencies: icss-utils: 5.1.0(postcss@8.5.6) loader-utils: 2.0.4 @@ -15228,7 +15231,7 @@ snapshots: stubborn-utils@1.0.2: {} - style-loader@4.0.0(webpack@5.105.0(esbuild@0.25.0)): + style-loader@4.0.0(webpack@5.105.0): dependencies: webpack: 5.105.0(esbuild@0.25.0) From 7f4b6c4f4af93dea48392e951a4cf1714d61cdc4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 26 Feb 2026 15:08:36 +0000 Subject: [PATCH 6/7] stabilize website linting and complete hover focus test execution Co-authored-by: Aiden Bai --- .../components/blog-article-layout.tsx | 4 +- packages/website/components/demo-footer.tsx | 5 ++- packages/website/components/install-tabs.tsx | 3 +- .../website/components/table-of-contents.tsx | 7 +-- packages/website/components/ui/card.tsx | 34 +++++++++++---- .../website/components/ui/dropdown-menu.tsx | 24 ++++++++--- packages/website/components/ui/hover-card.tsx | 4 +- packages/website/components/ui/input.tsx | 4 +- .../website/components/ui/scroll-area.tsx | 9 +++- packages/website/components/ui/table.tsx | 28 +++++++++--- packages/website/components/ui/tabs.tsx | 16 +++++-- packages/website/custom-modules.d.ts | 43 +++++++++++++++++++ .../website/e2e/blog-interactions.spec.ts | 15 ++++--- .../e2e/open-file-interactions.spec.ts | 25 ++++++++--- packages/website/package.json | 1 + packages/website/playwright.config.ts | 2 +- pnpm-lock.yaml | 3 ++ 17 files changed, 174 insertions(+), 53 deletions(-) create mode 100644 packages/website/custom-modules.d.ts diff --git a/packages/website/components/blog-article-layout.tsx b/packages/website/components/blog-article-layout.tsx index 06fc7c0d4..ee8d61192 100644 --- a/packages/website/components/blog-article-layout.tsx +++ b/packages/website/components/blog-article-layout.tsx @@ -81,14 +81,14 @@ export const BlogArticleLayout = ({ By{" "} {authors.map((author, index) => ( - {author.name} - + {index < authors.length - 1 && ", "} ))} diff --git a/packages/website/components/demo-footer.tsx b/packages/website/components/demo-footer.tsx index 2e788d1d8..fab6e9a71 100644 --- a/packages/website/components/demo-footer.tsx +++ b/packages/website/components/demo-footer.tsx @@ -1,6 +1,7 @@ "use client"; import { type ReactElement } from "react"; +import Link from "next/link"; import { RotateCcw } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -30,11 +31,11 @@ export const DemoFooter = (): ReactElement => { · {" "} ·{" "}
); diff --git a/packages/website/components/install-tabs.tsx b/packages/website/components/install-tabs.tsx index 5d3641221..3abb228bb 100644 --- a/packages/website/components/install-tabs.tsx +++ b/packages/website/components/install-tabs.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useState, useCallback, type ReactElement } from "react"; +import Link from "next/link"; import { Copy, Check } from "lucide-react"; import { COPY_FEEDBACK_DURATION_MS } from "@/constants"; import { cn } from "@/utils/cn"; @@ -457,7 +458,7 @@ export const InstallTabs = ({ Want to connect directly to your coding agent?{" "} )} diff --git a/packages/website/components/table-of-contents.tsx b/packages/website/components/table-of-contents.tsx index 4d6ca8ee5..6eff2320e 100644 --- a/packages/website/components/table-of-contents.tsx +++ b/packages/website/components/table-of-contents.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useState } from "react"; +import Link from "next/link"; import { cn } from "@/utils/cn"; interface TocHeading { @@ -47,7 +48,7 @@ export const TableOfContents = ({ headings }: TableOfContentsProps) => { }, [headings]); const handleClick = ( - event: React.MouseEvent, + event: React.MouseEvent, id: string, ) => { event.preventDefault(); @@ -75,7 +76,7 @@ export const TableOfContents = ({ headings }: TableOfContentsProps) => { return (
  • - handleClick(event, heading.id)} className={cn( @@ -87,7 +88,7 @@ export const TableOfContents = ({ headings }: TableOfContentsProps) => { )} > {heading.text} - +
  • ); })} diff --git a/packages/website/components/ui/card.tsx b/packages/website/components/ui/card.tsx index b171e57d3..ea503296e 100644 --- a/packages/website/components/ui/card.tsx +++ b/packages/website/components/ui/card.tsx @@ -3,7 +3,9 @@ import type { ComponentProps, ReactElement } from "react"; import { cn } from "@/utils/cn"; -interface CardProps extends ComponentProps<"div"> {} +interface CardProps extends ComponentProps<"div"> { + className?: string; +} export const Card = ({ className, ...props }: CardProps): ReactElement => (
    ( Card.displayName = "Card"; -interface CardHeaderProps extends ComponentProps<"div"> {} +interface CardHeaderProps extends ComponentProps<"div"> { + className?: string; +} export const CardHeader = ({ className, @@ -28,29 +32,41 @@ export const CardHeader = ({ CardHeader.displayName = "CardHeader"; -interface CardTitleProps extends ComponentProps<"h3"> {} +interface CardTitleProps extends ComponentProps<"h3"> { + className?: string; +} export const CardTitle = ({ className, + children, ...props }: CardTitleProps): ReactElement => ( -

    +

    + {children} +

    ); CardTitle.displayName = "CardTitle"; -interface CardDescriptionProps extends ComponentProps<"p"> {} +interface CardDescriptionProps extends ComponentProps<"p"> { + className?: string; +} export const CardDescription = ({ className, + children, ...props }: CardDescriptionProps): ReactElement => ( -

    +

    + {children} +

    ); CardDescription.displayName = "CardDescription"; -interface CardContentProps extends ComponentProps<"div"> {} +interface CardContentProps extends ComponentProps<"div"> { + className?: string; +} export const CardContent = ({ className, @@ -61,7 +77,9 @@ export const CardContent = ({ CardContent.displayName = "CardContent"; -interface CardFooterProps extends ComponentProps<"div"> {} +interface CardFooterProps extends ComponentProps<"div"> { + className?: string; +} export const CardFooter = ({ className, diff --git a/packages/website/components/ui/dropdown-menu.tsx b/packages/website/components/ui/dropdown-menu.tsx index cca4b421f..befa7a3a3 100644 --- a/packages/website/components/ui/dropdown-menu.tsx +++ b/packages/website/components/ui/dropdown-menu.tsx @@ -39,7 +39,9 @@ export const DropdownMenuSubTrigger = ({ DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger"; interface DropdownMenuSubContentProps - extends ComponentProps {} + extends ComponentProps { + className?: string; +} export const DropdownMenuSubContent = ({ className, @@ -57,7 +59,9 @@ export const DropdownMenuSubContent = ({ DropdownMenuSubContent.displayName = "DropdownMenuSubContent"; interface DropdownMenuContentProps - extends ComponentProps {} + extends ComponentProps { + className?: string; +} export const DropdownMenuContent = ({ className, @@ -90,7 +94,7 @@ export const DropdownMenuItem = ({ }: DropdownMenuItemProps): ReactElement => ( {} + extends ComponentProps { + className?: string; +} export const DropdownMenuCheckboxItem = ({ className, @@ -111,7 +117,7 @@ export const DropdownMenuCheckboxItem = ({ }: DropdownMenuCheckboxItemProps): ReactElement => ( {} + extends ComponentProps { + className?: string; +} export const DropdownMenuSeparator = ({ className, @@ -158,7 +166,9 @@ export const DropdownMenuSeparator = ({ DropdownMenuSeparator.displayName = "DropdownMenuSeparator"; -interface DropdownMenuShortcutProps extends ComponentProps<"span"> {} +interface DropdownMenuShortcutProps extends ComponentProps<"span"> { + className?: string; +} export const DropdownMenuShortcut = ({ className, diff --git a/packages/website/components/ui/hover-card.tsx b/packages/website/components/ui/hover-card.tsx index 77fa673c0..a93badac1 100644 --- a/packages/website/components/ui/hover-card.tsx +++ b/packages/website/components/ui/hover-card.tsx @@ -8,7 +8,9 @@ export const HoverCard = HoverCardPrimitive.Root; export const HoverCardTrigger = HoverCardPrimitive.Trigger; interface HoverCardContentProps - extends ComponentProps {} + extends ComponentProps { + className?: string; +} export const HoverCardContent = ({ className, diff --git a/packages/website/components/ui/input.tsx b/packages/website/components/ui/input.tsx index f5d26540a..a1d8315a5 100644 --- a/packages/website/components/ui/input.tsx +++ b/packages/website/components/ui/input.tsx @@ -3,7 +3,9 @@ import type { InputHTMLAttributes, ReactElement } from "react"; import { cn } from "@/utils/cn"; -interface InputProps extends InputHTMLAttributes {} +interface InputProps extends InputHTMLAttributes { + className?: string; +} export const Input = ({ className, ...props }: InputProps): ReactElement => ( {} +interface ScrollAreaProps extends ComponentProps { + className?: string; +} export const ScrollArea = ({ className, @@ -22,7 +24,10 @@ export const ScrollArea = ({ ScrollArea.displayName = "ScrollArea"; -interface ScrollBarProps extends ComponentProps {} +interface ScrollBarProps + extends ComponentProps { + className?: string; +} export const ScrollBar = ({ className, orientation = "vertical", ...props }: ScrollBarProps): ReactElement => ( {} +interface TableProps extends ComponentProps<"table"> { + className?: string; +} export const Table = ({ className, ...props }: TableProps): ReactElement => (
    @@ -13,7 +15,9 @@ export const Table = ({ className, ...props }: TableProps): ReactElement => ( Table.displayName = "Table"; -interface TableHeaderProps extends ComponentProps<"thead"> {} +interface TableHeaderProps extends ComponentProps<"thead"> { + className?: string; +} export const TableHeader = ({ className, @@ -24,7 +28,9 @@ export const TableHeader = ({ TableHeader.displayName = "TableHeader"; -interface TableBodyProps extends ComponentProps<"tbody"> {} +interface TableBodyProps extends ComponentProps<"tbody"> { + className?: string; +} export const TableBody = ({ className, ...props }: TableBodyProps): ReactElement => ( @@ -32,7 +38,9 @@ export const TableBody = ({ className, ...props }: TableBodyProps): ReactElement TableBody.displayName = "TableBody"; -interface TableRowProps extends ComponentProps<"tr"> {} +interface TableRowProps extends ComponentProps<"tr"> { + className?: string; +} export const TableRow = ({ className, ...props }: TableRowProps): ReactElement => ( {} +interface TableHeadProps extends ComponentProps<"th"> { + className?: string; +} export const TableHead = ({ className, ...props }: TableHeadProps): ReactElement => ( {} +interface TableCellProps extends ComponentProps<"td"> { + className?: string; +} export const TableCell = ({ className, ...props }: TableCellProps): ReactElement => ( @@ -68,7 +80,9 @@ export const TableCell = ({ className, ...props }: TableCellProps): ReactElement TableCell.displayName = "TableCell"; -interface TableCaptionProps extends ComponentProps<"caption"> {} +interface TableCaptionProps extends ComponentProps<"caption"> { + className?: string; +} export const TableCaption = ({ className, diff --git a/packages/website/components/ui/tabs.tsx b/packages/website/components/ui/tabs.tsx index 118b9df62..f9e8b50b1 100644 --- a/packages/website/components/ui/tabs.tsx +++ b/packages/website/components/ui/tabs.tsx @@ -4,7 +4,9 @@ import * as TabsPrimitive from "@radix-ui/react-tabs"; import type { ComponentProps, ReactElement } from "react"; import { cn } from "@/utils/cn"; -interface TabsProps extends ComponentProps {} +interface TabsProps extends ComponentProps { + className?: string; +} export const Tabs = ({ className, ...props }: TabsProps): ReactElement => ( @@ -12,7 +14,9 @@ export const Tabs = ({ className, ...props }: TabsProps): ReactElement => ( Tabs.displayName = "Tabs"; -interface TabsListProps extends ComponentProps {} +interface TabsListProps extends ComponentProps { + className?: string; +} export const TabsList = ({ className, @@ -29,7 +33,9 @@ export const TabsList = ({ TabsList.displayName = "TabsList"; -interface TabsTriggerProps extends ComponentProps {} +interface TabsTriggerProps extends ComponentProps { + className?: string; +} export const TabsTrigger = ({ className, @@ -46,7 +52,9 @@ export const TabsTrigger = ({ TabsTrigger.displayName = "TabsTrigger"; -interface TabsContentProps extends ComponentProps {} +interface TabsContentProps extends ComponentProps { + className?: string; +} export const TabsContent = ({ className, diff --git a/packages/website/custom-modules.d.ts b/packages/website/custom-modules.d.ts new file mode 100644 index 000000000..8587370a8 --- /dev/null +++ b/packages/website/custom-modules.d.ts @@ -0,0 +1,43 @@ +declare module "*.svg" { + import type { StaticImageData } from "next/image"; + + const content: StaticImageData; + export default content; +} + +declare module "@react-grab/design-system" { + export const renderDesignSystemPreview: ( + containerElement: HTMLElement, + ) => () => void; +} + +declare module "react-grab" { + interface ReactGrabPlugin { + name: string; + hooks?: Record void>; + } + + interface ReactGrabApi { + toggle: () => void; + deactivate: () => void; + dispose: () => void; + registerPlugin: (plugin: ReactGrabPlugin) => void; + } + + export const init: (options?: Record) => ReactGrabApi; + export const getGlobalApi: () => ReactGrabApi | undefined; + export const setGlobalApi: (api: ReactGrabApi) => void; +} + +declare module "react-grab/core" { + interface ReactGrabCorePlugin { + name: string; + hooks?: Record void>; + } + + interface ReactGrabCoreApi { + registerPlugin: (plugin: ReactGrabCorePlugin) => void; + } + + export const init: (options?: Record) => ReactGrabCoreApi; +} diff --git a/packages/website/e2e/blog-interactions.spec.ts b/packages/website/e2e/blog-interactions.spec.ts index bffcc027a..8c72e707c 100644 --- a/packages/website/e2e/blog-interactions.spec.ts +++ b/packages/website/e2e/blog-interactions.spec.ts @@ -10,17 +10,18 @@ test.describe("Blog Hover & Focus States", () => { }); await expect(introPostLink).toBeVisible(); - const linkBackgroundBeforeHover = await getStyleProperty( - introPostLink, - "background-color", + const postTitleText = introPostLink.getByText("React Grab Is Now 1.0"); + const titleColorBeforeHover = await getStyleProperty( + postTitleText, + "color", ); await introPostLink.hover(); - const linkBackgroundAfterHover = await getStyleProperty( - introPostLink, - "background-color", + const titleColorAfterHover = await getStyleProperty( + postTitleText, + "color", ); - expect(linkBackgroundAfterHover).not.toBe(linkBackgroundBeforeHover); + expect(titleColorAfterHover).not.toBe(titleColorBeforeHover); await expectVisibleFocusRing(introPostLink); }); diff --git a/packages/website/e2e/open-file-interactions.spec.ts b/packages/website/e2e/open-file-interactions.spec.ts index 967c2b32e..23a4199c4 100644 --- a/packages/website/e2e/open-file-interactions.spec.ts +++ b/packages/website/e2e/open-file-interactions.spec.ts @@ -12,21 +12,23 @@ test.describe("Open File Page Hover & Focus States", () => { const editorDropdownTrigger = page.getByRole("button", { name: /cursor/i, }); - const openButton = page.getByRole("button", { name: /open/i }); + const openButton = page.getByRole("button", { name: "Open", exact: true }); await expect(editorDropdownTrigger).toBeVisible(); await expect(openButton).toBeVisible(); - const openButtonColorBeforeHover = await getStyleProperty( + const openButtonBackgroundBeforeHover = await getStyleProperty( openButton, - "color", + "background-color", ); await openButton.hover(); - const openButtonColorAfterHover = await getStyleProperty( + const openButtonBackgroundAfterHover = await getStyleProperty( openButton, - "color", + "background-color", + ); + expect(openButtonBackgroundAfterHover).not.toBe( + openButtonBackgroundBeforeHover, ); - expect(openButtonColorAfterHover).not.toBe(openButtonColorBeforeHover); await expectVisibleFocusRing(editorDropdownTrigger); await expectVisibleFocusRing(openButton); @@ -42,7 +44,16 @@ test.describe("Open File Page Hover & Focus States", () => { const vsCodeOption = page.getByRole("menuitem", { name: /vs code/i }); await expect(vsCodeOption).toBeVisible(); - await expectVisibleFocusRing(vsCodeOption); + const optionBackgroundBeforeFocus = await getStyleProperty( + vsCodeOption, + "background-color", + ); + await vsCodeOption.focus(); + const optionBackgroundAfterFocus = await getStyleProperty( + vsCodeOption, + "background-color", + ); + expect(optionBackgroundAfterFocus).not.toBe(optionBackgroundBeforeFocus); await vsCodeOption.click(); await expect( diff --git a/packages/website/package.json b/packages/website/package.json index df78bd26e..73db104ca 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -45,6 +45,7 @@ "critters": "^0.0.25", "eslint": "^9", "eslint-config-next": "16.0.7", + "oxlint": "^1.42.0", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "typescript": "^5" diff --git a/packages/website/playwright.config.ts b/packages/website/playwright.config.ts index 1b9815526..388771f65 100644 --- a/packages/website/playwright.config.ts +++ b/packages/website/playwright.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ }, ], webServer: { - command: `pnpm dev -- --port ${PORT}`, + command: `pnpm exec next dev -p ${PORT}`, url: BASE_URL, timeout: 120_000, reuseExistingServer: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee1c8d13c..6752f2a26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -849,6 +849,9 @@ importers: eslint-config-next: specifier: 16.0.7 version: 16.0.7(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + oxlint: + specifier: ^1.42.0 + version: 1.42.0 tailwindcss: specifier: ^4 version: 4.1.15 From bfa87f9d329166ca24c22e440174539d69c07b9a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 27 Feb 2026 06:39:23 +0000 Subject: [PATCH 7/7] Fix website spacing regressions and review findings Co-authored-by: Aiden Bai --- .../website/components/benchmark-tooltip.tsx | 1 + .../components/blocks/read-tool-call-block.tsx | 2 +- packages/website/components/install-tabs.tsx | 8 ++++---- packages/website/components/ui/button.tsx | 17 ++++++++++++----- packages/website/components/ui/card.tsx | 2 -- packages/website/components/ui/input.tsx | 2 +- packages/website/components/ui/table.tsx | 2 -- packages/website/e2e/blog-interactions.spec.ts | 15 ++++++++++----- .../website/e2e/open-file-interactions.spec.ts | 5 +++-- 9 files changed, 32 insertions(+), 22 deletions(-) diff --git a/packages/website/components/benchmark-tooltip.tsx b/packages/website/components/benchmark-tooltip.tsx index eed1571ab..0016fdc2c 100644 --- a/packages/website/components/benchmark-tooltip.tsx +++ b/packages/website/components/benchmark-tooltip.tsx @@ -216,6 +216,7 @@ export const BenchmarkTooltip = ({ setActiveTabId("next-app")} variant="link" size="sm" - className="ml-3 h-auto px-0 py-0 text-xs italic text-white/40 hover:text-white/60 sm:text-sm" + className="ml-3 h-auto px-0 py-0 text-xs italic text-white/40 underline underline-offset-4 hover:text-white/60 sm:text-sm" > Prefer manual install? @@ -384,7 +384,7 @@ export const InstallTabs = ({ setActiveTabId(value)} - className="mt-4 overflow-hidden rounded-lg border border-white/10 bg-white/5 text-white shadow-[0_8px_30px_rgb(0,0,0,0.3)]" + className="mt-4 gap-0 overflow-hidden rounded-lg border border-white/10 bg-white/5 text-white shadow-[0_8px_30px_rgb(0,0,0,0.3)]" > {installTabsData.map((tab) => ( @@ -392,7 +392,7 @@ export const InstallTabs = ({ key={tab.id} value={tab.id} className={cn( - "h-auto shrink-0 rounded-none border-b pb-2 font-sans text-sm data-[state=active]:border-white data-[state=active]:bg-transparent data-[state=active]:shadow-none data-[state=active]:text-white sm:text-base", + "h-auto shrink-0 rounded-none border-b px-0 pt-0 pb-2 font-sans text-sm data-[state=active]:border-white data-[state=active]:bg-transparent data-[state=active]:shadow-none data-[state=active]:text-white sm:text-base", "border-transparent text-white/60 hover:text-white", )} > @@ -457,7 +457,7 @@ export const InstallTabs = ({ {showAgentNote && activeTabId !== "cli" && ( Want to connect directly to your coding agent?{" "} - diff --git a/packages/website/components/ui/button.tsx b/packages/website/components/ui/button.tsx index 78ef3e56a..bd66d758b 100644 --- a/packages/website/components/ui/button.tsx +++ b/packages/website/components/ui/button.tsx @@ -13,7 +13,7 @@ const buttonVariants = cva( default: "bg-white text-black hover:bg-white/90", secondary: "border border-white/20 bg-white/5 text-white hover:bg-white/10", ghost: "text-white/70 hover:bg-white/10 hover:text-white", - link: "text-white underline underline-offset-4 hover:text-white/80", + link: "text-white/70 hover:text-white", destructive: "bg-red-500 text-white hover:bg-red-500/90 focus-visible:ring-red-400/80", }, @@ -44,12 +44,19 @@ export const Button = ({ asChild = false, ...props }: ButtonProps): ReactElement => { - const Component = asChild ? Slot : "button"; + if (asChild) { + return ( + + ); + } + + const { type = "button", ...buttonProps } = props; return ( - ); }; diff --git a/packages/website/components/ui/card.tsx b/packages/website/components/ui/card.tsx index ea503296e..afd6c04cb 100644 --- a/packages/website/components/ui/card.tsx +++ b/packages/website/components/ui/card.tsx @@ -1,5 +1,3 @@ -"use client"; - import type { ComponentProps, ReactElement } from "react"; import { cn } from "@/utils/cn"; diff --git a/packages/website/components/ui/input.tsx b/packages/website/components/ui/input.tsx index a1d8315a5..21f1be9b3 100644 --- a/packages/website/components/ui/input.tsx +++ b/packages/website/components/ui/input.tsx @@ -10,7 +10,7 @@ interface InputProps extends InputHTMLAttributes { export const Input = ({ className, ...props }: InputProps): ReactElement => ( { "color", ); await introPostLink.hover(); - const titleColorAfterHover = await getStyleProperty( - postTitleText, - "color", - ); + await expect + .poll( + async () => + getStyleProperty( + postTitleText, + "color", + ), + { timeout: 2000 }, + ) + .not.toBe(titleColorBeforeHover); - expect(titleColorAfterHover).not.toBe(titleColorBeforeHover); await expectVisibleFocusRing(introPostLink); }); diff --git a/packages/website/e2e/open-file-interactions.spec.ts b/packages/website/e2e/open-file-interactions.spec.ts index 23a4199c4..4917cdcda 100644 --- a/packages/website/e2e/open-file-interactions.spec.ts +++ b/packages/website/e2e/open-file-interactions.spec.ts @@ -40,7 +40,8 @@ test.describe("Open File Page Hover & Focus States", () => { const editorDropdownTrigger = page.getByRole("button", { name: /cursor/i, }); - await editorDropdownTrigger.click(); + await editorDropdownTrigger.focus(); + await page.keyboard.press("Enter"); const vsCodeOption = page.getByRole("menuitem", { name: /vs code/i }); await expect(vsCodeOption).toBeVisible(); @@ -54,7 +55,7 @@ test.describe("Open File Page Hover & Focus States", () => { "background-color", ); expect(optionBackgroundAfterFocus).not.toBe(optionBackgroundBeforeFocus); - await vsCodeOption.click(); + await page.keyboard.press("Enter"); await expect( page.getByRole("button", {