"use client"; import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, CopyIcon, InfoIcon, LightbulbIcon, MenuIcon, SearchIcon, ThumbsDownIcon, ThumbsUpIcon, TriangleAlertIcon, XIcon, } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion, useScroll } from "motion/react"; import { type ReactNode, useCallback, useEffect, useId, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; export interface DocsNavGroup { label: string; items: { label: string; href: string; badge?: string }[]; } export interface DocsSection { id: string; title: string; level?: 2 | 3; content: ReactNode; } export interface DocsPageLink { label: string; href: string; } export interface DocsLayoutProps { product?: string; nav: DocsNavGroup[]; currentHref: string; breadcrumbs?: string[]; title: string; lead?: string; meta?: string; sections: DocsSection[]; prev?: DocsPageLink; next?: DocsPageLink; onNavigate?: (href: string) => void; onSearch?: () => void; className?: string; } const ease = [0.23, 1, 0.32, 1] as const; const CALLOUT = { note: { icon: InfoIcon, box: "border-info/25 bg-info/6 dark:bg-info/10", icon_: "text-info-foreground" }, tip: { icon: LightbulbIcon, box: "border-success/25 bg-success/6 dark:bg-success/10", icon_: "text-success-foreground" }, warning: { icon: TriangleAlertIcon, box: "border-warning/30 bg-warning/8 dark:bg-warning/10", icon_: "text-warning-foreground" }, }; export function DocsCallout({ tone = "note", title, children }: { tone?: keyof typeof CALLOUT; title?: string; children: ReactNode }) { const c = CALLOUT[tone]; return (
{title &&
{title}
}
{children}
); } const KEYWORDS = new Set([ "const", "let", "var", "await", "async", "function", "return", "import", "from", "export", "if", "else", "new", "throw", "true", "false", "null", ]); function highlight(line: string, lang: string) { const out: ReactNode[] = []; const re = /(\/\/.*$|#.*$)|("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b\d+\b)|(\s-{1,2}[A-Za-z-]+)|([A-Za-z_$][\w$]*)(?=\()|([A-Za-z_$][\w$]*)/g; let last = 0; let m: RegExpExecArray | null; let k = 0; while ((m = re.exec(line))) { if (m.index > last) out.push(line.slice(last, m.index)); const [text, comment, str, num, flag, fn, word] = m; let cls = ""; if (comment && (lang !== "bash" || comment.startsWith("#"))) cls = "text-muted-foreground italic"; else if (str) cls = "text-emerald-700 dark:text-emerald-300"; else if (num) cls = "text-amber-700 dark:text-amber-300"; else if (flag && lang === "bash") cls = "text-violet-700 dark:text-violet-300"; else if (fn) cls = "text-sky-700 dark:text-sky-300"; else if (word && KEYWORDS.has(word)) cls = "text-violet-700 dark:text-violet-300"; else if (word && lang === "bash" && m.index === 0) cls = "text-sky-700 dark:text-sky-300"; out.push(cls ? {text} : text); last = m.index + text.length; } if (last < line.length) out.push(line.slice(last)); return out; } export function DocsCode({ tabs, caption }: { tabs: { label: string; lang: string; code: string }[]; caption?: string }) { const reduce = useReducedMotion(); const layoutId = useId(); const [tab, setTab] = useState(0); const [copied, setCopied] = useState(false); useEffect(() => { if (!copied) return; const t = setTimeout(() => setCopied(false), 1500); return () => clearTimeout(t); }, [copied]); const current = tabs[tab]; const copy = async () => { try { await navigator.clipboard.writeText(current.code); } catch {} setCopied(true); }; return (
1 ? "tablist" : undefined}> {tabs.length > 1 ? ( tabs.map((t, i) => ( )) ) : ( {caption ?? current.label} )}
        
          {current.code.split("\n").map((line, i) => (
            
              {highlight(line, current.lang)}
            
          ))}
        
      
); } function Sidebar({ nav, currentHref, onNavigate, onSearch, product, }: { nav: DocsNavGroup[]; currentHref: string; onNavigate?: (href: string) => void; onSearch?: () => void; product: string; }) { return ( ); } export function DocsLayout({ product = "Relay", nav, currentHref, breadcrumbs = [], title, lead, meta, sections, prev, next, onNavigate, onSearch, className, }: DocsLayoutProps) { const reduce = useReducedMotion(); const uid = useId(); const scrollRef = useRef(null); const [active, setActive] = useState(sections[0]?.id); const [menuOpen, setMenuOpen] = useState(false); const [tocOpen, setTocOpen] = useState(false); const [feedback, setFeedback] = useState<"yes" | "no" | null>(null); const { scrollYProgress } = useScroll({ container: scrollRef }); const sync = useCallback(() => { const root = scrollRef.current; if (!root) return; const top = root.getBoundingClientRect().top; let current = sections[0]?.id; for (const s of sections) { const el = root.querySelector(`[data-docs-id="${s.id}"]`); if (el && el.getBoundingClientRect().top - top <= 96) current = s.id; } if (root.scrollTop + root.clientHeight >= root.scrollHeight - 4) current = sections[sections.length - 1]?.id; setActive(current); }, [sections]); useEffect(() => { sync(); }, [sync]); const jump = (id: string) => { const root = scrollRef.current; const el = root?.querySelector(`[data-docs-id="${id}"]`); if (!root || !el) return; const y = el.getBoundingClientRect().top - root.getBoundingClientRect().top + root.scrollTop - 24; root.scrollTo({ top: y, behavior: reduce ? "auto" : "smooth" }); setTocOpen(false); }; const activeIndex = Math.max(0, sections.findIndex((s) => s.id === active)); return (
{product} Docs
{breadcrumbs.length > 0 && (
    {breadcrumbs.map((b, i) => (
  1. {i > 0 && } {b}
  2. ))}
)}

{title}

{lead &&

{lead}

} {meta &&

{meta}

}
{tocOpen && (
    {sections.map((s) => (
  • ))}
)}
{sections.map((s) => { const H = s.level === 3 ? "h3" : "h2"; return (
{s.title} {s.content}
); })}
{feedback ? ( {feedback === "yes" ? "Thanks! Glad it helped." : "Thanks. We’ll use this to improve the page."} ) : (

Was this page helpful?

)}
{(prev || next) && ( )}
{menuOpen && ( <> setMenuOpen(false)} transition={{ duration: 0.2 }} type="button" /> e.key === "Escape" && setMenuOpen(false)} role="dialog" transition={{ duration: reduce ? 0 : 0.26, ease }} >
{product} Docs
{ setMenuOpen(false); onNavigate?.(href); }} onSearch={() => { setMenuOpen(false); onSearch?.(); }} product={product} />
)}
); }