Skip to content

Commit 7c0dcf4

Browse files
natemoo-reJonasBacursor[bot]
authored
fix(stories): unwrap extra <p> tags in mdx jsx (#96351)
Follow-up to (and revert of) #96343. Rather than shifting the burden of wrapping all JSX-in-MDX content in expressions to authors, this PR fixes the issue by adding a new `remark` plugin that unwraps problematic `<p>` tags. Problematic situations where MDX introduces `<p>` tags are: - Paragraph contains only `mdxJsxTextElement` nodes and whitespace-only `text` nodes. - MDX JSX element contains a single child which is a paragraph. --------- Co-authored-by: Jonas <jonas.badalic@sentry.io> Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
1 parent 6021f68 commit 7c0dcf4

File tree

3 files changed

+97
-48
lines changed

3 files changed

+97
-48
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type {Node, Literal, Parent} from 'hast';
2+
3+
import {visit} from 'unist-util-visit';
4+
5+
/**
6+
* Remark plugin that unwraps paragraphs with only JSX elements and paragraphs inside MDX components
7+
*
8+
* - Find paragraphs that only contain MDX JSX text and/or whitespace, replace with children
9+
* - Find paragraphs that are the only children of MDX JSX elements, replace with children
10+
*
11+
*/
12+
export function remarkUnwrapMdxParagraphs() {
13+
return function (tree: Node) {
14+
// unwrap paragraphs with only JSX text and whitespace
15+
visit(tree, (node, index, parent: Parent) => {
16+
if (isParagraph(node)) {
17+
const hasOnlyJsxAndWhitespace = node.children.every(child => {
18+
switch (true) {
19+
case child.type === 'mdxJsxTextElement':
20+
return true;
21+
case isText(child):
22+
// Allow only whitespace text nodes
23+
return child.value.trim() === '';
24+
default:
25+
return false;
26+
}
27+
});
28+
if (hasOnlyJsxAndWhitespace && parent && index !== undefined) {
29+
// replace paragraph with its children
30+
parent.children.splice(index, 1, ...node.children);
31+
// return new index
32+
return index + node.children.length - 1;
33+
}
34+
}
35+
return;
36+
});
37+
// unwrap paragraphs that are only children of MDX JSX elements
38+
visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], node => {
39+
if (
40+
isMdxJsxNode(node) &&
41+
node.children.length === 1 &&
42+
isParagraph(node.children[0])
43+
) {
44+
node.children = node.children[0].children;
45+
return node.children.length - 1;
46+
}
47+
return;
48+
});
49+
};
50+
}
51+
52+
function isParagraph(node?: Node): node is Parent {
53+
return node?.type === 'paragraph';
54+
}
55+
function isText(node: Node): node is Literal {
56+
return node.type === 'text';
57+
}
58+
function isMdxJsxNode(node: Node): node is Parent {
59+
return node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement';
60+
}

rspack.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {TsCheckerRspackPlugin} from 'ts-checker-rspack-plugin';
2424

2525
// @ts-expect-error: ts(5097) importing `.ts` extension is required for resolution, but not enabled until `allowImportingTsExtensions` is added to tsconfig
2626
import LastBuiltPlugin from './build-utils/last-built-plugin.ts';
27+
// @ts-expect-error: ts(5097) importing `.ts` extension is required for resolution, but not enabled until `allowImportingTsExtensions` is added to tsconfig
28+
import {remarkUnwrapMdxParagraphs} from './build-utils/remark-unwrap-mdx-paragraphs.ts';
2729
import packageJson from './package.json' with {type: 'json'};
2830

