feat(site): extract api reference from components (#464)

This commit is contained in:
Darius Cepulis
2026-02-05 19:57:54 -06:00
committed by GitHub
parent 48364f17fd
commit 0991a899b2
80 changed files with 3425 additions and 1562 deletions
@@ -0,0 +1,88 @@
import { describe, expect, it } from 'vitest';
import { renderInlineMarkdown } from '../renderInlineMarkdown';
describe('renderInlineMarkdown', () => {
it('returns plain text for a simple sentence', () => {
expect(renderInlineMarkdown('Whether the button is disabled.')).toBe('Whether the button is disabled.');
});
it('unwraps a single paragraph', () => {
const result = renderInlineMarkdown('Hello **world**.');
expect(result).not.toMatch(/^<p/);
expect(result).toContain('<strong class="font-semibold">world</strong>');
});
it('preserves multiple paragraphs', () => {
const result = renderInlineMarkdown('First paragraph.\n\nSecond paragraph.');
expect(result).toContain('<p');
expect(result).toMatch(/First paragraph/);
expect(result).toMatch(/Second paragraph/);
});
it('renders inline code with correct classes', () => {
const result = renderInlineMarkdown('Use `foo` here.');
expect(result).toContain('<code');
expect(result).toContain('font-mono');
expect(result).toContain('text-code');
expect(result).toContain('foo');
});
it('renders strong text', () => {
const result = renderInlineMarkdown('**bold text**');
expect(result).toContain('<strong class="font-semibold">bold text</strong>');
});
it('renders emphasized text', () => {
const result = renderInlineMarkdown('*italic text*');
expect(result).toContain('<em class="font-medium">italic text</em>');
});
it('renders links with correct classes', () => {
const result = renderInlineMarkdown('[link](https://example.com)');
expect(result).toContain('href="https://example.com"');
expect(result).toContain('underline');
expect(result).toContain('intent:no-underline');
});
it('renders unordered lists', () => {
const result = renderInlineMarkdown('- item one\n- item two');
expect(result).toContain('<ul');
expect(result).toContain('list-disc');
expect(result).toContain('<li');
expect(result).toContain('item one');
expect(result).toContain('item two');
});
it('renders ordered lists', () => {
const result = renderInlineMarkdown('1. first\n2. second');
expect(result).toContain('<ol');
expect(result).toContain('list-decimal');
expect(result).toContain('first');
expect(result).toContain('second');
});
it('downgrades headings to paragraphs', () => {
const result = renderInlineMarkdown('# Heading');
expect(result).not.toContain('<h1');
expect(result).toContain('Heading');
});
it('suppresses horizontal rules', () => {
const result = renderInlineMarkdown('before\n\n---\n\nafter');
expect(result).not.toContain('<hr');
});
it('renders a paragraph followed by a list', () => {
const md = 'The volume level:\n\n- `0` — muted\n- `1` — max';
const result = renderInlineMarkdown(md);
expect(result).toContain('<p');
expect(result).toContain('<ul');
expect(result).toContain('<code');
});
it('renders fenced code blocks as inline code', () => {
const result = renderInlineMarkdown('```\nconst x = 1;\n```');
expect(result).toContain('<code');
expect(result).toContain('const x = 1;');
});
});
+112
View File
@@ -0,0 +1,112 @@
import { Marked, type MarkedExtension, type Tokens } from 'marked';
import { twMerge } from 'tailwind-merge';
import { shared } from '@/components/typography/styles';
const classes = {
p: 'mt-3 first:mt-0',
ul: twMerge(shared.ul, 'mt-3'),
ol: twMerge(shared.ol, 'mt-3'),
li: shared.li,
code: shared.code,
strong: shared.strong,
em: shared.em,
a: shared.a,
} as const;
const renderer: MarkedExtension['renderer'] = {
// --- Supported elements ---
paragraph({ tokens }) {
return `<p class="${classes.p}">${this.parser.parseInline(tokens)}</p>`;
},
list(token: Tokens.List) {
const tag = token.ordered ? 'ol' : 'ul';
const cls = token.ordered ? classes.ol : classes.ul;
const body = token.items.map((item) => this.listitem(item)).join('\n');
return `<${tag} class="${cls}">${body}</${tag}>`;
},
listitem(item: Tokens.ListItem) {
let body = this.parser.parse(item.tokens, !!item.loose);
if (!item.loose) {
body = body.replace(/^<p class="[^"]*">/, '').replace(/<\/p>$/, '');
}
return `<li class="${classes.li}">${body}</li>`;
},
code({ text }) {
return `<code class="${classes.code}">${text}</code>`;
},
codespan({ text }) {
return `<code class="${classes.code}">${text}</code>`;
},
strong({ tokens }) {
return `<strong class="${classes.strong}">${this.parser.parseInline(tokens)}</strong>`;
},
em({ tokens }) {
return `<em class="${classes.em}">${this.parser.parseInline(tokens)}</em>`;
},
link({ href, tokens }) {
return `<a href="${href}" class="${classes.a}">${this.parser.parseInline(tokens)}</a>`;
},
// --- Unsupported elements — downgrade or suppress ---
heading({ tokens }) {
return `<p class="${classes.p}">${this.parser.parseInline(tokens)}</p>`;
},
blockquote({ tokens }) {
return this.parser.parse(tokens);
},
hr() {
return '';
},
image({ href, text }) {
return text || href;
},
table() {
return '';
},
tablerow() {
return '';
},
tablecell() {
return '';
},
};
const marked = new Marked({ renderer });
/**
* Unwrap a single `<p>` wrapper so simple descriptions sit inline.
*
* If the output is a lone `<p class="…">…</p>` with no other block-level
* elements, strip the wrapper and return only the inner content.
*/
function unwrapSingleParagraph(html: string): string {
const trimmed = html.trim();
const match = trimmed.match(/^<p class="[^"]*">([\s\S]*)<\/p>$/);
if (match && !trimmed.includes('<p', 1)) {
return match[1]!;
}
return trimmed;
}
export function renderInlineMarkdown(markdown: string): string {
const raw = marked.parse(markdown);
if (typeof raw !== 'string') {
return markdown;
}
return unwrapSingleParagraph(raw);
}