feat: add docs source loader and mdx components

This commit is contained in:
codebanditssss 2026-06-03 22:12:25 +05:30
parent 9242868b5a
commit b3ed10a5ae
6 changed files with 402 additions and 0 deletions

View File

@ -0,0 +1,65 @@
import Link from "next/link";
import { DocsBody, DocsPage } from "fumadocs-ui/page";
export function DocsMissingPage() {
return (
<DocsPage
toc={[]}
tableOfContent={{
enabled: false,
}}
breadcrumb={{
enabled: true,
includePage: false,
}}
footer={{
enabled: false,
}}
>
<DocsBody>
<div className="not-prose docs-missing-wrap">
<div className="docs-missing-card">
<div className="docs-missing-label">
docs / checkout failed
</div>
<div className="docs-missing-content">
<section className="docs-missing-copy">
<h2>
This page checked out the wrong worktree.
</h2>
<p>
The docs were rebuilt, and this URL did not survive the merge. Start from the docs
index, or use search in the sidebar to find where it landed.
</p>
<div className="docs-missing-actions">
<Link
href="/docs"
className="docs-missing-primary"
>
Browse docs
</Link>
<Link
href="/"
className="docs-missing-secondary"
>
Home
</Link>
</div>
</section>
<div className="docs-missing-terminal">
<div className="docs-missing-dots">
<span className="docs-missing-dot-red" />
<span className="docs-missing-dot-yellow" />
<span className="docs-missing-dot-green" />
</div>
<p className="docs-missing-command">$ ao docs resolve</p>
<p className="docs-missing-status">status: missing</p>
<p>next: /docs</p>
</div>
</div>
</div>
</div>
</DocsBody>
</DocsPage>
);
}

View File

@ -0,0 +1,100 @@
/**
* Brand logo component for docs pages.
*
* Renders real brand assets from /public/docs/logos/ when available, with
* name-aliasing. Mono brand SVGs are pre-processed to use white fill for the
* dark theme. Falls back to a monogram badge for anything not in the set.
*/
import type { CSSProperties } from "react";
/** Aliases: request name -> filename under /docs/logos/. */
const ALIASES: Record<string, string> = {
claude: "claude-code",
chatgpt: "openai",
};
/** Extension for each logo file. Defaults to svg; override for raster assets. */
const LOGO_EXT: Record<string, string> = {
aider: "png",
};
/** Brands with a real asset file under /public/docs/logos/. */
const FILE_LOGOS = new Set([
"aider",
"anthropic",
"apple",
"claude-code",
"codex",
"composio",
"cursor",
"discord",
"docker",
"github",
"gitlab",
"iterm2",
"linear",
"linux",
"microsoft",
"openai",
"openclaw",
"opencode",
"slack",
"tmux",
"web",
"webhook",
"windows",
]);
export interface LogoProps {
/** Brand name (case-insensitive). */
name: string;
/** Size in pixels. Default 20. */
size?: number;
className?: string;
/** Accepted for backwards compat with existing MDX; no-op. */
color?: boolean;
}
export function Logo({ name, size = 20, className }: LogoProps) {
const key = name.toLowerCase();
const resolved = ALIASES[key] ?? key;
const baseStyle: CSSProperties = { flexShrink: 0, width: size, height: size };
if (FILE_LOGOS.has(resolved)) {
const ext = LOGO_EXT[resolved] ?? "svg";
return (
<img
src={`/docs/logos/${resolved}.${ext}`}
alt=""
aria-hidden="true"
className={className}
style={baseStyle}
width={size}
height={size}
/>
);
}
const initial = name.charAt(0).toUpperCase();
return (
<span
className={className}
style={{
...baseStyle,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
borderRadius: size * 0.22,
backgroundColor: "var(--color-bg-elevated, #2a2827)",
color: "#ffffff",
fontSize: size * 0.55,
fontWeight: 700,
lineHeight: 1,
fontFamily: "var(--font-geist-sans), ui-sans-serif, system-ui",
}}
aria-hidden="true"
>
{initial}
</span>
);
}

View File

