Code Block MDX with Sugar-High

Create a Code Block component for MDX using Sugar-High for syntax highlighting.

ReactMDXSugar-HighComponents

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-sugar-high.json

Manual

Before creating the MDX component, make sure you have the basic component structure:

Code Block

The main structure of the Code Block component with header and content areas.

  1. Install the following dependencies:
MDX Types
pnpm i @types/mdx -D
  1. Create the Copy Button component:

Copy Button

A button component to copy content to the clipboard.

  1. Create a react-to-text utility to get plain text from pre elements:
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

  1. Setup Sugar-High highlighter:

Sugar-High Highlighter

The main highlighter utility to render syntax highlighted code.

  1. Create your PreSugarHighComponent component:
mdx-sugar-high
import type { ComponentProps } from "react";
import type { MDXComponents } from "mdx/types";

import { highlight } from "@/utils/sugar-high/highlight";
import { reactToText } from "@/utils/react-to-text";

import {
  CodeBlock,
  CodeBlockContent,
} from "@/components/code-block/code-block";
import { CopyButton } from "@/components/code-block/copy-button";

type PreProps = ComponentProps<"pre">;

const PreSugarHighComponent: MDXComponents = {
  pre: ({ children }: PreProps) => {
    const content = reactToText(children);
    const codeHTML = highlight({
      code: content,
    });
    return (
      <CodeBlock className="group/code-block">
        <CodeBlockContent className="relative">
          <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 className="sh-pre">
            <code dangerouslySetInnerHTML={{ __html: codeHTML }} />
          </pre>
        </CodeBlockContent>
      </CodeBlock>
    );
  },
};

export { PreSugarHighComponent };
  1. Usage:

In your MDX Components object, add the new PreSugarHighComponent component:

import type { MDXComponents as MDXComponentsType } from "mdx/types";

import { PreSugarHighComponent } from "@/components/code-block/pre-sugar-high";

const MDXComponents: MDXComponentsType = {
  ...PreSugarHighComponent,
};