Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/ghost-ui/src/app/components/[name]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Navigate, useParams } from "react-router";
import { ComponentPageShell } from "@/components/docs/component-page-shell";
import { getComponentDoc } from "@/lib/component-docs";
import {
getCategory,
getComponent,
Expand Down Expand Up @@ -42,6 +43,7 @@ export default function ComponentPage() {

const demoSource = getDemoSource(component.slug, component.demoSource);
const spec = getComponentSpec(component.slug);
const docs = getComponentDoc(name);

return (
<ComponentPageShell
Expand All @@ -51,6 +53,7 @@ export default function ComponentPage() {
spec={spec}
prev={prev ? { slug: prev.slug, name: prev.name } : null}
next={next ? { slug: next.slug, name: next.name } : null}
docs={docs}
/>
);
}
635 changes: 398 additions & 237 deletions packages/ghost-ui/src/components/docs/component-page-shell.tsx

Large diffs are not rendered by default.

37 changes: 36 additions & 1 deletion packages/ghost-ui/src/components/docs/demo-loader.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<div className="flex w-full flex-col gap-4 py-8">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-48 w-full" />
</div>
);

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 (
<Suspense fallback={<LoadingSkeleton />}>
<LazyComponent />
</Suspense>
);
}

/* eslint-disable @typescript-eslint/no-explicit-any */
const wrap = (imp: Promise<any>, name: string) =>
imp.then((m: any) => ({ default: m[name] }));
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<div className="w-full max-w-xl">
<CodeBlock
code={snippet.code}
language={snippet.language}
showLineNumbers
>
<CodeBlockHeader>
<CodeBlockLanguageSelector value={lang} onValueChange={setLang}>
<CodeBlockLanguageSelectorTrigger>
<CodeBlockLanguageSelectorValue />
</CodeBlockLanguageSelectorTrigger>
<CodeBlockLanguageSelectorContent>
<CodeBlockLanguageSelectorItem value="typescript">
TypeScript
</CodeBlockLanguageSelectorItem>
<CodeBlockLanguageSelectorItem value="python">
Python
</CodeBlockLanguageSelectorItem>
<CodeBlockLanguageSelectorItem value="rust">
Rust
</CodeBlockLanguageSelectorItem>
</CodeBlockLanguageSelectorContent>
</CodeBlockLanguageSelector>
<CodeBlockActions>
<CodeBlockCopyButton />
</CodeBlockActions>
</CodeBlockHeader>
</CodeBlock>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="h-[400px] rounded-lg border">
<Conversation>
<ConversationContent>
{messages.map((msg) => (
<Message key={msg.id} from={msg.role}>
<MessageContent>
{msg.role === "assistant" ? (
<MessageResponse>{msg.text}</MessageResponse>
) : (
<p>{msg.text}</p>
)}
</MessageContent>
</Message>
))}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col gap-6 p-4">
<Message from="user">
<MessageContent>
<p>Explain quantum entanglement in simple terms.</p>
</MessageContent>
</Message>

<Message from="assistant">
<MessageContent>
<MessageResponse isAnimating>
{`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."`}
</MessageResponse>
<Shimmer className="text-sm text-muted-foreground" duration={1.5}>
Generating...
</Shimmer>
</MessageContent>
</Message>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col gap-6 p-4">
<Message from="user">
<MessageContent>
<p>How do I center a div?</p>
</MessageContent>
</Message>

<Message from="assistant">
<MessageContent>
<MessageResponse>
{`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\`\`\``}
</MessageResponse>
</MessageContent>
<MessageActions>
<MessageAction tooltip="Copy">
<CopyIcon className="size-3.5" />
</MessageAction>
<MessageAction tooltip="Regenerate">
<RefreshCwIcon className="size-3.5" />
</MessageAction>
<MessageAction tooltip="Good response">
<ThumbsUpIcon className="size-3.5" />
</MessageAction>
<MessageAction tooltip="Bad response">
<ThumbsDownIcon className="size-3.5" />
</MessageAction>
</MessageActions>
</Message>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
PromptInput,
PromptInputActionAddAttachments,
PromptInputFooter,
PromptInputSubmit,
PromptInputTextarea,
PromptInputTools,
} from "@/components/ai-elements/prompt-input";

export default function PromptInputWithAttachments() {
return (
<div className="w-full max-w-xl mx-auto p-4">
<PromptInput
accept="image/*,.pdf,.txt"
maxFiles={5}
maxFileSize={10 * 1024 * 1024}
onSubmit={(msg) => {
console.log("Submitted:", msg);
}}
>
<PromptInputTextarea placeholder="Describe your task or attach files..." />
<PromptInputFooter>
<PromptInputTools>
<PromptInputActionAddAttachments />
</PromptInputTools>
<PromptInputSubmit />
</PromptInputFooter>
</PromptInput>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {

export function CarouselDemo() {
return (
<div className="hidden w-full flex-col items-center gap-4 @4xl:flex">
<div className="flex w-full flex-col items-center gap-4">
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
Expand Down
Loading
Loading