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.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/benchmark-tooltip.tsx b/packages/website/components/benchmark-tooltip.tsx index 4f0cd11a8..0016fdc2c 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,59 @@ 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/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 : "-"} - + )} -
-
+ +
); }; 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..5923cff41 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..ee8d61192 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 +
@@ -74,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 bdcd2736c..fab6e9a71 100644 --- a/packages/website/components/demo-footer.tsx +++ b/packages/website/components/demo-footer.tsx @@ -1,7 +1,9 @@ "use client"; import { type ReactElement } from "react"; +import Link from "next/link"; import { RotateCcw } from "lucide-react"; +import { Button } from "@/components/ui/button"; export const DemoFooter = (): ReactElement => { const handleRestartClick = () => { @@ -17,28 +19,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..fb5d214ae 100644 --- a/packages/website/components/install-tabs.tsx +++ b/packages/website/components/install-tabs.tsx @@ -1,11 +1,14 @@ "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"; 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 +369,45 @@ export const InstallTabs = ({ {headingText} {activeTabId === "cli" && ( - + )} )} -
-
- {installTabsData.map((tab) => { - const isActive = tab.id === activeTab.id; - - return ( - - ); - })} -
+ setActiveTabId(value)} + 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) => ( + + {tab.label} + + ))} +
{activeTabId === "cli" ? ( - + ) : (
- + {highlightedCode ? (
-
+ {activeTabId !== "cli" && ( {activeTab.description} @@ -452,9 +457,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..6eff2320e 100644 --- a/packages/website/components/table-of-contents.tsx +++ b/packages/website/components/table-of-contents.tsx @@ -1,6 +1,8 @@ "use client"; import { useEffect, useState } from "react"; +import Link from "next/link"; +import { cn } from "@/utils/cn"; interface TocHeading { id: string; @@ -46,7 +48,7 @@ export const TableOfContents = ({ headings }: TableOfContentsProps) => { }, [headings]); const handleClick = ( - event: React.MouseEvent, + event: React.MouseEvent, id: string, ) => { event.preventDefault(); @@ -74,17 +76,19 @@ export const TableOfContents = ({ headings }: TableOfContentsProps) => { return (
  • - 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/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..bd66d758b --- /dev/null +++ b/packages/website/components/ui/button.tsx @@ -0,0 +1,64 @@ +"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/70 hover:text-white", + 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 => { + if (asChild) { + return ( + + ); + } + + const { type = "button", ...buttonProps } = props; + + return ( +
    - + + {isExpanded && ( - - {children} - + + + {children} + + )} -
    + ); }; diff --git a/packages/website/components/ui/dropdown-menu.tsx b/packages/website/components/ui/dropdown-menu.tsx new file mode 100644 index 000000000..befa7a3a3 --- /dev/null +++ b/packages/website/components/ui/dropdown-menu.tsx @@ -0,0 +1,180 @@ +"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 { + className?: string; +} + +export const DropdownMenuSubContent = ({ + className, + ...props +}: DropdownMenuSubContentProps): ReactElement => ( + +); + +DropdownMenuSubContent.displayName = "DropdownMenuSubContent"; + +interface DropdownMenuContentProps + extends ComponentProps { + className?: string; +} + +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 { + className?: string; +} + +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 { + className?: string; +} + +export const DropdownMenuSeparator = ({ + className, + ...props +}: DropdownMenuSeparatorProps): ReactElement => ( + +); + +DropdownMenuSeparator.displayName = "DropdownMenuSeparator"; + +interface DropdownMenuShortcutProps extends ComponentProps<"span"> { + className?: string; +} + +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..a93badac1 --- /dev/null +++ b/packages/website/components/ui/hover-card.tsx @@ -0,0 +1,32 @@ +"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 { + className?: string; +} + +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..21f1be9b3 --- /dev/null +++ b/packages/website/components/ui/input.tsx @@ -0,0 +1,20 @@ +"use client"; + +import type { InputHTMLAttributes, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +interface InputProps extends InputHTMLAttributes { + className?: string; +} + +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..5e8968e24 --- /dev/null +++ b/packages/website/components/ui/scroll-area.tsx @@ -0,0 +1,46 @@ +"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 { + className?: string; +} + +export const ScrollArea = ({ + className, + children, + ...props +}: ScrollAreaProps): ReactElement => ( + + + {children} + + + + +); + +ScrollArea.displayName = "ScrollArea"; + +interface ScrollBarProps + extends ComponentProps { + className?: string; +} + +export const ScrollBar = ({ className, orientation = "vertical", ...props }: ScrollBarProps): ReactElement => ( + + + +); + +ScrollBar.displayName = "ScrollBar"; 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 && ( -
    -
    -
    - )} + +
    ); }; 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..2c0e84c1f --- /dev/null +++ b/packages/website/components/ui/table.tsx @@ -0,0 +1,92 @@ +import type { ComponentProps, ReactElement } from "react"; +import { cn } from "@/utils/cn"; + +interface TableProps extends ComponentProps<"table"> { + className?: string; +} + +export const Table = ({ className, ...props }: TableProps): ReactElement => ( +
    + + +); + +Table.displayName = "Table"; + +interface TableHeaderProps extends ComponentProps<"thead"> { + className?: string; +} + +export const TableHeader = ({ + className, + ...props +}: TableHeaderProps): ReactElement => ( + +); + +TableHeader.displayName = "TableHeader"; + +interface TableBodyProps extends ComponentProps<"tbody"> { + className?: string; +} + +export const TableBody = ({ className, ...props }: TableBodyProps): ReactElement => ( + +); + +TableBody.displayName = "TableBody"; + +interface TableRowProps extends ComponentProps<"tr"> { + className?: string; +} + +export const TableRow = ({ className, ...props }: TableRowProps): ReactElement => ( + +); + +TableRow.displayName = "TableRow"; + +interface TableHeadProps extends ComponentProps<"th"> { + className?: string; +} + +export const TableHead = ({ className, ...props }: TableHeadProps): ReactElement => ( +
    +); + +TableHead.displayName = "TableHead"; + +interface TableCellProps extends ComponentProps<"td"> { + className?: string; +} + +export const TableCell = ({ className, ...props }: TableCellProps): ReactElement => ( + +); + +TableCell.displayName = "TableCell"; + +interface TableCaptionProps extends ComponentProps<"caption"> { + className?: string; +} + +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..f9e8b50b1 --- /dev/null +++ b/packages/website/components/ui/tabs.tsx @@ -0,0 +1,72 @@ +"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 { + className?: string; +} + +export const Tabs = ({ className, ...props }: TabsProps): ReactElement => ( + +); + +Tabs.displayName = "Tabs"; + +interface TabsListProps extends ComponentProps { + className?: string; +} + +export const TabsList = ({ + className, + ...props +}: TabsListProps): ReactElement => ( + +); + +TabsList.displayName = "TabsList"; + +interface TabsTriggerProps extends ComponentProps { + className?: string; +} + +export const TabsTrigger = ({ + className, + ...props +}: TabsTriggerProps): ReactElement => ( + +); + +TabsTrigger.displayName = "TabsTrigger"; + +interface TabsContentProps extends ComponentProps { + className?: string; +} + +export const TabsContent = ({ + className, + ...props +}: TabsContentProps): ReactElement => ( + +); + +TabsContent.displayName = "TabsContent"; 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"; 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/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..cb6704a2b --- /dev/null +++ b/packages/website/e2e/blog-interactions.spec.ts @@ -0,0 +1,46 @@ +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 postTitleText = introPostLink.getByText("React Grab Is Now 1.0"); + const titleColorBeforeHover = await getStyleProperty( + postTitleText, + "color", + ); + await introPostLink.hover(); + await expect + .poll( + async () => + getStyleProperty( + postTitleText, + "color", + ), + { timeout: 2000 }, + ) + .not.toBe(titleColorBeforeHover); + + 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..4917cdcda --- /dev/null +++ b/packages/website/e2e/open-file-interactions.spec.ts @@ -0,0 +1,76 @@ +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", exact: true }); + + await expect(editorDropdownTrigger).toBeVisible(); + await expect(openButton).toBeVisible(); + + const openButtonBackgroundBeforeHover = await getStyleProperty( + openButton, + "background-color", + ); + await openButton.hover(); + const openButtonBackgroundAfterHover = await getStyleProperty( + openButton, + "background-color", + ); + expect(openButtonBackgroundAfterHover).not.toBe( + openButtonBackgroundBeforeHover, + ); + + 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.focus(); + await page.keyboard.press("Enter"); + + const vsCodeOption = page.getByRole("menuitem", { name: /vs code/i }); + await expect(vsCodeOption).toBeVisible(); + const optionBackgroundBeforeFocus = await getStyleProperty( + vsCodeOption, + "background-color", + ); + await vsCodeOption.focus(); + const optionBackgroundAfterFocus = await getStyleProperty( + vsCodeOption, + "background-color", + ); + expect(optionBackgroundAfterFocus).not.toBe(optionBackgroundBeforeFocus); + await page.keyboard.press("Enter"); + + 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 0ba9e9084..73db104ca 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -6,9 +6,17 @@ "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", + "@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", @@ -29,6 +37,7 @@ "tailwind-merge": "^3.4.0" }, "devDependencies": { + "@playwright/test": "^1.57.0", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", @@ -36,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 new file mode 100644 index 000000000..388771f65 --- /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 exec next dev -p ${PORT}`, + url: BASE_URL, + timeout: 120_000, + reuseExistingServer: true, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 130462c85..6752f2a26 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 @@ -807,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 @@ -828,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 @@ -2841,6 +2865,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 +2991,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 +3470,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 +9182,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 +9688,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 +9735,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 +9767,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 +9791,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 +9843,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 +9881,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 +9911,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 +9945,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 +9985,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 +10037,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 +10073,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 +10093,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 +10113,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 +10140,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 +10174,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 +10229,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 +10243,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 +10257,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 +10335,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 +10363,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 +10377,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 +10398,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 +10416,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 +10430,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) @@ -10405,14 +10770,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 @@ -14198,6 +14563,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 +14579,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 +14609,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 @@ -15250,6 +15642,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 +15656,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