a programming education platform
www.hypercommit.com
education
1import type { MDXComponents } from "mdx/types"
2import * as React from "react"
3import { CodeBlock } from "@workspace/ui/components/code-block"
4import { slugifyHeading } from "@/lib/headings"
5
6function HeadingAnchor({ id }: { id: string }) {
7 return (
8 <a
9 href={`#${id}`}
10 aria-label="Link to this section"
11 className="ml-2 font-normal text-foreground/30 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100"
12 >
13 #
14 </a>
15 )
16}
17
18function extractFigcaptionText(node: React.ReactNode): string | undefined {
19 let title: string | undefined
20 React.Children.forEach(node, (child) => {
21 if (!React.isValidElement(child)) return
22 const props = child.props as Record<string, unknown> & {
23 children?: React.ReactNode
24 }
25 if (props["data-rehype-pretty-code-title"] !== undefined) {
26 if (typeof props.children === "string") title = props.children
27 }
28 })
29 return title
30}
31
32function extractPre(node: React.ReactNode): React.ReactNode {
33 let pre: React.ReactNode = null
34 React.Children.forEach(node, (child) => {
35 if (!React.isValidElement(child)) return
36 const props = child.props as Record<string, unknown>
37 if (props["data-rehype-pretty-code-title"] === undefined) {
38 pre = child
39 }
40 })
41 return pre
42}
43
44export function useMDXComponents(components: MDXComponents): MDXComponents {
45 return {
46 h1: ({ children }) => (
47 <h1 className="text-lg font-semibold sm:text-xl">{children}</h1>
48 ),
49 h2: ({ children }) => {
50 const id = slugifyHeading(children)
51 return (
52 <h2 id={id} className="group mt-6 scroll-mt-6 text-base font-medium">
53 {children}
54 <HeadingAnchor id={id} />
55 </h2>
56 )
57 },
58 h3: ({ children }) => {
59 const id = slugifyHeading(children)
60 return (
61 <h3 id={id} className="group mt-4 scroll-mt-6 text-base font-medium">
62 {children}
63 <HeadingAnchor id={id} />
64 </h3>
65 )
66 },
67 p: ({ children }) => (
68 <p className="text-base leading-7 text-foreground/80">{children}</p>
69 ),
70 a: ({ children, href }) => (
71 <a
72 href={href}
73 className="text-primary underline-offset-4 hover:underline"
74 >
75 {children}
76 </a>
77 ),
78 ul: ({ children }) => (
79 <ul className="ms-5 list-disc space-y-1 text-base leading-7 text-foreground/80">
80 {children}
81 </ul>
82 ),
83 ol: ({ children }) => (
84 <ol className="ms-5 list-decimal space-y-1 text-base leading-7 text-foreground/80">
85 {children}
86 </ol>
87 ),
88 code: ({ children, ...props }) => (
89 <code
90 className="rounded border bg-muted px-1 py-0.5 font-mono text-sm text-foreground"
91 {...props}
92 >
93 {children}
94 </code>
95 ),
96 table: ({ children }) => (
97 <div className="my-4 overflow-hidden rounded-lg border border-border">
98 <table className="w-full border-collapse text-sm">{children}</table>
99 </div>
100 ),
101 thead: ({ children }) => (
102 <thead className="bg-muted [&_tr]:border-b [&_tr]:border-border">
103 {children}
104 </thead>
105 ),
106 tbody: ({ children }) => (
107 <tbody className="[&_tr]:border-border [&_tr:not(:last-child)]:border-b">
108 {children}
109 </tbody>
110 ),
111 tr: ({ children }) => <tr>{children}</tr>,
112 th: ({ children }) => (
113 <th className="px-4 py-2 text-left text-base font-medium tracking-wide text-muted-foreground uppercase not-last:border-r not-last:border-border">
114 {children}
115 </th>
116 ),
117 td: ({ children }) => (
118 <td className="px-4 py-2 text-sm leading-6 text-foreground/80 not-last:border-r not-last:border-border">
119 {children}
120 </td>
121 ),
122 figure: (props) => {
123 const dataAttr = (props as Record<string, unknown>)[
124 "data-rehype-pretty-code-figure"
125 ]
126 if (dataAttr === undefined) {
127 return <figure {...props} />
128 }
129 const filename = extractFigcaptionText(props.children)
130 const pre = extractPre(props.children)
131 return <CodeBlock filename={filename}>{pre}</CodeBlock>
132 },
133 ...components,
134 }
135}