Skip to content
Draft
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
4 changes: 1 addition & 3 deletions app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,7 @@ export default async function Page(props: {
</Suspense>
</div>
<div className="w-full flex-1 *:data-[slot=alert]:first:mt-0">
<Suspense fallback={<Skeleton className="size-full" />}>
<MDX components={mdxComponents} />
</Suspense>
<MDX components={mdxComponents} />
</div>
</div>
<div
Expand Down
8 changes: 6 additions & 2 deletions components/component-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import Link from "next/link";
import { type ReactNode, Suspense } from "react";

import { OpenInV0Button } from "@/components/open-in-v0-button";
import { Skeleton } from "@/components/ui/8bit/skeleton";
import { Button } from "@/components/ui/button";

interface ComponentPreviewProps {
children: React.ReactNode;
children: ReactNode;
fullPageHref?: string;
name: string;
title: string;
Expand All @@ -31,7 +33,9 @@ export default function ComponentPreview({
</div>
</div>
<div className="relative flex min-h-[400px] items-center justify-center p-10">
{children}
<Suspense fallback={<Skeleton className="min-h-[400px] w-full" />}>
{children}
</Suspense>
</div>
</div>
);
Expand Down
46 changes: 46 additions & 0 deletions mdx-components.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { CSSProperties } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";

vi.mock("@/components/code-snippet", () => ({
default: () => <div data-testid="client-code-snippet" />,
}));

import { mdxComponents } from "@/mdx-components";

const installCommand = "pnpm dlx shadcn@latest add @8bitcn/button";
type ShikiStyle = CSSProperties & { "--shiki-light": string };

const commandTokenStyle: ShikiStyle = { "--shiki-light": "#6f42c1" };
const argumentTokenStyle: ShikiStyle = { "--shiki-light": "#032f62" };

describe("mdxComponents.code", () => {
it("includes pre-highlighted fenced code in the server-rendered markup", () => {
const Code = mdxComponents.code;
const markup = renderToStaticMarkup(
<Code>
<span className="line">
<span style={commandTokenStyle}>pnpm</span>
<span style={argumentTokenStyle}>
{" dlx shadcn@latest add @8bitcn/button"}
</span>
</span>
</Code>
);
const renderedText = new DOMParser().parseFromString(markup, "text/html")
.body.textContent;

expect(renderedText).toContain(installCommand);
expect(markup).toContain('class="line"');
expect(markup).toContain("--shiki-light:#6f42c1");
expect(markup).not.toContain("client-code-snippet");
});

it("keeps inline code styling", () => {
const Code = mdxComponents.code;
const markup = renderToStaticMarkup(<Code>components.json</Code>);

expect(markup).toContain("components.json");
expect(markup).toContain("bg-muted");
});
});
69 changes: 13 additions & 56 deletions mdx-components.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { icons } from "@tabler/icons-react";
import { CodeBlock, Pre } from "fumadocs-ui/components/codeblock";

import Image from "next/image";
import Link from "next/link";
import {
type ComponentProps,
type HTMLAttributes,
isValidElement,
type ReactNode,
} from "react";
import type { ComponentProps, HTMLAttributes } from "react";
import { Kbd } from "@/components/ui/8bit/kbd";
import {
Accordion,
Expand All @@ -21,8 +17,6 @@ import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";

import CodeSnippet from "./components/code-snippet";

export const mdxComponents = {
h1: ({ className, ...props }: ComponentProps<"h1">) => (
<h1
Expand Down Expand Up @@ -155,15 +149,13 @@ export const mdxComponents = {
/>
),
pre: ({ className, children, ...props }: ComponentProps<"pre">) => (
<pre
className={cn(
"no-scrollbar min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-slot=tabs]]:p-0 has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0",
className
)}
<CodeBlock
{...props}
className={cn("my-4 rounded-none", className)}
viewportProps={{ className: "no-scrollbar" }}
>
{children}
</pre>
<Pre>{children}</Pre>
</CodeBlock>
),
figure: ({ className, ...props }: ComponentProps<"figure">) => (
<figure className={cn(className)} {...props} />
Expand Down Expand Up @@ -191,24 +183,7 @@ export const mdxComponents = {
</figcaption>
);
},
code: ({
className,
__raw__,
__src__,
__npm__,
__yarn__,
__pnpm__,
__bun__,
children,
...props
}: ComponentProps<"code"> & {
__raw__?: string;
__src__?: string;
__npm__?: string;
__yarn__?: string;
__pnpm__?: string;
__bun__?: string;
}) => {
code: ({ className, children, ...props }: ComponentProps<"code">) => {
// Inline Code.
if (typeof children === "string") {
return (
Expand All @@ -224,29 +199,11 @@ export const mdxComponents = {
);
}

// Extract text content from React children if __raw__ is not available
const getTextContent = (node: ReactNode): string => {
if (typeof node === "string") {
return node;
}
if (typeof node === "number") {
return String(node);
}
if (Array.isArray(node)) {
return node.map(getTextContent).join("");
}
if (isValidElement(node)) {
const nodeProps = node.props as { children?: ReactNode };
if (nodeProps.children) {
return getTextContent(nodeProps.children);
}
}
return "";
};

const codeContent = __raw__ || getTextContent(children);

return <CodeSnippet>{codeContent}</CodeSnippet>;
return (
<code className={className} {...props}>
{children}
</code>
);
},
Step: ({ className, ...props }: ComponentProps<"h3">) => (
<h3
Expand Down