+ {/* ── Install ── */}
+
+
+
+ {installCommand}
+
+
-
- {activeTab === "preview" && (
-
-
-
- )}
+ {docs?.usage && (
+
+
+
+
+ Usage
+
+
+
+
+
+
+
+ )}
- {activeTab === "source" && spec?.source && (
-
-
-
-
- {spec.filePath}
-
-
-
-
-
-
-
- )}
+ {/* ── Tabs: Preview / Source / Demo Code ── */}
+
+
+ {(
+ [
+ ["preview", "Preview"],
+ ...(spec?.source ? [["source", "Source"]] : []),
+ ...(demoSource ? [["demo", "Demo"]] : []),
+ ] as [string, string][]
+ ).map(([key, label]) => (
+
+ ))}
+
- {activeTab === "demo" && demoSource && (
-
-
-
-
- {component.slug}-demo.tsx
-
-
-
-
-
-
-
- )}
-
-
+
+ {activeTab === "preview" && (
+
+
+
+ )}
- {/* ── Spec Sheet ── */}
-
-
-
- Specification
-
+ {activeTab === "source" && spec?.source && (
+
+
+
+
+ {spec.filePath}
+
+
+
+
+
+
+
+ )}
+
+ {activeTab === "demo" && demoSource && (
+
+
+
+
+ {component.slug}-demo.tsx
+
+
+
+
+
+
+
+ )}
+
-
- {/* Variants */}
- {spec && spec.variants.length > 0 && (
-
-
-
- )}
+ {/* ── Spec Sheet ── */}
+
+
+
+ Specification
+
+
- {/* Sub-components */}
- {componentExports.length > 1 && (
-
-
- {componentExports.map((exp) => (
-
- {exp}
-
- ))}
-
-
- )}
+
+ {/* Variants */}
+ {spec && spec.variants.length > 0 && (
+
+
+
+ )}
- {/* Data slots */}
- {spec && spec.dataSlots.length > 0 && (
-
-
- {spec.dataSlots.map((slot) => (
-
- [data-slot="{slot}"]
-
- ))}
-
-
- )}
+ {/* Sub-components */}
+ {componentExports.length > 1 && (
+
+
+ {componentExports.map((exp) => (
+
+ {exp}
+
+ ))}
+
+
+ )}
+
+ {/* Data slots */}
+ {spec && spec.dataSlots.length > 0 && (
+
+
+ {spec.dataSlots.map((slot) => (
+
+ [data-slot="{slot}"]
+
+ ))}
+
+
+ )}
+
+ {/* Registry dependencies */}
+ {component.registryDependencies.length > 0 && (
+
+
+ {component.registryDependencies.map((dep) => (
+
+ {dep}
+
+ ))}
+
+
+ )}
+
+ {/* npm Dependencies */}
+ {component.dependencies.length > 0 && (
+
+
+ {component.dependencies.map((dep) => (
+
+ {dep}
+
+ ))}
+
+
+ )}
+
+ {/* File path */}
+ {spec?.filePath && (
+
+
+ {spec.filePath}
+
+
+ )}
+
+
+
+ {docs && docs.props.length > 0 && (
+
+
+
+ Props
+
+
+
+ {docs.props.map((prop) => (
+
+
+
+ {prop.type}
+
+ {prop.default && (
+
+ Default:{" "}
+ {prop.default}
+
+ )}
+ {prop.description}
+
+
+ ))}
+
+
+ )}
- {/* Registry dependencies */}
- {component.registryDependencies.length > 0 && (
-
+ {docs && docs.composedWith.length > 0 && (
+
+
+
+ Works With
+
+
+
- {component.registryDependencies.map((dep) => (
+ {docs.composedWith.map((slug) => (
- {dep}
+ {slug}
))}
-
- )}
+
+
+ )}
- {/* npm Dependencies */}
- {component.dependencies.length > 0 && (
-
-
- {component.dependencies.map((dep) => (
-
0 && (
+
+
+ Examples
+
+ {docs.examples.map((example) => (
+
+
+
{example.title}
+ {example.description && (
+
+ {example.description}
+
+ )}
+
+
+
- {dep}
-
- ))}
+
+
+
-
- )}
+ ))}
+
+ )}
- {/* File path */}
- {spec?.filePath && (
-
- {spec.filePath}
-
+ {/* ── Prev / Next (bottom, below xl) ── */}
+
+ {prev ? (
+
+
+
+
+ Previous
+
+
+ {prev.name}
+
+
+
+ ) : (
+
+ )}
+ {next ? (
+
+
+ Next
+
+ {next.name}
+
+
+
+
+ ) : (
+
)}
-
- {/* ── Prev / Next ── */}
-
- {prev ? (
-
-
-
-
- Previous
-
-
- {prev.name}
-
-
-
- ) : (
-
- )}
- {next ? (
-
-
- Next
-
- {next.name}
-
-
-
-
- ) : (
-
- )}
+ {/* spacer for bottom nav hidden on xl */}
+
-
-
+
+ >
);
}
diff --git a/packages/ghost-ui/src/components/docs/demo-loader.tsx b/packages/ghost-ui/src/components/docs/demo-loader.tsx
index 8efe902..3e4a7fb 100644
--- a/packages/ghost-ui/src/components/docs/demo-loader.tsx
+++ b/packages/ghost-ui/src/components/docs/demo-loader.tsx
@@ -1,8 +1,43 @@
"use client";
-import { lazy, Suspense } from "react";
+import { lazy, Suspense, useMemo } from "react";
import { Skeleton } from "@/components/ui/skeleton";
+const exampleModules = import.meta.glob<{ default: React.ComponentType }>(
+ "/src/components/docs/examples/**/*.tsx",
+);
+
+const LoadingSkeleton = () => (
+
+
+
+
+);
+
+export function ExampleLoader({
+ componentSlug,
+ exampleName,
+}: {
+ componentSlug: string;
+ exampleName: string;
+}) {
+ const path = `/src/components/docs/examples/${componentSlug}/${exampleName}.tsx`;
+ const loader = exampleModules[path];
+
+ const LazyComponent = useMemo(() => {
+ if (!loader) return null;
+ return lazy(loader);
+ }, [loader]);
+
+ if (!LazyComponent) return null;
+
+ return (
+
}>
+
+
+ );
+}
+
/* eslint-disable @typescript-eslint/no-explicit-any */
const wrap = (imp: Promise
, name: string) =>
imp.then((m: any) => ({ default: m[name] }));
diff --git a/packages/ghost-ui/src/components/docs/examples/code-block/with-diff.tsx b/packages/ghost-ui/src/components/docs/examples/code-block/with-diff.tsx
new file mode 100644
index 0000000..2f4b633
--- /dev/null
+++ b/packages/ghost-ui/src/components/docs/examples/code-block/with-diff.tsx
@@ -0,0 +1,86 @@
+import { useState } from "react";
+import {
+ CodeBlock,
+ CodeBlockActions,
+ CodeBlockCopyButton,
+ CodeBlockHeader,
+ CodeBlockLanguageSelector,
+ CodeBlockLanguageSelectorContent,
+ CodeBlockLanguageSelectorItem,
+ CodeBlockLanguageSelectorTrigger,
+ CodeBlockLanguageSelectorValue,
+} from "@/components/ai-elements/code-block";
+
+const snippets: Record<
+ string,
+ { code: string; language: "typescript" | "python" | "rust" }
+> = {
+ typescript: {
+ code: `function fibonacci(n: number): number {
+ if (n <= 1) return n;
+ return fibonacci(n - 1) + fibonacci(n - 2);
+}
+
+console.log(fibonacci(10)); // 55`,
+ language: "typescript",
+ },
+ python: {
+ code: `def fibonacci(n: int) -> int:
+ if n <= 1:
+ return n
+ return fibonacci(n - 1) + fibonacci(n - 2)
+
+print(fibonacci(10)) # 55`,
+ language: "python",
+ },
+ rust: {
+ code: `fn fibonacci(n: u32) -> u32 {
+ if n <= 1 {
+ return n;
+ }
+ fibonacci(n - 1) + fibonacci(n - 2)
+}
+
+fn main() {
+ println!("{}", fibonacci(10)); // 55
+}`,
+ language: "rust",
+ },
+};
+
+export default function CodeBlockWithDiff() {
+ const [lang, setLang] = useState("typescript");
+ const snippet = snippets[lang];
+
+ return (
+
+
+
+
+
+
+
+
+
+ TypeScript
+
+
+ Python
+
+
+ Rust
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/ghost-ui/src/components/docs/examples/conversation/with-messages.tsx b/packages/ghost-ui/src/components/docs/examples/conversation/with-messages.tsx
new file mode 100644
index 0000000..dbb8fde
--- /dev/null
+++ b/packages/ghost-ui/src/components/docs/examples/conversation/with-messages.tsx
@@ -0,0 +1,48 @@
+import {
+ Conversation,
+ ConversationContent,
+ ConversationScrollButton,
+} from "@/components/ai-elements/conversation";
+import {
+ Message,
+ MessageContent,
+ MessageResponse,
+} from "@/components/ai-elements/message";
+
+const messages = [
+ { id: "1", role: "user" as const, text: "What is TypeScript?" },
+ {
+ id: "2",
+ role: "assistant" as const,
+ text: "TypeScript is a strongly-typed superset of JavaScript developed by Microsoft. It adds optional static type checking, interfaces, enums, and other features that help catch errors at compile time rather than at runtime.",
+ },
+ { id: "3", role: "user" as const, text: "How does it compare to Flow?" },
+ {
+ id: "4",
+ role: "assistant" as const,
+ text: "Both TypeScript and Flow add static types to JavaScript, but they differ in key ways:\n\n- **Adoption**: TypeScript has much wider community adoption and tooling support.\n- **Type system**: TypeScript uses a structural type system; Flow also uses structural typing but with some nominal typing features.\n- **Tooling**: TypeScript ships its own compiler (`tsc`), while Flow relies on Babel for compilation.\n- **Ecosystem**: TypeScript has DefinitelyTyped with type definitions for thousands of packages.",
+ },
+];
+
+export default function ConversationWithMessages() {
+ return (
+
+
+
+ {messages.map((msg) => (
+
+
+ {msg.role === "assistant" ? (
+ {msg.text}
+ ) : (
+ {msg.text}
+ )}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/packages/ghost-ui/src/components/docs/examples/message/streaming.tsx b/packages/ghost-ui/src/components/docs/examples/message/streaming.tsx
new file mode 100644
index 0000000..3529d16
--- /dev/null
+++ b/packages/ghost-ui/src/components/docs/examples/message/streaming.tsx
@@ -0,0 +1,29 @@
+import {
+ Message,
+ MessageContent,
+ MessageResponse,
+} from "@/components/ai-elements/message";
+import { Shimmer } from "@/components/ai-elements/shimmer";
+
+export default function MessageStreaming() {
+ return (
+
+
+
+ Explain quantum entanglement in simple terms.
+
+
+
+
+
+
+ {`Quantum entanglement is when two particles become linked so that measuring one instantly affects the other, no matter how far apart they are. Einstein called it "spooky action at a distance."`}
+
+
+ Generating...
+
+
+
+
+ );
+}
diff --git a/packages/ghost-ui/src/components/docs/examples/message/with-actions.tsx b/packages/ghost-ui/src/components/docs/examples/message/with-actions.tsx
new file mode 100644
index 0000000..115c468
--- /dev/null
+++ b/packages/ghost-ui/src/components/docs/examples/message/with-actions.tsx
@@ -0,0 +1,47 @@
+import {
+ CopyIcon,
+ RefreshCwIcon,
+ ThumbsDownIcon,
+ ThumbsUpIcon,
+} from "lucide-react";
+import {
+ Message,
+ MessageAction,
+ MessageActions,
+ MessageContent,
+ MessageResponse,
+} from "@/components/ai-elements/message";
+
+export default function MessageWithActions() {
+ return (
+
+
+
+ How do I center a div?
+
+
+
+
+
+
+ {`You can center a div using **flexbox**:\n\n\`\`\`css\n.parent {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\`\`\`\n\nOr use **grid**:\n\n\`\`\`css\n.parent {\n display: grid;\n place-items: center;\n}\n\`\`\``}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/ghost-ui/src/components/docs/examples/prompt-input/with-attachments.tsx b/packages/ghost-ui/src/components/docs/examples/prompt-input/with-attachments.tsx
new file mode 100644
index 0000000..2d67b79
--- /dev/null
+++ b/packages/ghost-ui/src/components/docs/examples/prompt-input/with-attachments.tsx
@@ -0,0 +1,31 @@
+import {
+ PromptInput,
+ PromptInputActionAddAttachments,
+ PromptInputFooter,
+ PromptInputSubmit,
+ PromptInputTextarea,
+ PromptInputTools,
+} from "@/components/ai-elements/prompt-input";
+
+export default function PromptInputWithAttachments() {
+ return (
+
+
{
+ console.log("Submitted:", msg);
+ }}
+ >
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/ghost-ui/src/components/docs/primitives/calendar-demo.tsx b/packages/ghost-ui/src/components/docs/primitives/calendar-demo.tsx
index 5f57901..0e331b0 100644
--- a/packages/ghost-ui/src/components/docs/primitives/calendar-demo.tsx
+++ b/packages/ghost-ui/src/components/docs/primitives/calendar-demo.tsx
@@ -40,7 +40,7 @@ export function CalendarDemo() {
selected={range}
onSelect={setRange}
numberOfMonths={3}
- className="hidden rounded-md border @4xl:flex [&>div]:gap-5"
+ className="hidden rounded-md border @2xl:flex [&>div]:gap-5"
/>
);
diff --git a/packages/ghost-ui/src/components/docs/primitives/carousel-demo.tsx b/packages/ghost-ui/src/components/docs/primitives/carousel-demo.tsx
index 4d7ad7e..f1846bb 100644
--- a/packages/ghost-ui/src/components/docs/primitives/carousel-demo.tsx
+++ b/packages/ghost-ui/src/components/docs/primitives/carousel-demo.tsx
@@ -11,7 +11,7 @@ import {
export function CarouselDemo() {
return (
-