This post was originally published on my blog
Sometimes you may want to emphasize that command is destructive or note additional information in your blog or docs like this:
But this is not included in standard Markdown or GitHub Flavored Markdown. In this post, I will show you how to support this feature and notation with remark
plugins.
I assume you've already set up a static site generator that supports MDX by unified pipeline.
Remark plugins
Support notation
We'd like to add this custom notation for admonition:
:::info
Laboris dolore aliquip laboris irure.
:::
Fortunately, we don't have to write any code as there is remark-directive that does exactly what we need.
yarn add remark-directive
And add it to your own unified
pipeline.
import remarkDirective from "remark-directive"
import remarkAdmonition from "./remarkAdmonitions" // We'll implement this next
const remarkPlugins = [remarkDirective, remarkAdmonition]
Convert to mdxJsxFlowElement
But remark-directive
doesn't do everything we need for displaying admonitions. To convert the block into an MDX component, define the following plugin:
import { visit } from "unist-util-visit"
export default function remarkAdmonition() {
return (tree) => {
visit(tree, (node) => {
if (
node.type === "textDirective" ||
node.type === "leafDirective" ||
node.type === "containerDirective"
) {
if (!["info", "warn", "danger"].includes(node.name)) return
// Store node.name before overwritten with "Alert".
const status = node.name
const data = node.data || (node.data = {})
const tagName = node.type === "textDirective" ? "span" : "div"
node.type = "mdxJsxFlowElement"
node.name = "Admonition"
node.attributes = [{ type: "mdxJsxAttribute", name: "status", value: status }]
}
})
}
}
Note that the node's name is
Admonition
. We'll have to add a custom MDX component that has the same name.
React component and styles
Then we need a React component that accepts the status
property. This value comes from any of :::info
, :::warn
or :::danger
.
import { HiInformationCircle, HiExclamationTriangle } from "react-icons/hi2"
import clsx from "clsx"
import React from "react"
import styles from "./styles.module.scss"
type SvgComponent = React.ComponentType<React.ComponentProps<"svg">>
type Status = "info" | "danger" | "warn"
type Props = {
status?: Status
title?: React.ReactNode
children?: React.ReactNode
className?: string
}
const statusIconMap: { [S in Status]: SvgComponent } = {
info: HiInformationCircle,
danger: HiExclamationTriangle,
warn: HiExclamationTriangle,
}
export default function Message({ status = "info", children, className }: Props) {
const Icon = statusIconMap[status]
const statusClass = styles[`--${status}`]
return (
<aside className={clsx(styles.container, statusClass, className)}>
<div>
<Icon className={clsx(styles.icon, statusClass)} />
</div>
<div className={styles.text}>
{children && <div className={styles.message}>{children}</div>}
</div>
</aside>
)
}
$infoColor: #3182ce;
$warnColor: #e6a700;
$dangerColor: #e53e3e;
.container {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.5rem;
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid #e2e8f0;
border-left-width: 4px;
background: white;
&.--info {
border-left-color: $infoColor;
}
&.--warn {
border-left-color: $warnColor;
}
&.--danger {
border-left-color: $dangerColor;
}
}
.text {
display: grid;
row-gap: 0.25rem;
}
.title {
display: flex;
align-items: center;
column-gap: 0.5rem;
font-weight: var(--camome-font-weight-bold);
font-size: var(--camome-font-size-lg);
&.--info {
color: $infoColor;
}
&.--warn {
color: $warnColor;
}
&.--danger {
color: $dangerColor;
}
}
.message {
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
}
.icon {
width: 1.75rem;
height: 1.75rem;
&.--info {
color: $infoColor;
}
&.--warn {
color: $warnColor;
}
&.--danger {
color: $dangerColor;
}
}
Pass it as a custom component
Now, register it as a custom component otherwise MDX complains that you've forgotten to import it!
import { MDXProvider } from "@mdx-js/react"
import Admonition from "@/components/Admonition"
const components = {
Admonition,
}
export default function Post(props) {
return (
<MDXProvider components={components}>
<main {...props} />
</MDXProvider>
)
}
Exact code could be different if you're using mdx-bundler, Contentlayer, or anything else. But there should be a similar API for adding components.
That's all
You can add another type like :::note
or anything, and style the component as you want.
Thank you for reading!
Top comments (0)