feat(infra): migrate to rsbuild
This commit is contained in:
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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
"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>
|
||||
<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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in new issue
Block a user