Tutorial Intro Content

Introduction section for tutorial pages with overview and prerequisites.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/tutorial-intro-content.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

import ReactMarkdown from "react-markdown"; import { cn } from "../../lib/utils"; export type TutorialIntroContentProps = { className?: string; content: string; title: string; }; // Server-side markdown components (no 'use client') const serverMarkdownComponents = { a: ({ children, href, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement> & { children?: React.ReactNode; }) => ( <a className="text-primary underline underline-offset-4 hover:text-primary/80 font-medium" href={href} {...props} > {children} </a> ), blockquote: ({ children, ...props }: React.BlockquoteHTMLAttributes<HTMLQuoteElement> & { children?: React.ReactNode; }) => ( <blockquote className="border-l border-primary pl-4 italic text-muted-foreground my-6 py-2 text-sm" {...props} > {children} </blockquote> ), code: ({ children, className: codeClassName, ...props }: React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }) => { const isBlock = codeClassName?.includes("language-"); if (isBlock) { return ( <code className={cn("font-mono text-sm", codeClassName)} {...props}> {children} </code> ); } return ( <code className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono" {...props} > {children} </code> ); }, h1: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement> & { children?: React.ReactNode; }) => ( <h1 className="text-2xl font-semibold mt-8 mb-4" {...props}> {children} </h1> ), h2: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement> & { children?: React.ReactNode; }) => ( <h2 className="text-xl font-semibold mt-6 mb-3" {...props}> {children} </h2> ), h3: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement> & { children?: React.ReactNode; }) => ( <h3 className="text-lg font-semibold mt-4 mb-2" {...props}> {children} </h3> ), hr: (props: React.HTMLAttributes<HTMLHRElement>) => ( <hr className="my-8 border-border" {...props} /> ), li: ({ children, ...props }: React.LiHTMLAttributes<HTMLLIElement> & { children?: React.ReactNode; }) => ( <li className="mb-2 leading-relaxed text-muted-foreground text-sm pl-2" {...props} > {children} </li> ), ol: ({ children, ...props }: React.OlHTMLAttributes<HTMLOListElement> & { children?: React.ReactNode; }) => ( <ol className="list-decimal list-outside mb-6 space-y-2 ml-6 text-muted-foreground text-sm" {...props} > {children} </ol> ), p: ({ children, ...props }: React.HTMLAttributes<HTMLParagraphElement> & { children?: React.ReactNode; }) => ( <p className="mb-4 leading-relaxed text-muted-foreground text-sm" {...props} > {children} </p> ), pre: ({ children, ...props }: React.HTMLAttributes<HTMLPreElement> & { children?: React.ReactNode }) => ( <pre className="bg-zinc-950 dark:bg-zinc-900 text-zinc-100 p-4 rounded-lg overflow-x-auto my-6 border border-zinc-800 shadow-lg font-mono text-sm" {...props} > {children} </pre> ), strong: ({ children, ...props }: React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }) => ( <strong className="font-semibold text-foreground" {...props}> {children} </strong> ), ul: ({ children, ...props }: React.HTMLAttributes<HTMLUListElement> & { children?: React.ReactNode; }) => ( <ul className="list-disc list-outside mb-6 space-y-2 ml-6 text-muted-foreground text-sm" {...props} > {children} </ul> ), }; // Strip MDX component tags for server rendering (they render on client) function stripMDXComponents(content: string): string { let cleaned = content.replaceAll(/<[A-Z][A-Za-z]*[^>]*\/>/g, ""); cleaned = cleaned.replaceAll( /<[A-Z][A-Za-z]*[^>]*>[\S\s]*?<\/[A-Z][A-Za-z]*>/g, "", ); return cleaned; } export function TutorialIntroContent({ className, content, title, }: TutorialIntroContentProps): React.ReactNode { const markdownContent = stripMDXComponents(content); return ( <section className={cn("py-6", className)}> <h2 className="text-2xl md:text-3xl font-semibold mb-6">{title}</h2> <div className="max-w-none [&_h2:first-of-type]:hidden"> <ReactMarkdown components={serverMarkdownComponents}> {markdownContent} </ReactMarkdown> </div> </section> ); }

Dependencies

  • @vllnt/ui@^0.2.1