fix: resolve rendering issues with GFM alert boxes in <DynamicParameter /> (#22241)

Closes #22189

GFM alerts (e.g., `> [!IMPORTANT]`) in Markdown content failed to render
when the alert body contained inline formatting like `**bold**`,
`*italic*`, or `` `code` ``. The alert marker and subsequent text were
merged into a single string node by the parser, causing the type
detection to fail and fall back to a plain blockquote.

Additionally, multi-line alert content (`> line one\n> line two`) lost
its line breaks — all lines collapsed into one.

- Split the alert marker from trailing content in shared string nodes so
type detection works with inline formatting
- Preserve embedded newlines as `<br/>` elements to match GitHub's GFM
alert rendering
- Wrap plain-text children instead of splitting on `\n` to avoid
stripping newline information early

<img width="447" height="187" alt="image"
src="https://github.com/user-attachments/assets/d2fa3495-0b31-483c-97d8-12fed6819e24"
/>
This commit is contained in:
Jake Howell
2026-04-01 17:20:55 +11:00
committed by GitHub
parent 153a66b579
commit 2d03f7fd3d
2 changed files with 53 additions and 6 deletions
@@ -81,8 +81,8 @@ export const GFMAlerts: Story = {
> [!NOTE]
> Useful information that users should know, even when skimming content.
> [!TIP]
> Helpful advice for doing things better or more easily.
> [!TIP]
> Helpful advice for doing things better or more easily.
> [!IMPORTANT]
> Key information users need to know to achieve their goal.
@@ -95,3 +95,13 @@ export const GFMAlerts: Story = {
`,
},
};
export const GFMAlertWithInlineFormatting: Story = {
args: {
children: `
> [!IMPORTANT]
> Larger **instances** cost more. Choose based on your workload.
> Test line two
`,
},
};
+41 -4
View File
@@ -2,6 +2,7 @@ import type { Interpolation, Theme } from "@emotion/react";
import Link from "@mui/material/Link";
import isEqual from "lodash/isEqual";
import {
createElement,
type FC,
type HTMLProps,
isValidElement,
@@ -256,8 +257,9 @@ function parseChildrenAsAlertContent(
if (typeof parentChildren === "string") {
// Children will only be an array if the parsed text contains other
// content that can be turned into HTML. If there aren't any, you
// just get one big string
parentChildren = parentChildren.split("\n");
// just get one big string. Wrap it rather than splitting so that
// embedded newlines are preserved for line-break conversion later.
parentChildren = [parentChildren];
}
if (!Array.isArray(parentChildren)) {
return null;
@@ -304,7 +306,17 @@ function parseChildrenAsAlertContent(
return null;
}
const alertType = firstEl
// The alert marker (e.g., "[!IMPORTANT]") may share a string node
// with subsequent content when inline formatting follows on the
// next blockquote line. Split on the first newline so we only
// test the marker portion.
const firstNewline = firstEl.indexOf("\n");
const alertCandidate =
firstNewline === -1 ? firstEl : firstEl.substring(0, firstNewline);
const trailingContent =
firstNewline === -1 ? null : firstEl.substring(firstNewline + 1);
const alertType = alertCandidate
.trim()
.toLowerCase()
.replace("!", "")
@@ -314,15 +326,40 @@ function parseChildrenAsAlertContent(
return null;
}
if (trailingContent) {
remainingChildren.unshift(trailingContent);
}
const hasLeadingLinebreak =
isValidElement(remainingChildren[0]) && remainingChildren[0].type === "br";
if (hasLeadingLinebreak) {
remainingChildren.shift();
}
// GitHub's GFM alerts preserve line breaks within alert content,
// but the markdown parser treats them as soft wraps (spaces).
// Convert embedded newlines in text nodes to <br/> elements to
// match GitHub's rendering behavior.
const withLineBreaks: ReactNode[] = remainingChildren.flatMap((child, i) => {
if (typeof child !== "string" || !child.includes("\n")) {
return [child];
}
const parts = child.split("\n");
const result: ReactNode[] = [];
for (let j = 0; j < parts.length; j++) {
if (j > 0) {
result.push(createElement("br", { key: `alert-br-${i}-${j}` }));
}
if (parts[j]) {
result.push(parts[j]);
}
}
return result;
});
return {
type: alertType,
children: remainingChildren,
children: withLineBreaks,
};
}