Preview
import type { ComponentProps } from "react";
const MyComponent = (props: ComponentProps<"div">) => {
return <div {...props}>Hello, World!</div>;
};
export default MyComponent;Installation
shadcn/ui
shadcn/ui Command
pnpm dlx shadcn@latest add https://code-blocks.pheralb.dev/r/mdx-shiki.jsonManual
Before creating the MDX component, make sure you have the basic component structure:
react
Code Block
The main structure of the Code Block component with header and content areas.
- Install the following dependencies:
MDX Types
pnpm i @types/mdx -D- Create the Copy Button component:
react
Copy Button
A button component to copy content to the clipboard.
- Create a
react-to-textutility to get plain text frompreelements:
react-to-text
import {
isValidElement,
type ReactNode,
type JSXElementConstructor,
} from "react";
type ResolverMap = Map<
string | JSXElementConstructor<object>,
(props: object) => string
>;
const reactToText = (node: ReactNode, resolvers?: ResolverMap): string => {
if (node == null || typeof node === "boolean") return "";
if (typeof node === "string" || typeof node === "number") return String(node);
if (Array.isArray(node))
return node.map((n) => reactToText(n, resolvers)).join("");
if (isValidElement(node)) {
const resolver = resolvers?.get(
node.type as string | JSXElementConstructor<object>,
);
if (resolver) return resolver(node.props as object);
return reactToText(
(node.props as { children?: ReactNode }).children,
resolvers,
);
}
return "";
};
export { reactToText };Highlighter Setup
- Setup
data-title&data-languageproperties:
shiki
Add Properties
How to add custom properties to pre elements using a custom Shiki transformer.
- Setup Shiki highlighter:
shiki
Shiki Highlighter
The main highlighter utility to render syntax highlighted code.
- Setup Shiki Markdown integration:
shiki
Shiki Highlighter
#markdownmdx-integrationThe main highlighter utility to render syntax highlighted code.
- Create your
PreShikiComponentcomponent:
mdx-shiki
import type { ComponentProps } from "react";
import type { MDXComponents } from "mdx/types";
import { cn } from "@/utils/cn";
import { reactToText } from "@/utils/react-to-text";
import {
CodeBlock,
CodeBlockHeader,
CodeBlockContent,
CodeBlockIcon,
CodeBlockGroup,
} from "@/components/code-block/code-block";
import { CopyButton } from "@/components/code-block/copy-button";
interface PreProps extends ComponentProps<"pre"> {
["data-language"]: string;
["data-title"]?: string;
}
const PreShikiComponent: MDXComponents = {
pre: ({ children, ...props }: PreProps) => {
const content = reactToText(children);
const title = props["data-title"];
const language = props["data-language"];
return (
<CodeBlock className="group/code-block">
{title && (
<CodeBlockHeader>
<CodeBlockGroup>
<CodeBlockIcon language={language} />
<span>{title}</span>
</CodeBlockGroup>
<CopyButton content={content} />
</CodeBlockHeader>
)}
<CodeBlockContent className={cn(!title && "relative")}>
{!title && (
<CopyButton
content={content}
className="sticky top-3 right-3 z-50 float-right rounded-md text-neutral-950 opacity-0 transition-opacity group-hover/code-block:opacity-100 hover:opacity-70 dark:text-neutral-50"
/>
)}
<pre {...props}>{children}</pre>
</CodeBlockContent>
</CodeBlock>
);
},
};
export { PreShikiComponent };- Usage:
In your MDX Components object, add the new PreShikiComponent component:
import type { MDXComponents as MDXComponentsType } from "mdx/types";
import { PreShikiComponent } from "@/components/code-block/pre-shiki";
const MDXComponents: MDXComponentsType = {
...PreShikiComponent,
};