Preview
.tsx
import type { ComponentProps } from "react";
const MyComponent = (props: ComponentProps<"div">) => {
return <div {...props}>Hello, World!</div>;
};
export default MyComponent;Code Block MDX + Copy Button + Shiki Highlighter
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:
reactCode 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:
reactCopy Button
A button component to copy content to the clipboard.
- Create a
react-to-textutility to get plain text frompreelements:
.ts
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 your MDX highlighter:
- Setup
data-title&data-languageproperties:
shikiAdd Properties
How to add custom properties to pre elements using a custom Shiki transformer.
- Setup Shiki highlighter:
shikiShiki Highlighter
The main highlighter utility to render syntax highlighted code.
- Setup Shiki Markdown integration:
shikiShiki Highlighter#markdownmdx-integration
The main highlighter utility to render syntax highlighted code.
- Create your
PreShikiComponentcomponent:
.tsx
import type { ComponentProps } from "react";
import type { MDXComponents } from "mdx/types";
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>
<CodeBlockHeader>
<CodeBlockGroup>
<CodeBlockIcon language={language} />
<span>{title ?? `.${language}`}</span>
</CodeBlockGroup>
<CopyButton content={content} />
</CodeBlockHeader>
<CodeBlockContent>
<pre {...props}>{children}</pre>
</CodeBlockContent>
</CodeBlock>
);
},
};
export { PreShikiComponent };- Usage:
In your MDX Components object, add the new PreShikiComponent component:
.ts
import type { MDXComponents as MDXComponentsType } from "mdx/types";
import { PreShikiComponent } from "@/components/code-block/pre-shiki";
const MDXComponents: MDXComponentsType = {
...PreShikiComponent,
};