feat: Add markdown components
This commit is contained in:
26 files changed
+551
-258
No files matched your search
@@ -1,265 +1,163 @@
|
||||
import { Markdown } from './Markdown';
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import React from 'react';
|
||||
|
||||
import { Markdown, MARKDOWN_COMPONENTS } from './Markdown';
|
||||
|
||||
/**
|
||||
* Metadata for the Markdown component stories.
|
||||
*/
|
||||
const meta: Meta<typeof Markdown> = {
|
||||
title: 'components/Markdown',
|
||||
title: 'Components/Markdown',
|
||||
component: Markdown,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
/**
|
||||
* Type definition for Markdown component stories.
|
||||
*/
|
||||
type Story = StoryObj<typeof Markdown>;
|
||||
|
||||
/**
|
||||
* Default story showcasing various HTML elements to verify typography.
|
||||
*/
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<p>
|
||||
This is a <strong>Markdown</strong> component that uses Tailwind CSS Typography (prose) to
|
||||
render content with consistent styling.
|
||||
</p>
|
||||
<p>
|
||||
It is designed to be used with rendered HTML from markdown processors, but works with any
|
||||
HTML content that needs standard document styling.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Showcasing all heading levels.
|
||||
*/
|
||||
export const Typography: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<h1>Heading Level 1</h1>
|
||||
<p>Paragraph text following an H1.</p>
|
||||
<h2>Heading Level 2</h2>
|
||||
<p>Paragraph text following an H2.</p>
|
||||
<h3>Heading Level 3</h3>
|
||||
<p>Paragraph text following an H3.</p>
|
||||
<h4>Heading Level 4</h4>
|
||||
<p>Paragraph text following an H4.</p>
|
||||
<h5>Heading Level 5</h5>
|
||||
<p>Paragraph text following an H5.</p>
|
||||
<h6>Heading Level 6</h6>
|
||||
<p>Paragraph text following an H6.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Showcasing ordered and unordered lists.
|
||||
*/
|
||||
export const Lists: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<h3>Unordered List</h3>
|
||||
<ul>
|
||||
<li>First item</li>
|
||||
<li>
|
||||
Second item
|
||||
<ul>
|
||||
<li>Nested item A</li>
|
||||
<li>Nested item B</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Third item</li>
|
||||
</ul>
|
||||
|
||||
<h3>Ordered List</h3>
|
||||
<ol>
|
||||
<li>Step one</li>
|
||||
<li>
|
||||
Step two
|
||||
<ol>
|
||||
<li>Sub-step 2.1</li>
|
||||
<li>Sub-step 2.2</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>Step three</li>
|
||||
</ol>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Showcasing blockquotes.
|
||||
*/
|
||||
export const Quotes: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<p>Before the quote.</p>
|
||||
<blockquote>
|
||||
"Design is not just what it looks like and feels like. Design is how it works."
|
||||
<footer>— Steve Jobs</footer>
|
||||
</blockquote>
|
||||
<p>After the quote.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Showcasing code and pre blocks.
|
||||
*/
|
||||
export const Code: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<p>
|
||||
You can use <code>inline code</code> for small snippets.
|
||||
</p>
|
||||
<pre>
|
||||
<code>{`function hello() {
|
||||
console.log("Hello, world!");
|
||||
}`}</code>
|
||||
</pre>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Showcasing tables.
|
||||
*/
|
||||
export const Tables: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Description</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Typography</td>
|
||||
<td>Garamond and Lato fonts</td>
|
||||
<td>✅ Done</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dark Mode</td>
|
||||
<td>Automatic inversion</td>
|
||||
<td>✅ Done</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Responsive</td>
|
||||
<td>Fluid layouts</td>
|
||||
<td>✅ Done</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The "Kitchen Sink" containing everything.
|
||||
* A "Kitchen Sink" demonstration of all Markdown components working together
|
||||
* as they would in a real article.
|
||||
*/
|
||||
export const KitchenSink: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<h1>The Art of Markdown Rendering</h1>
|
||||
<p className="lead">
|
||||
A lead paragraph that stands out and sets the tone for the rest of the document.
|
||||
</p>
|
||||
<p>
|
||||
Markdown is a lightweight markup language with plain-text-formatting syntax. Its design
|
||||
allows it to be converted to many output formats, but the name originally referred to the
|
||||
tool for converting it to HTML.
|
||||
</p>
|
||||
render: () => (
|
||||
<div className="max-w-[800px] mx-auto">
|
||||
<Markdown>
|
||||
<MARKDOWN_COMPONENTS.h1>The Art of Markdown Integration</MARKDOWN_COMPONENTS.h1>
|
||||
<MARKDOWN_COMPONENTS.p>
|
||||
This article demonstrates how our design system translates standard Markdown tags into
|
||||
beautiful, accessible UI primitives. From typography to complex data tables.
|
||||
</MARKDOWN_COMPONENTS.p>
|
||||
|
||||
<h2>Key Features</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Easy to read</strong> - The source text looks like the final output.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Easy to write</strong> - Minimal syntax overhead.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Versatile</strong> - Supports many elements like <em>italics</em>,{' '}
|
||||
<del>strikethrough</del>, and <code>code</code>.
|
||||
</li>
|
||||
</ul>
|
||||
<MARKDOWN_COMPONENTS.hr />
|
||||
|
||||
<h3>Example Code Block</h3>
|
||||
<pre>
|
||||
<code>{`import { Markdown } from '@ermnvldmr/ui';
|
||||
<MARKDOWN_COMPONENTS.h2>Typography & Vertical Rhythm</MARKDOWN_COMPONENTS.h2>
|
||||
<MARKDOWN_COMPONENTS.p>
|
||||
Our typography system is built on a strict vertical rhythm. Headers use the{' '}
|
||||
<MARKDOWN_COMPONENTS.code>Header</MARKDOWN_COMPONENTS.code> component, while paragraphs
|
||||
leverage the <MARKDOWN_COMPONENTS.code>Paragraph</MARKDOWN_COMPONENTS.code> component with
|
||||
pre-configured gutters.
|
||||
</MARKDOWN_COMPONENTS.p>
|
||||
|
||||
export const MyPage = () => (
|
||||
<Markdown>
|
||||
<h1>Welcome</h1>
|
||||
<p>This is styled automatically.</p>
|
||||
</Markdown>
|
||||
);`}</code>
|
||||
</pre>
|
||||
<MARKDOWN_COMPONENTS.blockquote>
|
||||
"Design is not just what it looks like and feels like. Design is how it works."
|
||||
<br />
|
||||
<MARKDOWN_COMPONENTS.a href="https://en.wikipedia.org/wiki/Steve_Jobs">— Steve Jobs</MARKDOWN_COMPONENTS.a>
|
||||
</MARKDOWN_COMPONENTS.blockquote>
|
||||
|
||||
<blockquote>
|
||||
"Simplicity is the ultimate sophistication."
|
||||
<br />— Leonardo da Vinci
|
||||
</blockquote>
|
||||
<MARKDOWN_COMPONENTS.h3>Nesting and Lists</MARKDOWN_COMPONENTS.h3>
|
||||
<MARKDOWN_COMPONENTS.p>
|
||||
Lists are handled by our <MARKDOWN_COMPONENTS.code>List</MARKDOWN_COMPONENTS.code> system,
|
||||
supporting both ordered and unordered variants:
|
||||
</MARKDOWN_COMPONENTS.p>
|
||||
|
||||
<hr />
|
||||
<MARKDOWN_COMPONENTS.ul>
|
||||
<MARKDOWN_COMPONENTS.li>Standardization across all articles</MARKDOWN_COMPONENTS.li>
|
||||
<MARKDOWN_COMPONENTS.li>
|
||||
Support for nested structures:
|
||||
<MARKDOWN_COMPONENTS.ul className="mt-2">
|
||||
<MARKDOWN_COMPONENTS.li>Nested unordered item</MARKDOWN_COMPONENTS.li>
|
||||
<MARKDOWN_COMPONENTS.li>Another nested item</MARKDOWN_COMPONENTS.li>
|
||||
</MARKDOWN_COMPONENTS.ul>
|
||||
</MARKDOWN_COMPONENTS.li>
|
||||
<MARKDOWN_COMPONENTS.li>Consistent spacing and markers</MARKDOWN_COMPONENTS.li>
|
||||
</MARKDOWN_COMPONENTS.ul>
|
||||
|
||||
<h2>Data Representation</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Language</th>
|
||||
<th>Difficulty</th>
|
||||
<th>Usage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>TypeScript</td>
|
||||
<td>Moderate</td>
|
||||
<td>Frontend/Backend</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rust</td>
|
||||
<td>High</td>
|
||||
<td>Systems</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python</td>
|
||||
<td>Low</td>
|
||||
<td>Data Science</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<MARKDOWN_COMPONENTS.h2>Code & Technical Content</MARKDOWN_COMPONENTS.h2>
|
||||
<MARKDOWN_COMPONENTS.p>
|
||||
Technical articles often require code blocks. We use{' '}
|
||||
<MARKDOWN_COMPONENTS.code>CodeBlock</MARKDOWN_COMPONENTS.code> for multi-line snippets:
|
||||
</MARKDOWN_COMPONENTS.p>
|
||||
|
||||
<h3>Media Elements</h3>
|
||||
<p>Images should be rounded and have a nice shadow.</p>
|
||||
<img
|
||||
alt="Coding"
|
||||
src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=1200&q=80"
|
||||
<MARKDOWN_COMPONENTS.pre label="markdown-example.tsx">
|
||||
<MARKDOWN_COMPONENTS.code>
|
||||
{`export const MarkdownP = memo(function MarkdownP(props: ParagraphProps) {
|
||||
return <Paragraph gutterBottom {...props} />;
|
||||
});`}
|
||||
</MARKDOWN_COMPONENTS.code>
|
||||
</MARKDOWN_COMPONENTS.pre>
|
||||
|
||||
<MARKDOWN_COMPONENTS.h2>Data Representation</MARKDOWN_COMPONENTS.h2>
|
||||
<MARKDOWN_COMPONENTS.p>
|
||||
Tables are notoriously difficult to style in Markdown. Our components ensure they stay
|
||||
within the container and maintain readable density.
|
||||
</MARKDOWN_COMPONENTS.p>
|
||||
|
||||
<MARKDOWN_COMPONENTS.table>
|
||||
<MARKDOWN_COMPONENTS.thead>
|
||||
<MARKDOWN_COMPONENTS.tr>
|
||||
<MARKDOWN_COMPONENTS.th>Component</MARKDOWN_COMPONENTS.th>
|
||||
<MARKDOWN_COMPONENTS.th>Tag</MARKDOWN_COMPONENTS.th>
|
||||
<MARKDOWN_COMPONENTS.th>Status</MARKDOWN_COMPONENTS.th>
|
||||
</MARKDOWN_COMPONENTS.tr>
|
||||
</MARKDOWN_COMPONENTS.thead>
|
||||
<MARKDOWN_COMPONENTS.tbody>
|
||||
<MARKDOWN_COMPONENTS.tr>
|
||||
<MARKDOWN_COMPONENTS.td>MarkdownP</MARKDOWN_COMPONENTS.td>
|
||||
<MARKDOWN_COMPONENTS.td>
|
||||
<MARKDOWN_COMPONENTS.code>p</MARKDOWN_COMPONENTS.code>
|
||||
</MARKDOWN_COMPONENTS.td>
|
||||
<MARKDOWN_COMPONENTS.td>Verified</MARKDOWN_COMPONENTS.td>
|
||||
</MARKDOWN_COMPONENTS.tr>
|
||||
<MARKDOWN_COMPONENTS.tr>
|
||||
<MARKDOWN_COMPONENTS.td>MarkdownTable</MARKDOWN_COMPONENTS.td>
|
||||
<MARKDOWN_COMPONENTS.td>
|
||||
<MARKDOWN_COMPONENTS.code>table</MARKDOWN_COMPONENTS.code>
|
||||
</MARKDOWN_COMPONENTS.td>
|
||||
<MARKDOWN_COMPONENTS.td>Verified</MARKDOWN_COMPONENTS.td>
|
||||
</MARKDOWN_COMPONENTS.tr>
|
||||
</MARKDOWN_COMPONENTS.tbody>
|
||||
</MARKDOWN_COMPONENTS.table>
|
||||
|
||||
<MARKDOWN_COMPONENTS.h2>Visual Assets</MARKDOWN_COMPONENTS.h2>
|
||||
<MARKDOWN_COMPONENTS.img
|
||||
alt="Atmospheric landscape"
|
||||
ratio="16/9"
|
||||
src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&q=80&w=1600"
|
||||
width="100%"
|
||||
/>
|
||||
</Markdown>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
<p>Conclusion: We've built a robust system for rendering content.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
/**
|
||||
* Specifically showcases the header hierarchy from H1 to H6.
|
||||
*/
|
||||
export const HeaderHierarchy: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<MARKDOWN_COMPONENTS.h1>Header Level 1</MARKDOWN_COMPONENTS.h1>
|
||||
<MARKDOWN_COMPONENTS.h2>Header Level 2</MARKDOWN_COMPONENTS.h2>
|
||||
<MARKDOWN_COMPONENTS.h3>Header Level 3</MARKDOWN_COMPONENTS.h3>
|
||||
<MARKDOWN_COMPONENTS.h4>Header Level 4</MARKDOWN_COMPONENTS.h4>
|
||||
<MARKDOWN_COMPONENTS.h5>Header Level 5</MARKDOWN_COMPONENTS.h5>
|
||||
<MARKDOWN_COMPONENTS.h6>Header Level 6</MARKDOWN_COMPONENTS.h6>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* Demonstrates various list types and their spacing.
|
||||
*/
|
||||
export const ListStyles: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div>
|
||||
<MARKDOWN_COMPONENTS.h4 className="mb-4">Unordered List</MARKDOWN_COMPONENTS.h4>
|
||||
<MARKDOWN_COMPONENTS.ul>
|
||||
<MARKDOWN_COMPONENTS.li>First item</MARKDOWN_COMPONENTS.li>
|
||||
<MARKDOWN_COMPONENTS.li>Second item</MARKDOWN_COMPONENTS.li>
|
||||
<MARKDOWN_COMPONENTS.li>Third item</MARKDOWN_COMPONENTS.li>
|
||||
</MARKDOWN_COMPONENTS.ul>
|
||||
</div>
|
||||
<div>
|
||||
<MARKDOWN_COMPONENTS.h4 className="mb-4">Ordered List</MARKDOWN_COMPONENTS.h4>
|
||||
<MARKDOWN_COMPONENTS.ol>
|
||||
<MARKDOWN_COMPONENTS.li>Step one: Preparation</MARKDOWN_COMPONENTS.li>
|
||||
<MARKDOWN_COMPONENTS.li>Step two: Implementation</MARKDOWN_COMPONENTS.li>
|
||||
<MARKDOWN_COMPONENTS.li>Step three: Verification</MARKDOWN_COMPONENTS.li>
|
||||
</MARKDOWN_COMPONENTS.ol>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { Markdown, MARKDOWN_COMPONENTS } from './Markdown';
|
||||
|
||||
describe('Markdown Components', () => {
|
||||
it('renders various markdown components correctly', () => {
|
||||
render(
|
||||
<Markdown>
|
||||
<Markdown.H1>Heading 1</Markdown.H1>
|
||||
<Markdown.P>This is a paragraph.</Markdown.P>
|
||||
<Markdown.A href="https://example.com">Link</Markdown.A>
|
||||
<Markdown.UL>
|
||||
<Markdown.LI>Item 1</Markdown.LI>
|
||||
</Markdown.UL>
|
||||
<Markdown.Table>
|
||||
<Markdown.THead>
|
||||
<Markdown.TR>
|
||||
<Markdown.TH>Header</Markdown.TH>
|
||||
</Markdown.TR>
|
||||
</Markdown.THead>
|
||||
<Markdown.TBody>
|
||||
<Markdown.TR>
|
||||
<Markdown.TD>Cell</Markdown.TD>
|
||||
</Markdown.TR>
|
||||
</Markdown.TBody>
|
||||
</Markdown.Table>
|
||||
</Markdown>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Heading 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('This is a paragraph.')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link')).toHaveAttribute('href', 'https://example.com');
|
||||
expect(screen.getByRole('list')).toBeInTheDocument();
|
||||
expect(screen.getByRole('table')).toBeInTheDocument();
|
||||
expect(screen.getByRole('cell')).toHaveTextContent('Cell');
|
||||
});
|
||||
|
||||
it('MARKDOWN_COMPONENTS map contains all necessary tags', () => {
|
||||
expect(MARKDOWN_COMPONENTS.p).toBeDefined();
|
||||
expect(MARKDOWN_COMPONENTS.h1).toBeDefined();
|
||||
expect(MARKDOWN_COMPONENTS.table).toBeDefined();
|
||||
expect(MARKDOWN_COMPONENTS.pre).toBeDefined();
|
||||
expect(MARKDOWN_COMPONENTS.code).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,29 @@
|
||||
import { cn } from '@ermnvldmr/stl';
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { MarkdownA } from './MarkdownA/MarkdownA';
|
||||
import { MarkdownBlockquote } from './MarkdownBlockquote/MarkdownBlockquote';
|
||||
import { MarkdownCode } from './MarkdownCode/MarkdownCode';
|
||||
import { MarkdownH1 } from './MarkdownH1/MarkdownH1';
|
||||
import { MarkdownH2 } from './MarkdownH2/MarkdownH2';
|
||||
import { MarkdownH3 } from './MarkdownH3/MarkdownH3';
|
||||
import { MarkdownH4 } from './MarkdownH4/MarkdownH4';
|
||||
import { MarkdownH5 } from './MarkdownH5/MarkdownH5';
|
||||
import { MarkdownH6 } from './MarkdownH6/MarkdownH6';
|
||||
import { MarkdownHR } from './MarkdownHR/MarkdownHR';
|
||||
import { MarkdownImg } from './MarkdownImg/MarkdownImg';
|
||||
import { MarkdownLI } from './MarkdownLI/MarkdownLI';
|
||||
import { MarkdownOL } from './MarkdownOL/MarkdownOL';
|
||||
import { MarkdownP } from './MarkdownP/MarkdownP';
|
||||
import { MarkdownPre } from './MarkdownPre/MarkdownPre';
|
||||
import { MarkdownTable } from './MarkdownTable/MarkdownTable';
|
||||
import { MarkdownTBody } from './MarkdownTBody/MarkdownTBody';
|
||||
import { MarkdownTD } from './MarkdownTD/MarkdownTD';
|
||||
import { MarkdownTH } from './MarkdownTH/MarkdownTH';
|
||||
import { MarkdownTHead } from './MarkdownTHead/MarkdownTHead';
|
||||
import { MarkdownTR } from './MarkdownTR/MarkdownTR';
|
||||
import { MarkdownUL } from './MarkdownUL/MarkdownUL';
|
||||
|
||||
/**
|
||||
* Props for the Markdown component.
|
||||
*/
|
||||
/**
|
||||
* Props for the Markdown component.
|
||||
*/
|
||||
@@ -14,22 +34,75 @@ export interface MarkdownProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of HTML tags to Markdown components for use with MDX or Astro Content.
|
||||
*/
|
||||
export const MARKDOWN_COMPONENTS = {
|
||||
p: MarkdownP,
|
||||
a: MarkdownA,
|
||||
h1: MarkdownH1,
|
||||
h2: MarkdownH2,
|
||||
h3: MarkdownH3,
|
||||
h4: MarkdownH4,
|
||||
h5: MarkdownH5,
|
||||
h6: MarkdownH6,
|
||||
blockquote: MarkdownBlockquote,
|
||||
ul: MarkdownUL,
|
||||
ol: MarkdownOL,
|
||||
li: MarkdownLI,
|
||||
table: MarkdownTable,
|
||||
thead: MarkdownTHead,
|
||||
tbody: MarkdownTBody,
|
||||
tr: MarkdownTR,
|
||||
th: MarkdownTH,
|
||||
td: MarkdownTD,
|
||||
code: MarkdownCode,
|
||||
pre: MarkdownPre,
|
||||
hr: MarkdownHR,
|
||||
img: MarkdownImg,
|
||||
} as const;
|
||||
|
||||
const MarkdownComponent = memo(function Markdown({ children, className }: MarkdownProps) {
|
||||
return <div className={cn('flex flex-col gap-4', className)}>{children}</div>;
|
||||
});
|
||||
|
||||
/**
|
||||
* Markdown component provides a styled container for rendered markdown content.
|
||||
* It uses the Tailwind CSS Typography plugin (prose) with custom project styles.
|
||||
*
|
||||
* @param props - The component props.
|
||||
* @param props.children
|
||||
* @param props.className
|
||||
* @returns A React element.
|
||||
* It also exports all necessary sub-components for mapping HTML tags to design system components.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Markdown>
|
||||
* <h1>Title</h1>
|
||||
* <p>Content goes here.</p>
|
||||
* <Markdown.H1>Title</Markdown.H1>
|
||||
* <Markdown.P>Content goes here.</Markdown.P>
|
||||
* </Markdown>
|
||||
*
|
||||
* // Or with Astro:
|
||||
* <Content components={MARKDOWN_COMPONENTS} />
|
||||
* ```
|
||||
*/
|
||||
export const Markdown: React.FC<MarkdownProps> = ({ children, className }) => {
|
||||
return <div className={cn(className)}>{children}</div>;
|
||||
};
|
||||
export const Markdown = Object.assign(MarkdownComponent, {
|
||||
P: MarkdownP,
|
||||
A: MarkdownA,
|
||||
H1: MarkdownH1,
|
||||
H2: MarkdownH2,
|
||||
H3: MarkdownH3,
|
||||
H4: MarkdownH4,
|
||||
H5: MarkdownH5,
|
||||
H6: MarkdownH6,
|
||||
Blockquote: MarkdownBlockquote,
|
||||
UL: MarkdownUL,
|
||||
OL: MarkdownOL,
|
||||
LI: MarkdownLI,
|
||||
Table: MarkdownTable,
|
||||
THead: MarkdownTHead,
|
||||
TBody: MarkdownTBody,
|
||||
TR: MarkdownTR,
|
||||
TH: MarkdownTH,
|
||||
TD: MarkdownTD,
|
||||
Code: MarkdownCode,
|
||||
Pre: MarkdownPre,
|
||||
HR: MarkdownHR,
|
||||
Img: MarkdownImg,
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Link } from '../../Link/Link';
|
||||
|
||||
import type { LinkProps } from '../../Link/Link';
|
||||
|
||||
/**
|
||||
* A Markdown link component that wraps the design system Link.
|
||||
*/
|
||||
export const MarkdownA = memo(function MarkdownA(props: LinkProps) {
|
||||
return <Link {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Blockquote } from '../../Blockquote/Blockquote';
|
||||
|
||||
import type { BlockquoteProps } from '../../Blockquote/Blockquote';
|
||||
|
||||
/**
|
||||
* A Markdown blockquote component that wraps the design system Blockquote.
|
||||
*
|
||||
* Automatically applies gutterBottom and indentation.
|
||||
*/
|
||||
export const MarkdownBlockquote = memo(function MarkdownBlockquote(props: BlockquoteProps) {
|
||||
return <Blockquote gutterBottom indent {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Code } from '../../Code/Code';
|
||||
|
||||
import type { CodeProps } from '../../Code/Code';
|
||||
|
||||
/**
|
||||
* A Markdown code component for inline snippets.
|
||||
*/
|
||||
export const MarkdownCode = memo(function MarkdownCode(props: CodeProps) {
|
||||
return <Code {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Header } from '../../Header/Header';
|
||||
|
||||
import type { HeaderProps } from '../../Header/Header';
|
||||
|
||||
/**
|
||||
* A Markdown H1 component that wraps the design system Header.
|
||||
*/
|
||||
export const MarkdownH1 = memo(function MarkdownH1(props: Omit<HeaderProps, 'level'>) {
|
||||
return <Header level={1} {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Header } from '../../Header/Header';
|
||||
|
||||
import type { HeaderProps } from '../../Header/Header';
|
||||
|
||||
/**
|
||||
* A Markdown H2 component that wraps the design system Header.
|
||||
*/
|
||||
export const MarkdownH2 = memo(function MarkdownH2(props: Omit<HeaderProps, 'level'>) {
|
||||
return <Header level={2} {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Header } from '../../Header/Header';
|
||||
|
||||
import type { HeaderProps } from '../../Header/Header';
|
||||
|
||||
/**
|
||||
* A Markdown H3 component that wraps the design system Header.
|
||||
*/
|
||||
export const MarkdownH3 = memo(function MarkdownH3(props: Omit<HeaderProps, 'level'>) {
|
||||
return <Header level={3} {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Header } from '../../Header/Header';
|
||||
|
||||
import type { HeaderProps } from '../../Header/Header';
|
||||
|
||||
/**
|
||||
* A Markdown H4 component that wraps the design system Header.
|
||||
*/
|
||||
export const MarkdownH4 = memo(function MarkdownH4(props: Omit<HeaderProps, 'level'>) {
|
||||
return <Header level={4} {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Header } from '../../Header/Header';
|
||||
|
||||
import type { HeaderProps } from '../../Header/Header';
|
||||
|
||||
/**
|
||||
* A Markdown H5 component that wraps the design system Header.
|
||||
*/
|
||||
export const MarkdownH5 = memo(function MarkdownH5(props: Omit<HeaderProps, 'level'>) {
|
||||
return <Header level={5} {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Header } from '../../Header/Header';
|
||||
|
||||
import type { HeaderProps } from '../../Header/Header';
|
||||
|
||||
/**
|
||||
* A Markdown H6 component that wraps the design system Header.
|
||||
*/
|
||||
export const MarkdownH6 = memo(function MarkdownH6(props: Omit<HeaderProps, 'level'>) {
|
||||
return <Header level={6} {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Separator } from '../../Separator/Separator';
|
||||
|
||||
import type { SeparatorProps } from '../../Separator/Separator';
|
||||
|
||||
/**
|
||||
* A Markdown horizontal rule component that wraps the design system Separator.
|
||||
*/
|
||||
export const MarkdownHR = memo(function MarkdownHR(props: SeparatorProps) {
|
||||
return <Separator className="my-8" {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Image } from '../../Image/Image';
|
||||
|
||||
import type { ImageProps } from '../../Image/Image';
|
||||
|
||||
/**
|
||||
* A Markdown image component that wraps the design system Image.
|
||||
*
|
||||
* Note: Markdown usually only provides src and alt. Default constraints
|
||||
* might be needed if they are not provided by the processor.
|
||||
*/
|
||||
export const MarkdownImg = memo(function MarkdownImg(props: ImageProps) {
|
||||
// Default values for common Markdown images to prevent CLS
|
||||
// if specific dimensions aren't provided.
|
||||
return <Image rounded="md" shadow="sm" {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { List } from '../../List/List';
|
||||
|
||||
import type { ListItemProps } from '../../List/List';
|
||||
|
||||
/**
|
||||
* A Markdown list item component.
|
||||
*/
|
||||
export const MarkdownLI = memo(function MarkdownLI(props: ListItemProps) {
|
||||
return <List.Item {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { List } from '../../List/List';
|
||||
|
||||
import type { ListProps } from '../../List/List';
|
||||
|
||||
/**
|
||||
* A Markdown ordered list component.
|
||||
*/
|
||||
export const MarkdownOL = memo(function MarkdownOL(props: ListProps) {
|
||||
return <List spacing="m" variant="ordered" {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Paragraph } from '../../Paragraph/Paragraph';
|
||||
|
||||
import type { ParagraphProps } from '../../Paragraph/Paragraph';
|
||||
|
||||
/**
|
||||
* A Markdown paragraph component that wraps the design system Paragraph.
|
||||
*
|
||||
* Automatically applies gutterBottom for consistent vertical rhythm.
|
||||
*/
|
||||
export const MarkdownP = memo(function MarkdownP(props: ParagraphProps) {
|
||||
return <Paragraph gutterBottom {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { CodeBlock } from '../../Code/CodeBlock/CodeBlock';
|
||||
|
||||
import type { CodeBlockProps } from '../../Code/CodeBlock/CodeBlock';
|
||||
|
||||
/**
|
||||
* A Markdown pre component for code blocks.
|
||||
*
|
||||
* Wraps content in the design system CodeBlock.
|
||||
*/
|
||||
export const MarkdownPre = memo(function MarkdownPre(props: CodeBlockProps) {
|
||||
return <CodeBlock {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Table } from '../../Table/Table';
|
||||
|
||||
/**
|
||||
* A Markdown table body (tbody) component.
|
||||
*/
|
||||
export const MarkdownTBody = memo(function MarkdownTBody(props: React.HTMLAttributes<HTMLTableSectionElement>) {
|
||||
return <Table.Body {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Table } from '../../Table/Table';
|
||||
|
||||
/**
|
||||
* A Markdown table cell (td) component.
|
||||
*
|
||||
* Wraps content in Table.Content for consistent styling and alignment.
|
||||
*/
|
||||
export const MarkdownTD = memo(function MarkdownTD({ children, align, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) {
|
||||
const effectiveAlign = (align === 'left' || align === 'center' || align === 'right') ? align : undefined;
|
||||
|
||||
return (
|
||||
<Table.Cell {...props} align={effectiveAlign}>
|
||||
<Table.Content>{children}</Table.Content>
|
||||
</Table.Cell>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Table } from '../../Table/Table';
|
||||
|
||||
/**
|
||||
* A Markdown table header cell (th) component.
|
||||
*/
|
||||
export const MarkdownTH = memo(function MarkdownTH({ align, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>) {
|
||||
const effectiveAlign = (align === 'left' || align === 'center' || align === 'right') ? align : undefined;
|
||||
|
||||
return <Table.Head {...props} align={effectiveAlign} />;
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Table } from '../../Table/Table';
|
||||
|
||||
/**
|
||||
* A Markdown table head (thead) component.
|
||||
*/
|
||||
export const MarkdownTHead = memo(function MarkdownTHead(props: React.HTMLAttributes<HTMLTableSectionElement>) {
|
||||
return <Table.Header {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Table } from '../../Table/Table';
|
||||
|
||||
/**
|
||||
* A Markdown table row (tr) component.
|
||||
*/
|
||||
export const MarkdownTR = memo(function MarkdownTR(props: React.HTMLAttributes<HTMLTableRowElement>) {
|
||||
return <Table.Row {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Table } from '../../Table/Table';
|
||||
|
||||
import type { TableProps } from '../../Table/Table';
|
||||
|
||||
/**
|
||||
* A Markdown table root component.
|
||||
*/
|
||||
export const MarkdownTable = memo(function MarkdownTable(props: TableProps) {
|
||||
return <Table density="m" variant="surface" {...props} />;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { List } from '../../List/List';
|
||||
|
||||
import type { ListProps } from '../../List/List';
|
||||
|
||||
/**
|
||||
* A Markdown unordered list component.
|
||||
*/
|
||||
export const MarkdownUL = memo(function MarkdownUL(props: ListProps) {
|
||||
return <List spacing="m" variant="unordered" {...props} />;
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { MARKDOWN_COMPONENTS } from '@ermnvldmr/ui';
|
||||
import { getCollection, render } from 'astro:content';
|
||||
|
||||
import { ArticleLayout } from '#layouts/ArticleLayout/ArticleLayout';
|
||||
@@ -30,6 +31,6 @@ const { title, description, createdDate, updatedDate, tags } = entry.data;
|
||||
updatedDate={updatedDate}
|
||||
tags={tags}
|
||||
>
|
||||
<Content />
|
||||
<Content components={MARKDOWN_COMPONENTS} />
|
||||
</ArticleLayout>
|
||||
</IndexLayout>
|
||||
Reference in new issue
Block a user