add skills to docs (#576)

This commit is contained in:
Chris Tate
2026-03-01 09:02:06 -06:00
committed by GitHub
parent 14ec5b5ffa
commit 79d8dfe34c
19 changed files with 720 additions and 292 deletions
+4 -1
View File
@@ -10,7 +10,10 @@ export async function CodeBlock({ code, lang = "bash" }: CodeBlockProps) {
const trimmedCode = code.trim();
const html = await codeToHtml(trimmedCode, {
lang,
theme: "github-dark-default",
themes: {
light: "github-light-default",
dark: "github-dark-default",
},
});
return (
+71
View File
@@ -0,0 +1,71 @@
"use client";
import { useState } from "react";
import { usePathname } from "next/navigation";
export function CopyPageButton() {
const pathname = usePathname();
const [state, setState] = useState<"idle" | "loading" | "copied">("idle");
const handleCopy = async () => {
setState("loading");
try {
const response = await fetch(
`/api/docs-markdown?path=${encodeURIComponent(pathname)}`,
);
if (!response.ok) {
throw new Error("Failed to fetch markdown");
}
const markdown = await response.text();
await navigator.clipboard.writeText(markdown);
setState("copied");
setTimeout(() => setState("idle"), 2000);
} catch {
setState("idle");
}
};
return (
<button
onClick={handleCopy}
disabled={state === "loading"}
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs text-muted-foreground hover:text-foreground border border-border rounded-md hover:bg-muted transition-colors disabled:opacity-50"
aria-label="Copy page as Markdown"
>
{state === "copied" ? (
<>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
Copied
</>
) : (
<>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
Copy Page
</>
)}
</button>
);
}
+1 -1
View File
@@ -494,7 +494,7 @@ export function DocsChat({
{!open && (
<button
onClick={() => setOpen(true)}
className="fixed z-50 bottom-4 left-1/2 -translate-x-1/2 sm:left-auto sm:translate-x-0 sm:right-4 flex items-center gap-2 px-4 py-2 rounded-lg border bg-background text-primary shadow-lg hover:bg-primary hover:text-primary-foreground transition-colors text-sm font-medium"
className="fixed z-50 bottom-4 left-1/2 -translate-x-1/2 sm:left-auto sm:translate-x-0 sm:right-4 flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground shadow-lg hover:opacity-90 transition-opacity text-sm font-medium"
aria-label="Ask AI"
>
Ask AI
+81
View File
@@ -0,0 +1,81 @@
"use client";
import { useState, useMemo } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
Sheet,
SheetTrigger,
SheetContent,
SheetTitle,
} from "@/components/ui/sheet";
import { navigation, allDocsPages } from "@/lib/docs-navigation";
export function DocsMobileNav() {
const [open, setOpen] = useState(false);
const pathname = usePathname();
const currentPage = useMemo(() => {
const page = allDocsPages.find((p) => p.href === pathname);
return page ?? allDocsPages[0];
}, [pathname]);
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger className="lg:hidden sticky top-14 z-40 w-full px-6 py-3 bg-background/80 backdrop-blur-sm border-b border-border flex items-center justify-between focus:outline-none">
<div className="text-sm font-medium">{currentPage?.name}</div>
<div className="w-8 h-8 flex items-center justify-center">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-muted-foreground"
>
<line x1="8" y1="6" x2="21" y2="6" />
<line x1="8" y1="12" x2="21" y2="12" />
<line x1="8" y1="18" x2="21" y2="18" />
<line x1="3" y1="6" x2="3.01" y2="6" />
<line x1="3" y1="12" x2="3.01" y2="12" />
<line x1="3" y1="18" x2="3.01" y2="18" />
</svg>
</div>
</SheetTrigger>
<SheetContent side="left" showCloseButton={false} className="overflow-y-auto p-6">
<SheetTitle className="mb-6">Table of Contents</SheetTitle>
<nav className="space-y-6">
{navigation.map((section, sectionIndex) => (
<div key={section.title ?? sectionIndex}>
{section.title && (
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2">
{section.title}
</h4>
)}
<ul className="space-y-1">
{section.items.map((item) => (
<li key={item.href}>
<Link
href={item.href}
onClick={() => setOpen(false)}
className={`text-sm block py-2 transition-colors ${
pathname === item.href
? "text-primary font-medium"
: "text-muted-foreground hover:text-foreground"
}`}
>
{item.name}
</Link>
</li>
))}
</ul>
</div>
))}
</nav>
</SheetContent>
</Sheet>
);
}
+44
View File
@@ -0,0 +1,44 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import { navigation } from "@/lib/docs-navigation";
export function DocsSidebar() {
const pathname = usePathname();
return (
<nav className="space-y-6 pb-8">
{navigation.map((section, sectionIndex) => (
<div key={section.title ?? sectionIndex}>
{section.title && (
<h4 className="text-xs font-normal text-muted-foreground/50 uppercase tracking-wider mb-2">
{section.title}
</h4>
)}
<ul className="space-y-1">
{section.items.map((item) => {
const isActive = pathname === item.href;
return (
<li key={item.href}>
<Link
href={item.href}
className={cn(
"text-sm transition-colors block py-1",
isActive
? "text-primary font-medium"
: "text-muted-foreground hover:text-foreground",
)}
>
{item.name}
</Link>
</li>
);
})}
</ul>
</div>
))}
</nav>
);
}
+11 -26
View File
@@ -1,14 +1,11 @@
"use client";
import Link from "next/link";
import { useMobileNav } from "./mobile-nav-context";
import { ThemeToggle } from "./theme-toggle";
export function Header() {
const { isOpen, toggle } = useMobileNav();
return (
<header className="sticky top-0 z-50 bg-background/90 backdrop-blur-sm">
<header className="sticky top-0 z-50 bg-white/90 backdrop-blur-sm dark:bg-neutral-950/90">
<div className="flex h-14 items-center justify-between px-4 gap-6">
<div className="flex items-center gap-2">
<Link href="https://vercel.com" title="Made with love by Vercel">
@@ -25,10 +22,10 @@ export function Header() {
clipRule="evenodd"
d="M8 1L16 15H0L8 1Z"
fill="currentColor"
></path>
/>
</svg>
</Link>
<span className="text-border">
<span className="text-neutral-300 dark:text-neutral-700">
<svg
data-testid="geist-icon"
height="16"
@@ -42,11 +39,14 @@ export function Header() {
clipRule="evenodd"
d="M4.01526 15.3939L4.3107 14.7046L10.3107 0.704556L10.6061 0.0151978L11.9849 0.606077L11.6894 1.29544L5.68942 15.2954L5.39398 15.9848L4.01526 15.3939Z"
fill="currentColor"
></path>
/>
</svg>
</span>
<Link href="/">
<span className="font-medium tracking-tight text-lg" style={{ fontFamily: "var(--font-geist-pixel-square)" }}>
<span
className="font-medium tracking-tight text-lg"
style={{ fontFamily: "var(--font-geist-pixel-square)" }}
>
agent-browser
</span>
</Link>
@@ -56,7 +56,7 @@ export function Header() {
href="https://github.com/vercel-labs/agent-browser"
target="_blank"
rel="noopener noreferrer"
className="hidden sm:flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
className="flex items-center gap-1.5 text-sm text-neutral-500 hover:text-neutral-900 transition-colors dark:text-neutral-400 dark:hover:text-neutral-100"
>
<svg
viewBox="0 0 16 16"
@@ -66,32 +66,17 @@ export function Header() {
>
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
</svg>
<span>14k</span>
<span>16k</span>
</a>
<a
href="https://www.npmjs.com/package/agent-browser"
target="_blank"
rel="noopener noreferrer"
className="hidden sm:block text-sm text-muted-foreground hover:text-foreground transition-colors"
className="text-sm text-neutral-500 hover:text-neutral-900 transition-colors dark:text-neutral-400 dark:hover:text-neutral-100"
>
npm
</a>
<ThemeToggle />
<button
onClick={toggle}
className="lg:hidden p-2 -mr-2 text-muted-foreground hover:text-foreground 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>
</nav>
</div>
</header>
@@ -1,45 +0,0 @@
"use client";
import { createContext, useContext, useState, useEffect } from "react";
import { usePathname } from "next/navigation";
type MobileNavContextType = {
isOpen: boolean;
setIsOpen: (open: boolean) => void;
toggle: () => void;
};
const MobileNavContext = createContext<MobileNavContextType | null>(null);
export function MobileNavProvider({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
const pathname = usePathname();
useEffect(() => {
setIsOpen(false);
}, [pathname]);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") setIsOpen(false);
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, []);
const toggle = () => setIsOpen(!isOpen);
return (
<MobileNavContext.Provider value={{ isOpen, setIsOpen, toggle }}>
{children}
</MobileNavContext.Provider>
);
}
export function useMobileNav() {
const context = useContext(MobileNavContext);
if (!context) {
throw new Error("useMobileNav must be used within a MobileNavProvider");
}
return context;
}
-66
View File
@@ -1,66 +0,0 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useMobileNav } from "./mobile-nav-context";
import { navigation } from "@/lib/docs-navigation";
export function Sidebar() {
const pathname = usePathname();
const { isOpen, setIsOpen } = useMobileNav();
return (
<>
{/* Mobile overlay */}
{isOpen && (
<div
className="lg:hidden fixed inset-0 z-40 bg-background/80"
onClick={() => setIsOpen(false)}
/>
)}
{/* Sidebar */}
<aside
className={`
fixed lg:sticky top-14 left-0 z-50 lg:z-auto
w-56 lg:w-48 h-[calc(100vh-3.5rem)]
bg-background
transform transition-transform duration-150 ease-out
${isOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"}
`}
>
<div className="h-full overflow-y-auto py-5 pl-3 pr-5">
<nav className="space-y-4">
{navigation.map((section, sectionIndex) => (
<div key={section.title ?? sectionIndex}>
{section.title && (
<div className="px-2 pb-1 text-xs font-medium text-muted-foreground/60 uppercase tracking-wider">
{section.title}
</div>
)}
<div className="space-y-0.5">
{section.items.map((item) => {
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
className={`block px-2 py-1.5 text-sm transition-colors ${
isActive
? "text-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
>
{item.name}
</Link>
);
})}
</div>
</div>
))}
</nav>
</div>
</aside>
</>
);
}
+1 -1
View File
@@ -18,7 +18,7 @@ export function ThemeToggle() {
return (
<button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="w-8 h-8 flex items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
className="w-8 h-8 flex items-center justify-center rounded-md text-neutral-500 hover:text-neutral-900 hover:bg-neutral-100 transition-colors dark:text-neutral-400 dark:hover:text-neutral-100 dark:hover:bg-neutral-800"
aria-label="Toggle theme"
>
{theme === "dark" ? (