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
162 changes: 162 additions & 0 deletions apps/website/app/components/faq.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
'use client';

import { CodeBlock } from '@/components/code-block';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';

const faqs = [
{
id: 'streamdown-difference',
question: 'What makes Streamdown different from react-markdown?',
answer:
"Streamdown is specifically designed for AI-powered streaming applications. It includes built-in support for incomplete markdown parsing, which means it can render markdown even while it's being generated by AI models. It also includes security features like URL prefix restrictions and better performance optimizations for streaming contexts.",
},
{
id: 'custom-components',
question: 'Can I use custom components with Streamdown?',
answer:
'Yes! Streamdown fully supports custom components through the `components` prop, just like react-markdown. You can override any markdown element with your own React components to customize the rendering.',
},
{
id: 'incomplete-parsing',
question: 'How does the incomplete markdown parsing work?',
answer:
'When `parseIncompleteMarkdown` is enabled (default), Streamdown automatically detects and fixes common issues in incomplete markdown like unclosed code blocks, incomplete lists, and partial formatting. This ensures smooth rendering even as markdown is being streamed from AI models.',
},
{
id: 'plugin-compatibility',
question: 'Is Streamdown compatible with all react-markdown plugins?',
answer:
'Streamdown supports both remark and rehype plugins, making it compatible with most react-markdown plugins. It includes popular plugins like remarkGfm, remarkMath, and rehypeKatex by default, and you can add your own through the `remarkPlugins` and `rehypePlugins` props.',
},
{
id: 'shiki-warning',
question: (
<span>
Why do I get a{' '}
<code className="rounded-md bg-foreground/5 px-2 py-1 font-mono text-sm tracking-tight">
Package shiki can't be external
</code>{' '}
warning?
</span>
),
answer: (
<div>
<p>
This warning occurs when Next.js tries to treat Shiki as an external
package. To fix this, you need to install Shiki explicitly with{' '}
<code className="rounded-md bg-foreground/5 px-2 py-1 font-mono text-sm tracking-tight">
npm install shiki
</code>{' '}
and add it to your{' '}
<code className="rounded-md bg-foreground/5 px-2 py-1 font-mono text-sm tracking-tight">
transpilePackages
</code>{' '}
array in your{' '}
<code className="rounded-md bg-foreground/5 px-2 py-1 font-mono text-sm tracking-tight">
next.config.ts
</code>
:
</p>
<div className="my-2">
<CodeBlock
className="my-4 rounded-md border p-4"
code={`{
// ... other config
transpilePackages: ["shiki"],
}`}
language="tsx"
/>
</div>
<p>This ensures Shiki is properly bundled with your application.</p>
</div>
),
},
{
id: 'tailwind-config',
question: 'How do I configure Tailwind CSS to work with Streamdown?',
answer: (
<>
<div>
<p>
For <span className="font-medium">Tailwind v4</span>, add a{' '}
<code className="rounded-md bg-foreground/5 px-2 py-1 font-mono text-sm tracking-tight">
@source
</code>{' '}
directive to your{' '}
<code className="rounded-md bg-foreground/5 px-2 py-1 font-mono text-sm tracking-tight">
globals.css
</code>{' '}
file with the path to Streamdown's distribution files:
</p>
<div className="my-2">
<CodeBlock
className="my-4 rounded-md border p-4"
code={`@source "../node_modules/streamdown/dist/index.js";`}
language="css"
/>
</div>
</div>
<div>
<p>
For <span className="font-medium">Tailwind v3</span>, add Streamdown
to your{' '}
<code className="rounded-md bg-foreground/5 px-2 py-1 font-mono text-sm tracking-tight">
content
</code>{' '}
array in{' '}
<code className="rounded-md bg-foreground/5 px-2 py-1 font-mono text-sm tracking-tight">
tailwind.config.js
</code>
:
</p>
<div className="my-2">
<CodeBlock
className="my-4 rounded-md border p-4"
code={`content: [
// ... your other content paths
"./node_modules/streamdown/dist/**/*.js",
]`}
language="js"
/>
</div>
</div>
<p>
Adjust the paths based on your project structure. This ensures
Tailwind scans Streamdown's files for any utility classes used in the
component.
</p>
</>
),
},
];

export const FAQ = () => (
<div className="divide-y sm:grid sm:grid-cols-3 sm:divide-x sm:divide-y-0">
<div className="space-y-2 p-4 sm:p-8">
<h2 className="font-semibold text-2xl tracking-tight">FAQ</h2>
<p className="text-muted-foreground">
Common questions about Streamdown and how it works with AI-powered
streaming applications.
</p>
</div>
<div className="sm:col-span-2">
<Accordion className="w-full" collapsible type="single">
{faqs.map((faq) => (
<AccordionItem key={faq.id} value={faq.id}>
<AccordionTrigger className="p-4 text-left text-base sm:p-8">
{faq.question}
</AccordionTrigger>
<AccordionContent className="p-4 pt-0 text-base leading-relaxed sm:p-8 sm:pt-0">
{faq.answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</div>
);
22 changes: 22 additions & 0 deletions apps/website/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,26 @@
background-image: linear-gradient(45deg, var(--border) 12.5%, transparent 12.5%, transparent 50%, var(--border) 50%, var(--border) 62.5%, transparent 62.5%, transparent 100%);
background-size: .25rem .25rem;
}
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
.animate-accordion-down {
animation: accordion-down 0.2s ease-out;
}
.animate-accordion-up {
animation: accordion-up 0.2s ease-out;
}
}
2 changes: 2 additions & 0 deletions apps/website/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import { CodeBlocks } from './components/code-blocks';
import { CallToAction } from './components/cta';
import { FAQ } from './components/faq';
import { Footer } from './components/footer';
import { GitHubFlavoredMarkdown } from './components/gfm';
import { HardenedMarkdown } from './components/hardened';
Expand Down Expand Up @@ -33,6 +34,7 @@ const Home = () => (
<HardenedMarkdown />
<Props />
<CallToAction />
<FAQ />
<Footer />
</div>
);
Expand Down
66 changes: 66 additions & 0 deletions apps/website/components/ui/accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client"

import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon } from "lucide-react"

import { cn } from "@/lib/utils"

function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}

function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("border-b last:border-b-0", className)}
{...props}
/>
)
}

function AccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
)
}

function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
)
}

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
1 change: 1 addition & 0 deletions apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.13",
"@vercel/analytics": "^1.5.0",
Expand Down
62 changes: 62 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.