@ -0,0 +1,102 @@
/**
* Shows a three-column support matrix for macOS / Linux / Windows.
* Use at the top of plugin pages to set platform expectations immediately.
*/
import type { ReactNode } from "react";
import { Logo } from "./Logo";
type Status = "full" | "partial" | "none";
export interface PlatformSupportProps {
macos?: Status;
linux?: Status;
windows?: Status;
note?: ReactNode;
}
const LABEL: Record<Status, string> = {
full: "Supported",
partial: "In progress",
none: "Not supported",
};
const DOT_COLOR: Record<Status, string> = {
full: "var(--color-accent-amber, #f97316)",
partial: "var(--color-accent-amber-dim, #a3581b)",
none: "var(--color-text-muted, #605e5c)",
};
function Cell({ platform, status }: { platform: "macos" | "linux" | "windows"; status: Status }) {
const logoName = platform === "macos" ? "apple" : platform;
const title = platform === "macos" ? "macOS" : platform === "linux" ? "Linux" : "Windows";
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: "0.5rem",
padding: "0.5rem 0.75rem",
borderRadius: "0.375rem",
border: "1px solid var(--color-fd-border)",
backgroundColor: "var(--color-fd-card)",
flex: "1 1 0",
minWidth: 0,
}}
>
<Logo name={logoName} size={18} />
<div style={{ display: "flex", flexDirection: "column", minWidth: 0 }}>
<span style={{ fontSize: "0.8125rem", fontWeight: 600, color: "var(--color-fd-foreground)" }}>
{title}
</span>
<span
style={{
fontSize: "0.75rem",
color: "var(--color-fd-muted-foreground)",
display: "inline-flex",
alignItems: "center",
gap: "0.375rem",
}}
>
<span
style={{
width: "6px",
height: "6px",
borderRadius: "999px",
backgroundColor: DOT_COLOR[status],
display: "inline-block",
}}
/>
{LABEL[status]}
</span>
</div>
</div>
);
}
export function PlatformSupport({
macos = "full",
linux = "full",
windows = "full",
note,
}: PlatformSupportProps) {
return (
<div style={{ margin: "1.25rem 0" }}>
<div style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap" }}>
<Cell platform="macos" status={macos} />
<Cell platform="linux" status={linux} />
<Cell platform="windows" status={windows} />
</div>
{note && (
<p
style={{
margin: "0.5rem 0 0 0",
fontSize: "0.8125rem",
color: "var(--color-fd-muted-foreground)",
}}
>
{note}
</p>
)}
</div>
);
}

View File

@ -0,0 +1,91 @@
/**
* Catalog-style card for a single plugin.
* Used on the plugin overview pages to let users scan by logo + one-liner.
*/
import Link from "next/link";
import type { ReactNode } from "react";
import { Logo } from "./Logo";
export interface PluginCardProps {
name: string;
logo: string;
href: string;
description: string;
badge?: ReactNode;
}
export function PluginCard({ name, logo, href, description, badge }: PluginCardProps) {
return (
<Link
href={href}
style={{
display: "flex",
alignItems: "flex-start",
gap: "0.875rem",
padding: "1rem",
borderRadius: "0.5rem",
border: "1px solid var(--color-fd-border)",
backgroundColor: "var(--color-fd-card)",
textDecoration: "none",
transition: "border-color 150ms, transform 150ms",
}}
className="ao-plugin-card"
>
<span
style={{
width: "36px",
height: "36px",
borderRadius: "0.375rem",
backgroundColor: "var(--color-fd-muted)",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
color: "var(--color-fd-foreground)",
flexShrink: 0,
}}
>
<Logo name={logo} size={22} />
</span>
<span style={{ display: "flex", flexDirection: "column", gap: "0.25rem", minWidth: 0 }}>
<span
style={{
display: "inline-flex",
alignItems: "center",
gap: "0.5rem",
fontSize: "0.9375rem",
fontWeight: 600,
color: "var(--color-fd-foreground)",
margin: 0,
}}
>
{name}
{badge}
</span>
<span
style={{
fontSize: "0.8125rem",
color: "var(--color-fd-muted-foreground)",
lineHeight: 1.45,
}}
>
{description}
</span>
</span>
</Link>
);
}
export function PluginGrid({ children }: { children: ReactNode }) {
return (
<div
style={{
display: "grid",
gap: "0.75rem",
gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
margin: "1.25rem 0",
}}
>
{children}
</div>
);
}

View File

@ -0,0 +1,37 @@
/**
* Shared MDX component registry for docs pages.
* Exposes docs-specific components and a small Accordion pair used by content.
*/
import type { ReactNode } from "react";
import defaultMdxComponents from "fumadocs-ui/mdx";
import type { MDXComponents } from "mdx/types";
import { Logo } from "./Logo";
import { PlatformSupport } from "./PlatformSupport";
import { PluginCard, PluginGrid } from "./PluginCard";
function Accordions({ children }: { children: ReactNode }) {
return <div className="my-6 space-y-3">{children}</div>;
}
function Accordion({ title, children }: { title: ReactNode; children: ReactNode }) {
return (
<details className="rounded-md border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-4 py-3">
<summary className="cursor-pointer list-none text-sm font-medium text-[var(--color-text-primary)]">
{title}
</summary>
<div className="mt-3 text-sm text-[var(--color-text-secondary)]">{children}</div>
</details>
);
}
export function getMDXComponents(): MDXComponents {
return {
...defaultMdxComponents,
Accordion,
Accordions,
Logo,
PlatformSupport,
PluginCard,
PluginGrid,
};
}

View File

@ -0,0 +1,7 @@
import { loader } from "fumadocs-core/source";
import { docs } from ".source";
export const source = loader({
baseUrl: "/docs",
source: docs.toFumadocsSource(),
});