From ca203104601658f6b75f1b465c4c5e0c0f7f520c Mon Sep 17 00:00:00 2001 From: Vladimir Eremin Date: Sun, 1 Feb 2026 01:49:02 +0300 Subject: [PATCH] refactor: Refactor UI structure --- packages/ui/package.json | 1 + .../Article.stories.tsx} | 10 +- .../Article.test.tsx} | 12 +- .../PaperArticle.tsx => Article/Article.tsx} | 28 +- .../Breadcrumbs/Breadcrumbs.stories.tsx | 2 +- .../Breadcrumbs/Breadcrumbs.test.tsx | 0 .../{generic => }/Breadcrumbs/Breadcrumbs.tsx | 0 .../Button.stories.tsx} | 24 +- .../Button.test.tsx} | 46 ++-- .../PaperButton.tsx => Button/Button.tsx} | 14 +- .../Container/Container.stories.tsx | 2 +- .../Container/Container.test.tsx | 0 .../{generic => }/Container/Container.tsx | 0 .../{generic => }/HStack/HStack.tsx | 0 .../{generic => }/Header/Header.stories.tsx | 4 +- .../{generic => }/Header/Header.tsx | 0 .../{generic => }/Link/Link.stories.tsx | 2 +- .../{generic => }/Link/Link.test.tsx | 0 .../components/{generic => }/Link/Link.tsx | 0 .../components/Markdown/Markdown.stories.tsx | 254 ++++++++++++++++++ .../ui/src/components/Markdown/Markdown.tsx | 43 +++ .../Separator.stories.tsx} | 32 +-- .../Separator.tsx} | 20 +- .../{generic => }/Stack/Stack.stories.tsx | 2 +- .../components/{generic => }/Stack/Stack.tsx | 0 .../{generic => }/Text/Text.stories.tsx | 2 +- .../components/{generic => }/Text/Text.tsx | 0 .../{generic => }/VStack/VStack.tsx | 0 .../ui/src/components/{generic => }/index.ts | 1 + packages/ui/src/components/paper/index.ts | 3 - packages/ui/src/styles/index.css | 153 +---------- pnpm-lock.yaml | 21 ++ .../layouts/ArticleLayout/ArticleLayout.tsx | 6 +- 33 files changed, 435 insertions(+), 247 deletions(-) rename packages/ui/src/components/{paper/PaperArticle/PaperArticle.stories.tsx => Article/Article.stories.tsx} (82%) rename packages/ui/src/components/{paper/PaperArticle/PaperArticle.test.tsx => Article/Article.test.tsx} (68%) rename packages/ui/src/components/{paper/PaperArticle/PaperArticle.tsx => Article/Article.tsx} (66%) rename packages/ui/src/components/{generic => }/Breadcrumbs/Breadcrumbs.stories.tsx (97%) rename packages/ui/src/components/{generic => }/Breadcrumbs/Breadcrumbs.test.tsx (100%) rename packages/ui/src/components/{generic => }/Breadcrumbs/Breadcrumbs.tsx (100%) rename packages/ui/src/components/{paper/PaperButton/PaperButton.stories.tsx => Button/Button.stories.tsx} (83%) rename packages/ui/src/components/{paper/PaperButton/PaperButton.test.tsx => Button/Button.test.tsx} (71%) rename packages/ui/src/components/{paper/PaperButton/PaperButton.tsx => Button/Button.tsx} (84%) rename packages/ui/src/components/{generic => }/Container/Container.stories.tsx (99%) rename packages/ui/src/components/{generic => }/Container/Container.test.tsx (100%) rename packages/ui/src/components/{generic => }/Container/Container.tsx (100%) rename packages/ui/src/components/{generic => }/HStack/HStack.tsx (100%) rename packages/ui/src/components/{generic => }/Header/Header.stories.tsx (97%) rename packages/ui/src/components/{generic => }/Header/Header.tsx (100%) rename packages/ui/src/components/{generic => }/Link/Link.stories.tsx (93%) rename packages/ui/src/components/{generic => }/Link/Link.test.tsx (100%) rename packages/ui/src/components/{generic => }/Link/Link.tsx (100%) create mode 100644 packages/ui/src/components/Markdown/Markdown.stories.tsx create mode 100644 packages/ui/src/components/Markdown/Markdown.tsx rename packages/ui/src/components/{paper/PaperSeparator/PaperSeparator.stories.tsx => Separator/Separator.stories.tsx} (91%) rename packages/ui/src/components/{paper/PaperSeparator/PaperSeparator.tsx => Separator/Separator.tsx} (88%) rename packages/ui/src/components/{generic => }/Stack/Stack.stories.tsx (99%) rename packages/ui/src/components/{generic => }/Stack/Stack.tsx (100%) rename packages/ui/src/components/{generic => }/Text/Text.stories.tsx (98%) rename packages/ui/src/components/{generic => }/Text/Text.tsx (100%) rename packages/ui/src/components/{generic => }/VStack/VStack.tsx (100%) rename packages/ui/src/components/{generic => }/index.ts (93%) delete mode 100644 packages/ui/src/components/paper/index.ts diff --git a/packages/ui/package.json b/packages/ui/package.json index e3d3005..d6f22da 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -55,6 +55,7 @@ "@storybook/react-vite": "^8.6.15", "@storybook/test": "^8.6.15", "@storybook/test-runner": "^0.19.1", + "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.1.4", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", diff --git a/packages/ui/src/components/paper/PaperArticle/PaperArticle.stories.tsx b/packages/ui/src/components/Article/Article.stories.tsx similarity index 82% rename from packages/ui/src/components/paper/PaperArticle/PaperArticle.stories.tsx rename to packages/ui/src/components/Article/Article.stories.tsx index b9f49bc..d31bb2a 100644 --- a/packages/ui/src/components/paper/PaperArticle/PaperArticle.stories.tsx +++ b/packages/ui/src/components/Article/Article.stories.tsx @@ -1,11 +1,11 @@ -import { PaperArticle } from './PaperArticle'; +import { Article } from './Article'; import type { Meta, StoryObj } from '@storybook/react'; -const meta: Meta = { - title: 'Components/Paper/PaperArticle', - component: PaperArticle, +const meta: Meta = { + title: 'components/Article', + component: Article, tags: ['autodocs'], argTypes: { onPress: { action: 'pressed' }, @@ -16,7 +16,7 @@ export default meta; /** * */ -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = { args: { diff --git a/packages/ui/src/components/paper/PaperArticle/PaperArticle.test.tsx b/packages/ui/src/components/Article/Article.test.tsx similarity index 68% rename from packages/ui/src/components/paper/PaperArticle/PaperArticle.test.tsx rename to packages/ui/src/components/Article/Article.test.tsx index 19ba7b4..a1f4046 100644 --- a/packages/ui/src/components/paper/PaperArticle/PaperArticle.test.tsx +++ b/packages/ui/src/components/Article/Article.test.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { PaperArticle } from './PaperArticle'; -import { render, screen } from '../../../test-utils'; +import { Article } from './Article'; +import { render, screen } from '../../test-utils'; -describe('components/paper/PaperArticle', () => { +describe('components/paper/Article', () => { const props = { headline: 'Breaking News', subHeadline: 'Local cat saves city', @@ -11,7 +11,7 @@ describe('components/paper/PaperArticle', () => { }; it('renders all parts of the article', () => { - render(Article body content); + render(
Article body content
); expect(screen.getByText('Breaking News')).toBeInTheDocument(); expect(screen.getByText('Local cat saves city')).toBeInTheDocument(); @@ -20,11 +20,11 @@ describe('components/paper/PaperArticle', () => { }); it('renders separators only when appropriate', () => { - const { rerender } = render(Body); + const { rerender } = render(
Body
); // Should have 1 separator (between title and body) expect(screen.getAllByRole('separator')).toHaveLength(1); - rerender(Body); + rerender(
Body
); // Should have 2 separators (Title-Sub and Sub-Body) expect(screen.getAllByRole('separator')).toHaveLength(2); }); diff --git a/packages/ui/src/components/paper/PaperArticle/PaperArticle.tsx b/packages/ui/src/components/Article/Article.tsx similarity index 66% rename from packages/ui/src/components/paper/PaperArticle/PaperArticle.tsx rename to packages/ui/src/components/Article/Article.tsx index fd37111..c6ebf1b 100644 --- a/packages/ui/src/components/paper/PaperArticle/PaperArticle.tsx +++ b/packages/ui/src/components/Article/Article.tsx @@ -1,17 +1,17 @@ import React, { memo } from 'react'; -import { Container } from '../../generic/Container/Container'; -import { Header } from '../../generic/Header/Header'; -import { Text } from '../../generic/Text/Text'; -import { VStack } from '../../generic/VStack/VStack'; -import { PaperSeparator } from '../PaperSeparator/PaperSeparator'; +import { Container } from '../Container/Container'; +import { Header } from '../Header/Header'; +import { Separator } from '../Separator/Separator'; +import { Text } from '../Text/Text'; +import { VStack } from '../VStack/VStack'; -import type { ContainerProps } from '../../generic/Container/Container'; +import type { ContainerProps } from '../Container/Container'; /** - * Props for the newspaper-style PaperArticle component. + * Props for the Article component. */ -export interface PaperArticleProps { +export interface ArticleProps { /** The main article headline */ headline: string; /** Optional sub-headline displayed between separators */ @@ -25,7 +25,7 @@ export interface PaperArticleProps { } /** - * A newspaper-style article component with a structured layout. + * A article component with a structured layout. * * Features: * - Centered medium headline (headline-m) @@ -35,13 +35,13 @@ export interface PaperArticleProps { * - Optional bottom gray italic label (label-s) * - Interactive wrapper via Container */ -export const PaperArticle = memo(function PaperArticle({ +export const Article = memo(function Article({ headline, subHeadline, additionalText, onPress, children, -}: PaperArticleProps) { +}: ArticleProps) { return ( @@ -52,13 +52,13 @@ export const PaperArticle = memo(function PaperArticle({ {subHeadline ? ( <> - + {subHeadline} - + - ) : } + ) : } diff --git a/packages/ui/src/components/generic/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/ui/src/components/Breadcrumbs/Breadcrumbs.stories.tsx similarity index 97% rename from packages/ui/src/components/generic/Breadcrumbs/Breadcrumbs.stories.tsx rename to packages/ui/src/components/Breadcrumbs/Breadcrumbs.stories.tsx index 91d0b32..59c5c0e 100644 --- a/packages/ui/src/components/generic/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/packages/ui/src/components/Breadcrumbs/Breadcrumbs.stories.tsx @@ -6,7 +6,7 @@ import { Link } from '../Link/Link'; import type { Meta, StoryObj } from '@storybook/react'; const meta: Meta = { - title: 'components/generic/Breadcrumbs', + title: 'components/Breadcrumbs', component: Breadcrumbs, tags: ['autodocs'], }; diff --git a/packages/ui/src/components/generic/Breadcrumbs/Breadcrumbs.test.tsx b/packages/ui/src/components/Breadcrumbs/Breadcrumbs.test.tsx similarity index 100% rename from packages/ui/src/components/generic/Breadcrumbs/Breadcrumbs.test.tsx rename to packages/ui/src/components/Breadcrumbs/Breadcrumbs.test.tsx diff --git a/packages/ui/src/components/generic/Breadcrumbs/Breadcrumbs.tsx b/packages/ui/src/components/Breadcrumbs/Breadcrumbs.tsx similarity index 100% rename from packages/ui/src/components/generic/Breadcrumbs/Breadcrumbs.tsx rename to packages/ui/src/components/Breadcrumbs/Breadcrumbs.tsx diff --git a/packages/ui/src/components/paper/PaperButton/PaperButton.stories.tsx b/packages/ui/src/components/Button/Button.stories.tsx similarity index 83% rename from packages/ui/src/components/paper/PaperButton/PaperButton.stories.tsx rename to packages/ui/src/components/Button/Button.stories.tsx index f425121..4592c3a 100644 --- a/packages/ui/src/components/paper/PaperButton/PaperButton.stories.tsx +++ b/packages/ui/src/components/Button/Button.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { PaperButton } from './PaperButton'; +import { Button } from './Button'; import type { Meta, StoryObj } from '@storybook/react'; @@ -15,9 +15,9 @@ import type { Meta, StoryObj } from '@storybook/react'; * - Accessible interaction patterns via react-aria * - Responsive hover and focus states */ -const meta: Meta = { - title: 'components/paper/PaperButton', - component: PaperButton, +const meta: Meta = { + title: 'components/Button', + component: Button, parameters: { layout: 'centered', docs: { @@ -111,18 +111,18 @@ export const LongText: Story = { export const AllVariants: Story = { render: () => (
- console.log('Default')}> + + + +
), }; diff --git a/packages/ui/src/components/paper/PaperButton/PaperButton.test.tsx b/packages/ui/src/components/Button/Button.test.tsx similarity index 71% rename from packages/ui/src/components/paper/PaperButton/PaperButton.test.tsx rename to packages/ui/src/components/Button/Button.test.tsx index 1b4dcbd..0e16c54 100644 --- a/packages/ui/src/components/paper/PaperButton/PaperButton.test.tsx +++ b/packages/ui/src/components/Button/Button.test.tsx @@ -1,27 +1,27 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; -import { PaperButton } from './PaperButton'; -import { render, screen } from '../../../test-utils'; +import { Button } from './Button'; +import { render, screen } from '../../test-utils'; /** - * Test suite for the newspaper-style PaperButton component + * Test suite for the newspaper-style Button component */ -describe('components/paper/PaperButton/PaperButton', () => { +describe('components/paper/Button/Button', () => { const defaultProps = { - children: 'Test PaperButton', + children: 'Test Button', }; it('renders correctly with default props', () => { - render(); + render(); const button = screen.getByRole('button'); await user.click(button); @@ -47,19 +47,19 @@ describe('components/paper/PaperButton/PaperButton', () => { it('supports different variants', () => { const { rerender } = render( - {defaultProps.children} + ); let button = screen.getByRole('button'); expect(button).toHaveClass('bg-primary'); - rerender({defaultProps.children}); + rerender(); button = screen.getByRole('button'); expect(button).toHaveClass('bg-secondary'); }); it('handles disabled state correctly', () => { - render({defaultProps.children}); + render(); const button = screen.getByRole('button'); expect(button).toBeDisabled(); @@ -72,9 +72,9 @@ describe('components/paper/PaperButton/PaperButton', () => { const handlePress = jest.fn(); render( - + ); const button = screen.getByRole('button'); @@ -86,7 +86,7 @@ describe('components/paper/PaperButton/PaperButton', () => { it('supports custom className prop', () => { const customClass = 'custom-button-class'; - render({defaultProps.children}); + render(); const button = screen.getByRole('button'); expect(button).toHaveClass(customClass); @@ -95,27 +95,27 @@ describe('components/paper/PaperButton/PaperButton', () => { it('supports data-testid prop', () => { const testId = 'my-test-button'; - render({defaultProps.children}); + render(); const button = screen.getByTestId(testId); expect(button).toBeInTheDocument(); - expect(button).toHaveTextContent('Test PaperButton'); + expect(button).toHaveTextContent('Test Button'); }); it('only accepts string children', () => { // This test verifies TypeScript constraint - should compile without error - render(Simple string); + render(); expect(screen.getByText('Simple string')).toBeInTheDocument(); }); it('maintains accessibility attributes', () => { render( - {defaultProps.children} - + ); const button = screen.getByRole('button'); @@ -127,7 +127,7 @@ describe('components/paper/PaperButton/PaperButton', () => { const user = userEvent.setup(); const handlePress = jest.fn(); - render({defaultProps.children}); + render(); const button = screen.getByRole('button'); button.focus(); @@ -142,7 +142,7 @@ describe('components/paper/PaperButton/PaperButton', () => { }); it('renders string content correctly', () => { - render(Simple text content); + render(); expect(screen.getByText('Simple text content')).toBeInTheDocument(); }); }); diff --git a/packages/ui/src/components/paper/PaperButton/PaperButton.tsx b/packages/ui/src/components/Button/Button.tsx similarity index 84% rename from packages/ui/src/components/paper/PaperButton/PaperButton.tsx rename to packages/ui/src/components/Button/Button.tsx index 015d29b..439b75a 100644 --- a/packages/ui/src/components/paper/PaperButton/PaperButton.tsx +++ b/packages/ui/src/components/Button/Button.tsx @@ -5,9 +5,9 @@ import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl'; import type { AriaButtonProps } from 'react-aria'; /** - * Props for the newspaper-style PaperButton component. + * Props for the Button component. */ -export interface PaperButtonProps extends AriaButtonProps, ClassNameProps, TestIdProps { +export interface ButtonProps extends AriaButtonProps, ClassNameProps, TestIdProps { /** The button text content */ children: string; /** Visual style variant */ @@ -15,7 +15,7 @@ export interface PaperButtonProps extends AriaButtonProps, ClassNameProps, TestI } /** - * A newspaper-style button component with italic Lato font and underlined text. + * A button component with italic Lato font and underlined text. * * Features: * - Rectangular design with subtle background @@ -26,18 +26,18 @@ export interface PaperButtonProps extends AriaButtonProps, ClassNameProps, TestI * * @example * ```tsx - * console.log('Clicked!')}> + * * ``` */ -export const PaperButton = memo(function PaperButton({ +export const Button = memo(function Button({ children, variant = 'default', className = '', 'data-testid': testId, ...buttonProps -}: PaperButtonProps) { +}: ButtonProps) { const ref = React.useRef(null); const { buttonProps: ariaButtonProps } = useButton(buttonProps, ref); diff --git a/packages/ui/src/components/generic/Container/Container.stories.tsx b/packages/ui/src/components/Container/Container.stories.tsx similarity index 99% rename from packages/ui/src/components/generic/Container/Container.stories.tsx rename to packages/ui/src/components/Container/Container.stories.tsx index 4af4123..b25282b 100644 --- a/packages/ui/src/components/generic/Container/Container.stories.tsx +++ b/packages/ui/src/components/Container/Container.stories.tsx @@ -8,7 +8,7 @@ import type { Meta, StoryObj } from '@storybook/react'; const meta: Meta = { - title: 'components/generic/Container', + title: 'components/Container', component: Container, tags: ['autodocs'], argTypes: { diff --git a/packages/ui/src/components/generic/Container/Container.test.tsx b/packages/ui/src/components/Container/Container.test.tsx similarity index 100% rename from packages/ui/src/components/generic/Container/Container.test.tsx rename to packages/ui/src/components/Container/Container.test.tsx diff --git a/packages/ui/src/components/generic/Container/Container.tsx b/packages/ui/src/components/Container/Container.tsx similarity index 100% rename from packages/ui/src/components/generic/Container/Container.tsx rename to packages/ui/src/components/Container/Container.tsx diff --git a/packages/ui/src/components/generic/HStack/HStack.tsx b/packages/ui/src/components/HStack/HStack.tsx similarity index 100% rename from packages/ui/src/components/generic/HStack/HStack.tsx rename to packages/ui/src/components/HStack/HStack.tsx diff --git a/packages/ui/src/components/generic/Header/Header.stories.tsx b/packages/ui/src/components/Header/Header.stories.tsx similarity index 97% rename from packages/ui/src/components/generic/Header/Header.stories.tsx rename to packages/ui/src/components/Header/Header.stories.tsx index 3169eab..4d80ef3 100644 --- a/packages/ui/src/components/generic/Header/Header.stories.tsx +++ b/packages/ui/src/components/Header/Header.stories.tsx @@ -7,7 +7,7 @@ import type { Meta, StoryObj } from '@storybook/react'; const meta: Meta = { - title: 'Generic/Header', + title: 'components/Header', component: Header, tags: ['autodocs'], argTypes: { @@ -56,4 +56,4 @@ export const Overrides: Story = {
H6 with Modifiers
), -}; \ No newline at end of file +}; diff --git a/packages/ui/src/components/generic/Header/Header.tsx b/packages/ui/src/components/Header/Header.tsx similarity index 100% rename from packages/ui/src/components/generic/Header/Header.tsx rename to packages/ui/src/components/Header/Header.tsx diff --git a/packages/ui/src/components/generic/Link/Link.stories.tsx b/packages/ui/src/components/Link/Link.stories.tsx similarity index 93% rename from packages/ui/src/components/generic/Link/Link.stories.tsx rename to packages/ui/src/components/Link/Link.stories.tsx index 3975e30..dcd30c0 100644 --- a/packages/ui/src/components/generic/Link/Link.stories.tsx +++ b/packages/ui/src/components/Link/Link.stories.tsx @@ -4,7 +4,7 @@ import { Link } from './Link'; import type { Meta, StoryObj } from '@storybook/react'; const meta: Meta = { - title: 'components/generic/Link', + title: 'components/Link', component: Link, tags: ['autodocs'], argTypes: { diff --git a/packages/ui/src/components/generic/Link/Link.test.tsx b/packages/ui/src/components/Link/Link.test.tsx similarity index 100% rename from packages/ui/src/components/generic/Link/Link.test.tsx rename to packages/ui/src/components/Link/Link.test.tsx diff --git a/packages/ui/src/components/generic/Link/Link.tsx b/packages/ui/src/components/Link/Link.tsx similarity index 100% rename from packages/ui/src/components/generic/Link/Link.tsx rename to packages/ui/src/components/Link/Link.tsx diff --git a/packages/ui/src/components/Markdown/Markdown.stories.tsx b/packages/ui/src/components/Markdown/Markdown.stories.tsx new file mode 100644 index 0000000..42b298e --- /dev/null +++ b/packages/ui/src/components/Markdown/Markdown.stories.tsx @@ -0,0 +1,254 @@ +import { Markdown } from './Markdown'; + +import type { Meta, StoryObj } from '@storybook/react'; + +/** + * Metadata for the Markdown component stories. + */ +const meta: Meta = { + title: 'components/Markdown', + component: Markdown, + tags: ['autodocs'], +}; + +export default meta; + +/** + * Type definition for Markdown component stories. + */ +type Story = StoryObj; + +/** + * Default story showcasing various HTML elements to verify typography. + */ +export const Default: Story = { + args: { + children: ( + <> +

+ This is a Markdown component that uses Tailwind CSS Typography (prose) + to render content with consistent styling. +

+

+ It is designed to be used with rendered HTML from markdown processors, + but works with any HTML content that needs standard document styling. +

+ + ), + }, +}; + +/** + * Showcasing all heading levels. + */ +export const Typography: Story = { + args: { + children: ( + <> +

Heading Level 1

+

Paragraph text following an H1.

+

Heading Level 2

+

Paragraph text following an H2.

+

Heading Level 3

+

Paragraph text following an H3.

+

Heading Level 4

+

Paragraph text following an H4.

+
Heading Level 5
+

Paragraph text following an H5.

+
Heading Level 6
+

Paragraph text following an H6.

+ + ), + }, +}; + +/** + * Showcasing ordered and unordered lists. + */ +export const Lists: Story = { + args: { + children: ( + <> +

Unordered List

+
    +
  • First item
  • +
  • Second item +
      +
    • Nested item A
    • +
    • Nested item B
    • +
    +
  • +
  • Third item
  • +
+ +

Ordered List

+
    +
  1. Step one
  2. +
  3. Step two +
      +
    1. Sub-step 2.1
    2. +
    3. Sub-step 2.2
    4. +
    +
  4. +
  5. Step three
  6. +
+ + ), + }, +}; + +/** + * Showcasing blockquotes. + */ +export const Quotes: Story = { + args: { + children: ( + <> +

Before the quote.

+
+ "Design is not just what it looks like and feels like. Design is how it works." +
— Steve Jobs
+
+

After the quote.

+ + ), + }, +}; + +/** + * Showcasing code and pre blocks. + */ +export const Code: Story = { + args: { + children: ( + <> +

+ You can use inline code for small snippets. +

+
+          {`function hello() {
+  console.log("Hello, world!");
+}`}
+        
+ + ), + }, +}; + +/** + * Showcasing tables. + */ +export const Tables: Story = { + args: { + children: ( + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureDescriptionStatus
TypographyGaramond and Lato fonts✅ Done
Dark ModeAutomatic inversion✅ Done
ResponsiveFluid layouts✅ Done
+ ), + }, +}; + +/** + * The "Kitchen Sink" containing everything. + */ +export const KitchenSink: Story = { + args: { + children: ( + <> +

The Art of Markdown Rendering

+

+ A lead paragraph that stands out and sets the tone for the rest of the document. +

+

+ 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. +

+ +

Key Features

+
    +
  • Easy to read - The source text looks like the final output.
  • +
  • Easy to write - Minimal syntax overhead.
  • +
  • Versatile - Supports many elements like italics, strikethrough, and code.
  • +
+ +

Example Code Block

+
+          {`import { Markdown } from '@ermnvldmr/ui';
+
+export const MyPage = () => (
+  
+    

Welcome

+

This is styled automatically.

+
+);`}
+
+ +
+ "Simplicity is the ultimate sophistication." +
+ — Leonardo da Vinci +
+ +
+ +

Data Representation

+ + + + + + + + + + + + + + + + + + + + + + + + + +
LanguageDifficultyUsage
TypeScriptModerateFrontend/Backend
RustHighSystems
PythonLowData Science
+ +

Media Elements

+

Images should be rounded and have a nice shadow.

+ Coding + +

Conclusion: We've built a robust system for rendering content.

+ + ), + }, +}; diff --git a/packages/ui/src/components/Markdown/Markdown.tsx b/packages/ui/src/components/Markdown/Markdown.tsx new file mode 100644 index 0000000..f827670 --- /dev/null +++ b/packages/ui/src/components/Markdown/Markdown.tsx @@ -0,0 +1,43 @@ +import { cn } from '@ermnvldmr/stl'; +import React from 'react'; + +/** + * Props for the Markdown component. + */ +/** + * Props for the Markdown component. + */ +export interface MarkdownProps { + /** The content to be rendered, typically HTML from a markdown processor. */ + children: React.ReactNode; + /** Optional additional class names. */ + className?: string; +} + +/** + * 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. + * @example + * ```tsx + * + *

Title

+ *

Content goes here.

+ *
+ * ``` + */ +export const Markdown: React.FC = ({ children, className }) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/ui/src/components/paper/PaperSeparator/PaperSeparator.stories.tsx b/packages/ui/src/components/Separator/Separator.stories.tsx similarity index 91% rename from packages/ui/src/components/paper/PaperSeparator/PaperSeparator.stories.tsx rename to packages/ui/src/components/Separator/Separator.stories.tsx index 7cf056b..79c0a8f 100644 --- a/packages/ui/src/components/paper/PaperSeparator/PaperSeparator.stories.tsx +++ b/packages/ui/src/components/Separator/Separator.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { PaperSeparator } from './PaperSeparator'; +import { Separator } from './Separator'; import type { Meta, StoryObj } from '@storybook/react'; @@ -15,9 +15,9 @@ import type { Meta, StoryObj } from '@storybook/react'; * - 2px stroke width following print design standards * - Semantic div elements with proper ARIA separator role for accessibility */ -const meta: Meta = { - title: 'components/paper/PaperSeparator', - component: PaperSeparator, +const meta: Meta = { + title: 'components/Separator', + component: Separator, parameters: { layout: 'centered', docs: { @@ -56,7 +56,7 @@ const meta: Meta = { export default meta; /** - * Story type for PaperSeparator component stories. + * Story type for Separator component stories. */ type Story = StoryObj; @@ -201,7 +201,7 @@ export const VerticalDouble: Story = { }; /** - * PaperSeparator without thinned ends showing clean, sharp line edges. + * Separator without thinned ends showing clean, sharp line edges. * Compare with default to see the gradient fade difference. */ export const NonThinned: Story = { @@ -232,15 +232,15 @@ export const AllColors: Story = {

Black

- +

Outline (Default for Double)

- +

Outline Light (Default for Single)

- +
), @@ -255,11 +255,11 @@ export const AllTypes: Story = {

Single (Default: Outline Light)

- +

Double (Default: Outline)

- +
), @@ -274,11 +274,11 @@ export const ThinnedComparison: Story = {

With Thinned Ends (Default)

- +

Without Thinned Ends

- +
), @@ -296,7 +296,7 @@ export const VerticalShowcase: Story = { Short
- +
Content @@ -308,7 +308,7 @@ export const VerticalShowcase: Story = { Medium
- +
Content @@ -320,7 +320,7 @@ export const VerticalShowcase: Story = { Tall
- +
Content diff --git a/packages/ui/src/components/paper/PaperSeparator/PaperSeparator.tsx b/packages/ui/src/components/Separator/Separator.tsx similarity index 88% rename from packages/ui/src/components/paper/PaperSeparator/PaperSeparator.tsx rename to packages/ui/src/components/Separator/Separator.tsx index c9e21c3..55ad0e6 100644 --- a/packages/ui/src/components/paper/PaperSeparator/PaperSeparator.tsx +++ b/packages/ui/src/components/Separator/Separator.tsx @@ -1,15 +1,15 @@ import { cn } from '@ermnvldmr/stl'; import React, { memo } from 'react'; -import { VStack } from '../../generic/VStack/VStack'; +import { VStack } from '../VStack/VStack'; import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl'; /** - * Props for the newspaper-style PaperSeparator component. + * Props for the Separator component. */ -export interface PaperSeparatorProps extends ClassNameProps, TestIdProps { +export interface SeparatorProps extends ClassNameProps, TestIdProps { /** Type of separator line styling */ type?: 'single' | 'double'; /** Color variant following newspaper design patterns */ @@ -21,7 +21,7 @@ export interface PaperSeparatorProps extends ClassNameProps, TestIdProps { } /** - * A newspaper-style separator component for dividing content sections. + * A separator component for dividing content sections. * * Features: * - Single or double line variants inspired by newspaper layout traditions @@ -37,26 +37,26 @@ export interface PaperSeparatorProps extends ClassNameProps, TestIdProps { * @example * ```tsx * // Basic horizontal separator - * + * * * // Double-line separator with black color - * + * * * // Vertical separator without thinned ends - * + * * * // Light separator for subtle content division - * + * * ``` */ -export const PaperSeparator = memo(function PaperSeparator({ +export const Separator = memo(function Separator({ type = 'single', color, direction = 'horizontal', thinned = true, className, 'data-testid': testId, -}: PaperSeparatorProps) { +}: SeparatorProps) { // Default color based on type as specified const defaultColor = type === 'single' ? 'outline-light' : 'outline'; const finalColor = color ?? defaultColor; diff --git a/packages/ui/src/components/generic/Stack/Stack.stories.tsx b/packages/ui/src/components/Stack/Stack.stories.tsx similarity index 99% rename from packages/ui/src/components/generic/Stack/Stack.stories.tsx rename to packages/ui/src/components/Stack/Stack.stories.tsx index 6d9ea04..de417bf 100644 --- a/packages/ui/src/components/generic/Stack/Stack.stories.tsx +++ b/packages/ui/src/components/Stack/Stack.stories.tsx @@ -12,7 +12,7 @@ import type { Meta, StoryObj } from '@storybook/react'; * through semantic prop names that map to Tailwind CSS utility classes. */ const meta: Meta = { - title: 'components/generic/Stack', + title: 'components/Stack', component: Stack, parameters: { layout: 'centered', diff --git a/packages/ui/src/components/generic/Stack/Stack.tsx b/packages/ui/src/components/Stack/Stack.tsx similarity index 100% rename from packages/ui/src/components/generic/Stack/Stack.tsx rename to packages/ui/src/components/Stack/Stack.tsx diff --git a/packages/ui/src/components/generic/Text/Text.stories.tsx b/packages/ui/src/components/Text/Text.stories.tsx similarity index 98% rename from packages/ui/src/components/generic/Text/Text.stories.tsx rename to packages/ui/src/components/Text/Text.stories.tsx index 6d55e14..78fd6c1 100644 --- a/packages/ui/src/components/generic/Text/Text.stories.tsx +++ b/packages/ui/src/components/Text/Text.stories.tsx @@ -7,7 +7,7 @@ import type { Meta, StoryObj } from '@storybook/react'; const meta: Meta = { - title: 'components/generic/Text', + title: 'components/Text', component: Text, tags: ['autodocs'], argTypes: { diff --git a/packages/ui/src/components/generic/Text/Text.tsx b/packages/ui/src/components/Text/Text.tsx similarity index 100% rename from packages/ui/src/components/generic/Text/Text.tsx rename to packages/ui/src/components/Text/Text.tsx diff --git a/packages/ui/src/components/generic/VStack/VStack.tsx b/packages/ui/src/components/VStack/VStack.tsx similarity index 100% rename from packages/ui/src/components/generic/VStack/VStack.tsx rename to packages/ui/src/components/VStack/VStack.tsx diff --git a/packages/ui/src/components/generic/index.ts b/packages/ui/src/components/index.ts similarity index 93% rename from packages/ui/src/components/generic/index.ts rename to packages/ui/src/components/index.ts index 6aef11a..89ba14f 100644 --- a/packages/ui/src/components/generic/index.ts +++ b/packages/ui/src/components/index.ts @@ -6,6 +6,7 @@ export * from './Text/Text'; export * from './VStack/VStack'; export * from './Link/Link'; export * from './Breadcrumbs/Breadcrumbs'; +export * from './Markdown/Markdown'; export type { StackProps } from './Stack/Stack'; export type { ContainerProps, ContainerBackground, ContainerMaxWidth, ContainerRounded } from './Container/Container'; diff --git a/packages/ui/src/components/paper/index.ts b/packages/ui/src/components/paper/index.ts deleted file mode 100644 index df8f0d3..0000000 --- a/packages/ui/src/components/paper/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './PaperButton/PaperButton'; -export * from './PaperSeparator/PaperSeparator'; -export * from './PaperArticle/PaperArticle'; diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 0ad1df2..72abf40 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -1,63 +1,10 @@ @import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap'); + @import 'tailwindcss'; @import 'tw-animate-css'; @custom-variant dark (&:where(.dark, .dark *)); -/* - @layer base defaults inspired by shadcn UI Typography - Provides opinionated, utility-driven defaults for HTML elements. -*/ -@layer base { - /* Headings */ - h1 { - @apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl; - } - h2 { - @apply mt-10 scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0; - } - h3, - h4, - h5, - h6 { - @apply mt-8 scroll-m-20 text-2xl font-semibold tracking-tight; - } - - /* Paragraphs */ - p { - @apply leading-7 [&:not(:first-child)]:mt-6; - } - - /* Links */ - a { - @apply text-ring font-medium underline underline-offset-2; - } - - /* Blockquotes */ - blockquote { - @apply mt-6 border-l-2 pl-6 italic; - } - - /* Code */ - code { - @apply bg-muted rounded px-1 font-mono text-sm; - } - pre { - @apply bg-muted overflow-auto rounded-lg p-4 font-mono text-sm; - } - - /* Images and Media */ - img, - video { - @apply max-w-full rounded-lg; - } - - /* Horizontal rules */ - hr { - @apply border-border my-6 border-t; - } -} - @theme { --font-sans: 'Lato', sans-serif; --font-serif: 'EB Garamond', serif; @@ -186,24 +133,11 @@ --rb-muted-text: var(--rb-color-neutral-600); /* Charts */ - --chart-1: var(--rb-color-blue-500); - --chart-2: var(--rb-color-green-500); - --chart-3: var(--rb-color-yellow-500); - --chart-4: var(--rb-color-orange-500); - --chart-5: var(--rb-color-red-500); - - /* Sidebar */ - --sidebar: var(--rb-color-neutral-50); - --sidebar-foreground: var(--rb-color-neutral-950); - --sidebar-primary: var(--rb-color-cyan-200); - --sidebar-primary-foreground: var(--rb-color-cyan-950); - --sidebar-accent: var(--rb-color-violet-200); - --sidebar-accent-foreground: var(--rb-color-violet-950); - --sidebar-border: var(--rb-color-neutral-500); - --sidebar-ring: var(--rb-color-cyan-700); - - /* Radii */ - --radius: 0.625rem; + --rb-chart-1: var(--rb-color-blue-500); + --rb--chart-2: var(--rb-color-green-500); + --rb-chart-3: var(--rb-color-yellow-500); + --rb-chart-4: var(--rb-color-orange-500); + --rb-chart-5: var(--rb-color-red-500); } .dark { @@ -231,79 +165,14 @@ --rb-muted-text: var(--rb-color-neutral-100); /* Charts */ - --chart-1: var(--rb-color-blue-400); - --chart-2: var(--rb-color-green-400); - --chart-3: var(--rb-color-yellow-400); - --chart-4: var(--rb-color-orange-400); - --chart-5: var(--rb-color-red-400); - - /* Sidebar */ - --sidebar: var(--rb-color-neutral-950); - --sidebar-foreground: var(--rb-color-neutral-50); - --sidebar-primary: var(--rb-color-cyan-800); - --sidebar-primary-foreground: var(--rb-color-cyan-50); - --sidebar-accent: var(--rb-color-violet-800); - --sidebar-accent-foreground: var(--rb-color-violet-50); - --sidebar-border: var(--rb-color-neutral-500); - --sidebar-ring: var(--rb-color-cyan-700); -} - -@utility vstack { - @apply flex flex-col; -} - -@utility hstack { - @apply flex flex-row; -} - -@theme inline { - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - - --color-background: var(--rb-base); - --color-foreground: var(--rb-text); - --color-card: var(--rb-container-base); - --color-card-foreground: var(--rb-container-text); - --color-popover: var(--rb-container-base); - --color-popover-foreground: var(--rb-container-text); - - --color-primary: var(--rb-primary-base); - --color-primary-foreground: var(--rb-primary-text); - --color-secondary: var(--rb-secondary-base); - --color-secondary-foreground: var(--rb-secondary-text); - --color-accent: var(--rb-tertiary-base); - --color-accent-foreground: var(--rb-tertiary-text); - --color-destructive: var(--rb-error-base); - --color-destructive-foreground: var(--rb-error-text); - - --color-muted: var(--rb-muted-base); - --color-muted-foreground: var(--rb-muted-text); - --color-border: var(--rb-outline); - --color-input: var(--rb-input); - --color-ring: var(--rb-ring); - - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); + --rb-chart-1: var(--rb-color-blue-400); + --rb-chart-2: var(--rb-color-green-400); + --rb-chart-3: var(--rb-color-yellow-400); + --rb-chart-4: var(--rb-color-orange-400); + --rb-chart-5: var(--rb-color-red-400); } @layer base { - * { - @apply border-border outline-ring/50; - } body { @apply bg-background text-foreground; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bce8b8b..276ceef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -262,6 +262,9 @@ importers: '@storybook/test-runner': specifier: ^0.19.1 version: 0.19.1(@swc/helpers@0.5.18)(@types/node@25.0.10)(storybook@8.6.15(prettier@3.5.3))(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.0.10)(typescript@5.8.3)) + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19 '@tailwindcss/vite': specifier: ^4.1.4 version: 4.1.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) @@ -3084,6 +3087,11 @@ packages: resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} engines: {node: '>= 10'} + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + '@tailwindcss/vite@4.1.18': resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} peerDependencies: @@ -6305,6 +6313,10 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} @@ -10922,6 +10934,10 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + '@tailwindcss/typography@0.5.19': + dependencies: + postcss-selector-parser: 6.0.10 + '@tailwindcss/vite@4.1.18(vite@6.4.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@tailwindcss/node': 4.1.18 @@ -15416,6 +15432,11 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 diff --git a/services/www/src/layouts/ArticleLayout/ArticleLayout.tsx b/services/www/src/layouts/ArticleLayout/ArticleLayout.tsx index 140c84b..7c755fa 100644 --- a/services/www/src/layouts/ArticleLayout/ArticleLayout.tsx +++ b/services/www/src/layouts/ArticleLayout/ArticleLayout.tsx @@ -1,5 +1,5 @@ import { cn } from '@ermnvldmr/stl'; -import { VStack, HStack } from '@ermnvldmr/ui'; +import { VStack, HStack, Markdown } from '@ermnvldmr/ui'; import React from 'react'; /** @@ -100,7 +100,9 @@ export const ArticleLayout: React.FC = ({ -
{children}
+
+ {children} +
); };