2931
const {env} = process;
@@ -296,6 +298,7 @@ const appConfig: Configuration = {
296298
loader: '@mdx-js/loader',
297299
options: {
298300
remarkPlugins: [
301+
remarkUnwrapMdxParagraphs,
299302
remarkFrontmatter,
300303
remarkMdxFrontmatter,
301304
remarkGfm,

static/app/components/core/text/index.mdx

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ resources:
1212
WCAG 1.4.12: https://www.w3.org/TR/WCAG22/#text-spacing
1313
---
1414

15+
import {Flex} from 'sentry/components/core/layout';
1516
import {Heading, Text} from 'sentry/components/core/text';
1617
import * as Storybook from 'sentry/stories';
1718

@@ -177,39 +178,32 @@ Control line height with the `density` prop: `compressed` (1.0), default (1.2),
177178
<Storybook.Demo>
178179
<Storybook.SideBySide vertical>
179180
<div style={{width: '300px', marginBottom: '16px'}}>
180-
<Text bold as="p">
181-
{`Compressed density.`}
181+
<Heading as="h4">Compressed density</Heading>
182+
<Text as="p" density="compressed">
183+
Compressed density text. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
184+
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
182185
</Text>
183-
<Text
184-
density="compressed"
185-
as="p"
186-
>{`Compressed density text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`}</Text>
187186
</div>
188187
<div style={{width: '300px', marginBottom: '16px'}}>
189-
<Text bold as="p">
190-
{`Default density.`}
188+
<Heading as="h4">Default density</Heading>
189+
<Text as="p">
190+
Default density text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
191+
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
191192
</Text>
192-
<Text as="p">{`Default density text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`}</Text>
193193
</div>
194194
<div style={{width: '300px'}}>
195-
<Text bold as="p">
196-
{`Comfortable density.`}
195+
<Heading as="h4">Comfortable density</Heading>
196+
<Text as="p" density="comfortable">
197+
Comfortable density text. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
198+
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
197199
</Text>
198-
<Text
199-
density="comfortable"
200-
as="p"
201-
>{`Comfortable density text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`}</Text>
202200
</div>
203201
</Storybook.SideBySide>
204202
</Storybook.Demo>
205203
```jsx
206-
<Text density="compressed" as="p">
207-
Compressed density text...
208-
</Text>
209-
<Text as="p">Default density text...</Text>
210-
<Text density="comfortable" as="p">
211-
Comfortable density text...
212-
</Text>
204+
<Text density="compressed">Compressed density text...</Text>
205+
<Text>Default density text...</Text>
206+
<Text density="comfortable">Comfortable density text...</Text>
213207
```
214208

215209
### Ellipsis Overflow
@@ -233,20 +227,18 @@ Use `monospace` for fixed-width text.
233227

234228
<Storybook.Demo>
235229
<Storybook.SideBySide vertical>
236-
<div
237-
style={{marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '16px'}}
238-
>
230+
<Flex align="center" gap="xl" style={{marginBottom: '16px'}}>
239231
<Text>1234567890</Text>
240232
<Text size="sm" variant="muted">
241-
{`Regular`}
233+
Regular
242234
</Text>
243-
</div>
244-
<div style={{display: 'flex', alignItems: 'center', gap: '16px'}}>
235+
</Flex>
236+
<Flex align="center" gap="xl">
245237
<Text monospace>1234567890</Text>
246238
<Text size="sm" variant="muted">
247-
{`Monospace`}
239+
Monospace
248240
</Text>
249-
</div>
241+
</Flex>
250242
</Storybook.SideBySide>
251243
</Storybook.Demo>
252244
```jsx
@@ -260,36 +252,30 @@ Use `tabular` for consistent number alignment and `fraction` for diagonal fracti
260252

261253
<Storybook.Demo>
262254
<Storybook.SideBySide vertical>
263-
<div
264-
style={{marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '16px'}}
265-
>
255+
<Flex align="center" gap="xl" style={{marginBottom: '16px'}}>
266256
<Text>1234567890</Text>
267257
<Text size="sm" variant="muted">
268-
{`Regular numbers`}
258+
Regular numbers
269259
</Text>
270-
</div>
271-
<div
272-
style={{marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '16px'}}
273-
>
260+
</Flex>
261+
<Flex align="center" gap="xl" style={{marginBottom: '16px'}}>
274262
<Text tabular>1234567890</Text>
275263
<Text size="sm" variant="muted">
276-
{`Tabular numbers`}
264+
Tabular numbers
277265
</Text>
278-
</div>
279-
<div
280-
style={{marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '16px'}}
281-
>
266+
</Flex>
267+
<Flex align="center" gap="xl" style={{marginBottom: '16px'}}>
282268
<Text>1/2 3/4 5/8</Text>
283269
<Text size="sm" variant="muted">
284-
{`Regular fractions`}
270+
Regular fractions
285271
</Text>
286-
</div>
287-
<div style={{display: 'flex', alignItems: 'center', gap: '16px'}}>
272+
</Flex>
273+
<Flex align="center" gap="xl">
288274
<Text fraction>1/2 3/4 5/8</Text>
289275
<Text size="sm" variant="muted">
290-
{`Diagonal fractions`}
276+
Diagonal fractions
291277
</Text>
292-
</div>
278+
</Flex>
293279
</Storybook.SideBySide>
294280
</Storybook.Demo>
295281
```jsx

0 commit comments

Comments
 (0)