feat(infra): migrate to rsbuild

This commit is contained in:
deytenit committed 2026-02-12 23:39:20 +03:00
1 parent 97c6347f2e
commit 4628c043ce
95 files changed
+5582 -11282

No files matched your search

@@ -19,7 +19,7 @@ describe('components/Button', () => {
});
it('calls onPress when clicked in button mode', () => {
const onPress = jest.fn();
const onPress = vi.fn();
render(<Button onPress={onPress}>Click me</Button>);
const button = screen.getByRole('button');
fireEvent.click(button);
@@ -27,7 +27,7 @@ describe('components/Button', () => {
});
it('does not call onPress when disabled', () => {
const onPress = jest.fn();
const onPress = vi.fn();
render(
<Button isDisabled onPress={onPress}>
Disabled
@@ -5,7 +5,7 @@ import { Code } from './Code';
import { CodeBlock } from './CodeBlock/CodeBlock';
// Mock clipboard API
const mockWriteText = jest.fn().mockImplementation(() => Promise.resolve());
const mockWriteText = vi.fn().mockImplementation(() => Promise.resolve());
Object.assign(navigator, {
clipboard: {
writeText: mockWriteText,
+24 -15
View File
@@ -73,6 +73,15 @@ export const Code = memo(function Code({
if (context?.isInCodeBlock) {
const { showLineNumbers } = context;
let content = children;
if (typeof children === 'string') {
content = children.trimEnd().split('\n').map((line, i) => (
// eslint-disable-next-line react/no-array-index-key
<span key={i} className="block min-h-[1.5em]">{line || '\n'}</span>
));
}
return (
<code
ref={codeRef}
@@ -80,25 +89,25 @@ export const Code = memo(function Code({
'block min-w-full font-mono whitespace-pre',
showLineNumbers && [
'[counter-reset:line]',
'[&>*]:relative [&>*]:block [&>*]:pl-10',
'[&>*]:[counter-increment:line]',
'[&>*]:before:content-[counter(line)]',
'[&>*]:before:absolute [&>*]:before:left-0',
'[&>*]:before:inline-block',
'[&>*]:before:w-8',
'[&>*]:before:text-right',
'[&>*]:before:text-[var(--rb-muted-text)]/50',
'[&>*]:before:select-none',
'[&>*]:before:border-r',
'[&>*]:before:border-[var(--rb-outline)]/10',
'[&>*]:before:pr-3',
'[&>*]:before:font-mono [&>*]:before:text-[10px]',
'[&>span]:relative [&>span]:pl-10',
'[&>span]:[counter-increment:line]',
'[&>span]:before:content-[counter(line)]',
'[&>span]:before:absolute [&>span]:before:left-0 [&>span]:before:top-[4px]',
'[&>span]:before:inline-block',
'[&>span]:before:w-8',
'[&>span]:before:text-right',
'[&>span]:before:text-[var(--rb-muted-text)]/50',
'[&>span]:before:select-none',
'[&>span]:before:border-r',
'[&>span]:before:border-[var(--rb-outline)]/10',
'[&>span]:before:pr-3',
'[&>span]:before:font-mono [&>span]:before:text-[10px]',
],
className
)}
data-testid={testId}
>
{children}
{content}
</code>
);
}
@@ -124,4 +133,4 @@ export const Code = memo(function Code({
{children}
</code>
);
});
});
@@ -7,7 +7,7 @@ import { render, screen } from '../../test-utils';
describe('components/Container', () => {
it('handles onPress event', async () => {
const user = userEvent.setup();
const handlePress = jest.fn();
const handlePress = vi.fn();
render(<Container onPress={handlePress}>Content</Container>);
const container = screen.getByText('Content');
@@ -115,6 +115,8 @@ export const Container = memo(function Container({
isDisabled: !onPress,
});
const interactionProps = onPress ? pressProps : {};
const bgClasses: Record<ContainerBackground, string> = {
base: 'bg-[var(--rb-container-base)] text-[var(--rb-container-text)]',
primary: 'bg-[var(--rb-primary-base)] text-[var(--rb-primary-text)]',
@@ -180,7 +182,7 @@ export const Container = memo(function Container({
return (
<Component
{...pressProps}
{...interactionProps}
ref={ref}
className={cn(
'overflow-hidden',
+10 -2
View File
@@ -25,10 +25,18 @@ describe('components/Link', () => {
});
it('respects explicit isExternal prop', () => {
const { rerender } = render(<Link isExternal href="/internal">Force External</Link>);
const { rerender } = render(
<Link isExternal href="/internal">
Force External
</Link>
);
expect(screen.getByRole('link')).toHaveAttribute('target', '_blank');
rerender(<Link href="https://external.com" isExternal={false}>Force Internal</Link>);
rerender(
<Link href="https://external.com" isExternal={false}>
Force Internal
</Link>
);
expect(screen.getByRole('link')).not.toHaveAttribute('target');
});
+23 -6
View File
@@ -6,6 +6,7 @@ import { Text } from '../Text/Text';
import type { ClassNameProps } from '@ermnvldmr/stl';
import type { AriaLinkOptions } from 'react-aria';
import type { TextColor, TextSize, TextType } from '../Text/Text';
/**
* Props for the Link component.
@@ -17,6 +18,12 @@ export interface LinkProps extends AriaLinkOptions, ClassNameProps {
children: React.ReactNode;
/** Whether the link is external (opens in new tab) */
isExternal?: boolean;
/** Typography style type */
type?: TextType;
/** Typography size variant */
size?: TextSize;
/** Text color variant */
color?: TextColor;
}
/**
@@ -26,7 +33,7 @@ export interface LinkProps extends AriaLinkOptions, ClassNameProps {
* It uses standard design system colors (--rb-ring) and provides responsive hover/focus states.
*/
export const Link = memo(function Link(props: LinkProps) {
const { children, className, href, isExternal, ...otherProps } = props;
const { children, className, href, isExternal, type, size, color, ...otherProps } = props;
// We use a specific ref type that useLink expects.
// Since Link can be an 'a' or a 'span', we use HTMLElement as common denominator.
@@ -34,16 +41,15 @@ export const Link = memo(function Link(props: LinkProps) {
const { linkProps } = useLink(otherProps, ref);
const sharedClasses = cn(
'text-[var(--rb-ring)] cursor-pointer hover:underline underline-offset-4 transition-colors',
!color && 'text-[var(--rb-ring)]',
'cursor-pointer hover:underline underline-offset-4 transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className
);
if (href) {
const isAutoExternal = isExternal ?? href.startsWith('http');
const externalProps = isAutoExternal
? { target: '_blank', rel: 'noopener noreferrer' }
: {};
const externalProps = isAutoExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {};
return (
<Text
@@ -52,7 +58,10 @@ export const Link = memo(function Link(props: LinkProps) {
ref={ref}
as="a"
className={sharedClasses}
color={color}
href={href}
size={size}
type={type}
>
{children}
</Text>
@@ -60,7 +69,15 @@ export const Link = memo(function Link(props: LinkProps) {
}
return (
<Text {...linkProps} ref={castRef<HTMLSpanElement>(ref)} as="span" className={sharedClasses}>
<Text
{...linkProps}
ref={castRef<HTMLSpanElement>(ref)}
as="span"
className={sharedClasses}
color={color}
size={size}
type={type}
>
{children}
</Text>
);
@@ -1,7 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { Markdown, MARKDOWN_COMPONENTS } from './Markdown';
import { Markdown } from './Markdown';
import type { Meta, StoryObj } from '@storybook/react';
const meta: Meta<typeof Markdown> = {
title: 'Components/Markdown',
@@ -22,94 +23,93 @@ export const KitchenSink: Story = {
render: () => (
<div className="max-w-[800px] mx-auto">
<Markdown>
<MARKDOWN_COMPONENTS.h1>The Art of Markdown Integration</MARKDOWN_COMPONENTS.h1>
<MARKDOWN_COMPONENTS.p>
<Markdown.H1>The Art of Markdown Integration</Markdown.H1>
<Markdown.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>
</Markdown.P>
<MARKDOWN_COMPONENTS.hr />
<Markdown.Hr />
<MARKDOWN_COMPONENTS.h2>Typography & Vertical Rhythm</MARKDOWN_COMPONENTS.h2>
<MARKDOWN_COMPONENTS.p>
<Markdown.H2>Typography & Vertical Rhythm</Markdown.H2>
<Markdown.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>
<Markdown.Code>Header</Markdown.Code> component, while paragraphs leverage the{' '}
<Markdown.Code>Paragraph</Markdown.Code> component with pre-configured gutters.
</Markdown.P>
<MARKDOWN_COMPONENTS.blockquote>
"Design is not just what it looks like and feels like. Design is how it works."
<Markdown.Blockquote>
&quot;Design is not just what it looks like and feels like. Design is how it works.&quot;
<br />
<MARKDOWN_COMPONENTS.a href="https://en.wikipedia.org/wiki/Steve_Jobs">— Steve Jobs</MARKDOWN_COMPONENTS.a>
</MARKDOWN_COMPONENTS.blockquote>
<Markdown.A href="https://en.wikipedia.org/wiki/Steve_Jobs">— Steve Jobs</Markdown.A>
</Markdown.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>
<Markdown.H3>Nesting and Lists</Markdown.H3>
<Markdown.P>
Lists are handled by our <Markdown.Code>List</Markdown.Code> system, supporting both
ordered and unordered variants:
</Markdown.P>
<MARKDOWN_COMPONENTS.ul>
<MARKDOWN_COMPONENTS.li>Standardization across all articles</MARKDOWN_COMPONENTS.li>
<MARKDOWN_COMPONENTS.li>
<Markdown.Ul>
<Markdown.Li>Standardization across all articles</Markdown.Li>
<Markdown.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>
<Markdown.Ul className="mt-2">
<Markdown.Li>Nested unordered item</Markdown.Li>
<Markdown.Li>Another nested item</Markdown.Li>
</Markdown.Ul>
</Markdown.Li>
<Markdown.Li>Consistent spacing and markers</Markdown.Li>
</Markdown.Ul>
<MARKDOWN_COMPONENTS.h2>Code & Technical Content</MARKDOWN_COMPONENTS.h2>
<MARKDOWN_COMPONENTS.p>
<Markdown.H2>Code & Technical Content</Markdown.H2>
<Markdown.P>
Technical articles often require code blocks. We use{' '}
<MARKDOWN_COMPONENTS.code>CodeBlock</MARKDOWN_COMPONENTS.code> for multi-line snippets:
</MARKDOWN_COMPONENTS.p>
<Markdown.Code>CodeBlock</Markdown.Code> for multi-line snippets:
</Markdown.P>
<MARKDOWN_COMPONENTS.pre label="markdown-example.tsx">
<MARKDOWN_COMPONENTS.code>
<Markdown.Pre label="markdown-example.tsx">
<Markdown.Code>
{`export const MarkdownP = memo(function MarkdownP(props: ParagraphProps) {
return <Paragraph gutterBottom {...props} />;
});`}
</MARKDOWN_COMPONENTS.code>
</MARKDOWN_COMPONENTS.pre>
</Markdown.Code>
</Markdown.Pre>
<MARKDOWN_COMPONENTS.h2>Data Representation</MARKDOWN_COMPONENTS.h2>
<MARKDOWN_COMPONENTS.p>
<Markdown.H2>Data Representation</Markdown.H2>
<Markdown.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.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.Table>
<Markdown.THead>
<Markdown.Tr>
<Markdown.Th>Component</Markdown.Th>
<Markdown.Th>Tag</Markdown.Th>
<Markdown.Th>Status</Markdown.Th>
</Markdown.Tr>
</Markdown.THead>
<Markdown.TBody>
<Markdown.Tr>
<Markdown.Td>MarkdownP</Markdown.Td>
<Markdown.Td>
<Markdown.Code>p</Markdown.Code>
</Markdown.Td>
<Markdown.Td>Verified</Markdown.Td>
</Markdown.Tr>
<Markdown.Tr>
<Markdown.Td>MarkdownTable</Markdown.Td>
<Markdown.Td>
<Markdown.Code>table</Markdown.Code>
</Markdown.Td>
<Markdown.Td>Verified</Markdown.Td>
</Markdown.Tr>
</Markdown.TBody>
</Markdown.Table>
<MARKDOWN_COMPONENTS.h2>Visual Assets</MARKDOWN_COMPONENTS.h2>
<MARKDOWN_COMPONENTS.img
<Markdown.H2>Visual Assets</Markdown.H2>
<Markdown.Img
alt="Atmospheric landscape"
ratio="16/9"
src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&q=80&w=1600"
@@ -126,12 +126,12 @@ export const KitchenSink: Story = {
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>
<Markdown.H1>Header Level 1</Markdown.H1>
<Markdown.H2>Header Level 2</Markdown.H2>
<Markdown.H3>Header Level 3</Markdown.H3>
<Markdown.H4>Header Level 4</Markdown.H4>
<Markdown.H5>Header Level 5</Markdown.H5>
<Markdown.H6>Header Level 6</Markdown.H6>
</div>
),
};
@@ -143,20 +143,20 @@ 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>
<Markdown.H4 className="mb-4">Unordered List</Markdown.H4>
<Markdown.Ul>
<Markdown.Li>First item</Markdown.Li>
<Markdown.Li>Second item</Markdown.Li>
<Markdown.Li>Third item</Markdown.Li>
</Markdown.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>
<Markdown.H4 className="mb-4">Ordered List</Markdown.H4>
<Markdown.Ol>
<Markdown.Li>Step one: Preparation</Markdown.Li>
<Markdown.Li>Step two: Implementation</Markdown.Li>
<Markdown.Li>Step three: Verification</Markdown.Li>
</Markdown.Ol>
</div>
</div>
),
@@ -10,19 +10,19 @@ describe('Markdown Components', () => {
<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.Ul>
<Markdown.Li>Item 1</Markdown.Li>
</Markdown.Ul>
<Markdown.Table>
<Markdown.THead>
<Markdown.TR>
<Markdown.TH>Header</Markdown.TH>
</Markdown.TR>
<Markdown.Tr>
<Markdown.Th>Header</Markdown.Th>
</Markdown.Tr>
</Markdown.THead>
<Markdown.TBody>
<Markdown.TR>
<Markdown.TD>Cell</Markdown.TD>
</Markdown.TR>
<Markdown.Tr>
<Markdown.Td>Cell</Markdown.Td>
</Markdown.Tr>
</Markdown.TBody>
</Markdown.Table>
</Markdown>
@@ -34,33 +34,7 @@ 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;
export { MARKDOWN_COMPONENTS } from './components';
const MarkdownComponent = memo(function Markdown({ children, className }: MarkdownProps) {
return <div className={cn('flex flex-col gap-4', className)}>{children}</div>;
@@ -92,17 +66,17 @@ export const Markdown = Object.assign(MarkdownComponent, {
H5: MarkdownH5,
H6: MarkdownH6,
Blockquote: MarkdownBlockquote,
UL: MarkdownUL,
OL: MarkdownOL,
LI: MarkdownLI,
Ul: MarkdownUL,
Ol: MarkdownOL,
Li: MarkdownLI,
Table: MarkdownTable,
THead: MarkdownTHead,
TBody: MarkdownTBody,
TR: MarkdownTR,
TH: MarkdownTH,
TD: MarkdownTD,
Tr: MarkdownTR,
Th: MarkdownTH,
Td: MarkdownTD,
Code: MarkdownCode,
Pre: MarkdownPre,
HR: MarkdownHR,
Hr: MarkdownHR,
Img: MarkdownImg,
});
@@ -9,4 +9,4 @@ import type { LinkProps } from '../../Link/Link';
*/
export const MarkdownA = memo(function MarkdownA(props: LinkProps) {
return <Link {...props} />;
});
});
@@ -9,4 +9,4 @@ import type { CodeProps } from '../../Code/Code';
*/
export const MarkdownCode = memo(function MarkdownCode(props: CodeProps) {
return <Code {...props} />;
});
});
@@ -11,4 +11,4 @@ import type { ParagraphProps } from '../../Paragraph/Paragraph';
*/
export const MarkdownP = memo(function MarkdownP(props: ParagraphProps) {
return <Paragraph gutterBottom {...props} />;
});
});
@@ -5,6 +5,8 @@ import { Table } from '../../Table/Table';
/**
* A Markdown table body (tbody) component.
*/
export const MarkdownTBody = memo(function MarkdownTBody(props: React.HTMLAttributes<HTMLTableSectionElement>) {
export const MarkdownTBody = memo(function MarkdownTBody(
props: React.HTMLAttributes<HTMLTableSectionElement>
) {
return <Table.Body {...props} />;
});
@@ -7,9 +7,14 @@ import { Table } from '../../Table/Table';
*
* 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;
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>
@@ -5,8 +5,12 @@ 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;
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} />;
});
@@ -5,6 +5,8 @@ import { Table } from '../../Table/Table';
/**
* A Markdown table head (thead) component.
*/
export const MarkdownTHead = memo(function MarkdownTHead(props: React.HTMLAttributes<HTMLTableSectionElement>) {
export const MarkdownTHead = memo(function MarkdownTHead(
props: React.HTMLAttributes<HTMLTableSectionElement>
) {
return <Table.Header {...props} />;
});
@@ -5,6 +5,8 @@ import { Table } from '../../Table/Table';
/**
* A Markdown table row (tr) component.
*/
export const MarkdownTR = memo(function MarkdownTR(props: React.HTMLAttributes<HTMLTableRowElement>) {
export const MarkdownTR = memo(function MarkdownTR(
props: React.HTMLAttributes<HTMLTableRowElement>
) {
return <Table.Row {...props} />;
});
@@ -0,0 +1,53 @@
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';
import type React from 'react';
/**
* A map of HTML tags to Markdown components for use with MDX or Astro Content.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const MARKDOWN_COMPONENTS: Record<string, React.ComponentType<any>> = {
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,
};
@@ -33,10 +33,7 @@ describe('components/Time', () => {
it('applies custom formatting options', () => {
const date = new Date('2026-02-10T00:00:00Z');
render(
<Time
date={date}
formatOptions={{ year: '2-digit', month: 'short', day: 'numeric' }}
/>
<Time date={date} formatOptions={{ year: '2-digit', month: 'short', day: 'numeric' }} />
);
// Depending on locale, "Feb 10, 26" or similar
+1 -6
View File
@@ -55,12 +55,7 @@ export const Time = genericMemo(
}, [children, dateObj, locale, formatOptions]);
return (
<Text
ref={castRef<HTMLElement>(ref)}
as="time"
dateTime={isoString}
{...props}
>
<Text ref={castRef<HTMLElement>(ref)} as="time" dateTime={isoString} {...props}>
{formattedDate}
</Text>
);
+12 -10
View File
@@ -1,22 +1,24 @@
export * from './Article/Article';
export * from './Blockquote/Blockquote';
export * from './Breadcrumbs/Breadcrumbs';
export * from './Button/Button';
export * from './Code/Code';
export * from './Code/CodeBlock/CodeBlock';
export * from './Container/Container';
export * from './Header/Header';
export * from './HStack/HStack';
export * from './Stack/Stack';
export * from './Text/Text';
export * from './VStack/VStack';
export * from './Image/Image';
export * from './Link/Link';
export * from './Breadcrumbs/Breadcrumbs';
export * from './List/List';
export * from './Markdown/Markdown';
export * from './Paragraph/Paragraph';
export * from './Button/Button';
export * from './Code';
export * from './Blockquote';
export * from './Separator/Separator';
export * from './Stack/Stack';
export * from './Stub/Stub';
export * from './Image/Image';
export * from './Table/Table';
export * from './Text/Text';
export * from './Time/Time';
export * from './Table';
export * from './VStack/VStack';
export type { StackProps } from './Stack/Stack';
export type { ListProps, ListVariant, ListSpacing, ListItemProps } from './List/List';