add docs (#54)
* docs * updates * Fix: The handleCopy function fails to handle errors from navigator.clipboard.writeText(), causing unhandled exceptions and misleading UI feedback when clipboard operations fail. Co-authored-by: ctate <chris@ctate.dev> * Fix: The benchmark file uses emojis (📊, 🚀, 🔨, 📈, 📋, ✅, ⏱️, ⚠) in console output, violating repository guidelines that forbid emojis in code and output. Co-authored-by: ctate <chris@ctate.dev> * Remove benchmark/run.ts from PR --------- Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Vercel <vercel[bot]@users.noreply.github.com>
parent
b4bc761168
commit
4713c8b520
@@ -0,0 +1,22 @@
|
||||
import { codeToHtml } from "shiki";
|
||||
import { CopyButton } from "./copy-button";
|
||||
|
||||
interface CodeBlockProps {
|
||||
code: string;
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
export async function CodeBlock({ code, lang = "bash" }: CodeBlockProps) {
|
||||
const trimmedCode = code.trim();
|
||||
const html = await codeToHtml(trimmedCode, {
|
||||
lang,
|
||||
theme: "github-dark-default",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="code-block relative group">
|
||||
<CopyButton code={trimmedCode} />
|
||||
<div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
interface CopyButtonProps {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export function CopyButton({ code }: CopyButtonProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (error) {
|
||||
console.error("Failed to copy to clipboard:", error);
|
||||
// Optionally, you could set an error state or show a toast notification here
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="absolute top-2 right-2 p-1.5 rounded text-[#666] hover:text-[#999] hover:bg-[#333] opacity-0 group-hover:opacity-100 transition-all"
|
||||
aria-label="Copy code"
|
||||
>
|
||||
{copied ? (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const navigation = [
|
||||
{ name: "Introduction", href: "/" },
|
||||
{ name: "Installation", href: "/installation" },
|
||||
{ name: "Quick Start", href: "/quick-start" },
|
||||
{ name: "Commands", href: "/commands" },
|
||||
{ name: "Selectors", href: "/selectors" },
|
||||
{ name: "Sessions", href: "/sessions" },
|
||||
{ name: "Snapshots", href: "/snapshots" },
|
||||
{ name: "Agent Mode", href: "/agent-mode" },
|
||||
{ name: "CDP Mode", href: "/cdp-mode" },
|
||||
];
|
||||
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") setIsOpen(false);
|
||||
};
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
return () => document.removeEventListener("keydown", handleEscape);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile header */}
|
||||
<header className="lg:hidden fixed top-0 left-0 right-0 z-50 bg-black/90 backdrop-blur-sm border-b border-[#222] px-4 py-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link href="/" className="text-sm font-medium">
|
||||
agent-browser
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="p-2 -mr-2 text-[#888] hover:text-white transition-colors"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{isOpen ? (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Mobile overlay */}
|
||||
{isOpen && (
|
||||
<div
|
||||
className="lg:hidden fixed inset-0 z-40 bg-black/80"
|
||||
onClick={() => setIsOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`
|
||||
fixed lg:sticky top-0 left-0 z-50 lg:z-auto
|
||||
w-56 lg:w-48 h-screen
|
||||
bg-black border-r border-[#222]
|
||||
transform transition-transform duration-150 ease-out
|
||||
${isOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"}
|
||||
pt-14 lg:pt-0
|
||||
`}
|
||||
>
|
||||
<div className="h-full overflow-y-auto p-5">
|
||||
{/* Desktop header */}
|
||||
<div className="mb-8 hidden lg:block">
|
||||
<Link href="/" className="text-sm font-medium">
|
||||
agent-browser
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav className="space-y-0.5">
|
||||
{navigation.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={`block px-2 py-1.5 text-[13px] transition-colors ${
|
||||
isActive
|
||||
? "text-white"
|
||||
: "text-[#666] hover:text-[#999]"
|
||||
}`}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="mt-8 pt-4 border-t border-[#222] space-y-0.5">
|
||||
<a
|
||||
href="https://github.com/vercel-labs/agent-browser"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block px-2 py-1.5 text-[13px] text-[#666] hover:text-[#999] transition-colors"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
<a
|
||||
href="https://www.npmjs.com/package/agent-browser"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block px-2 py-1.5 text-[13px] text-[#666] hover:text-[#999] transition-colors"
|
||||
>
|
||||
npm
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user