feat: Refactor layouts in www

This commit is contained in:
deytenit committed 2026-03-07 20:59:03 +03:00
1 parent 6aa3a03319
commit f3a3a52aa8
66 files changed
+1805 -352

No files matched your search

@@ -31,9 +31,8 @@ describe('components/Article', () => {
</Article>
);
separators = screen.getAllByRole('separator');
expect(separators).toHaveLength(2);
expect(separators[0].className).not.toContain('mask-image');
expect(separators[1].className).toContain('mask-image');
expect(separators).toHaveLength(1);
expect(separators[0].className).toContain('mask-image');
});
it('renders as a link when href is provided', () => {
+12 -14
View File
@@ -56,26 +56,24 @@ export const Article = memo(function Article({
return (
<Container className={className} href={href} padding={4} onPress={onPress}>
<VStack align="stretch" gap={4}>
<Header level={4}>{headline}</Header>
<VStack align="stretch" gap={2}>
{subHeadline ? (
<>
<Separator thinned="none" type="single" />
<Text italic size="m" type="label">
{subHeadline}
</Text>
<Header level={4}>{headline}</Header>
<VStack align="stretch" gap={2}>
{subHeadline ? (
<>
<Text italic size="m" type="label">
{subHeadline}
</Text>
<Separator thinned="thinned-end" type="single" />
</>
) : (
<Separator thinned="thinned-end" type="single" />
</>
) : (
<Separator thinned="thinned-end" type="single" />
)}
)}
</VStack>
</VStack>
<Text size="m" type="body">
{children}
</Text>
{additionalText && (
<VStack align="end">
<Text italic color="muted" size="s" type="label">
@@ -0,0 +1,35 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { describe, it, expect } from 'vitest';
import { PageColumn } from './PageColumn';
describe('PageColumn', () => {
it('renders children correctly', () => {
render(
<PageColumn>
<div data-testid="child">Test Content</div>
</PageColumn>
);
expect(screen.getByTestId('child')).toBeInTheDocument();
});
it('applies size classes', () => {
const { container: small } = render(<PageColumn size="small">Content</PageColumn>);
expect(small.firstChild).toHaveClass('lg:w-[var(--rb-page-column-small,300px)]');
const { container: full } = render(<PageColumn size="full">Content</PageColumn>);
expect(full.firstChild).toHaveClass('flex-1');
});
it('handles sticky prop', () => {
const { container } = render(<PageColumn sticky>Content</PageColumn>);
expect(container.firstChild).toHaveClass('lg:sticky');
expect(container.firstChild).toHaveClass('lg:top-24');
});
it('renders as different element', () => {
render(<PageColumn as="aside">Content</PageColumn>);
expect(screen.getByRole('complementary')).toBeInTheDocument();
});
});
@@ -0,0 +1,66 @@
import { cn } from '@ermnvldmr/stl';
import React from 'react';
/**
* Props for the PageColumn component.
*/
export interface PageColumnProps {
/** The content of the column. */
children: React.ReactNode;
/**
* The size of the column on desktop.
* - `small`: Fixed width (300px)
* - `full`: Takes up remaining space (flex-1)
* @default 'full'
*/
size?: 'small' | 'full';
/**
* If true, the column will stick to the top when scrolling on desktop.
* @default false
*/
sticky?: boolean;
/**
* Optional additional class names.
*/
className?: string;
/**
* The HTML element to use for the root of the component.
* @default 'div'
*/
as?: React.ElementType;
}
/**
/**
* A single column unit within the PageColumns host.
*
* @param props - The component props.
* @returns A PageColumn component.
* @example
* ```tsx
* <PageColumn size="small">Sidebar</PageColumn>
* ```
*/
export const PageColumn = ({
children,
size = 'full',
sticky = false,
className,
as: Component = 'div',
}: PageColumnProps) => {
return (
<Component
className={cn(
'w-full transition-all duration-200',
{
'lg:w-[var(--rb-page-column-small,300px)] lg:flex-none': size === 'small',
'flex-1': size === 'full',
'lg:sticky lg:top-24 self-start': sticky,
},
className
)}
>
{children}
</Component>
);
};
@@ -0,0 +1,137 @@
import React from 'react';
import { PageColumns } from './PageColumns';
import { PageColumn } from '../PageColumn/PageColumn';
import type { Meta, StoryObj } from '@storybook/react';
const meta: Meta<typeof PageColumns> = {
title: 'components/Page/PageColumns',
component: PageColumns,
parameters: {
layout: 'padded',
},
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof PageColumns>;
const Box = ({ children, className }: { children: React.ReactNode; className?: string }) => (
<div
className={`p-6 bg-slate-100 dark:bg-slate-900 border-2 border-slate-300 dark:border-slate-700 rounded-xl h-full flex items-center justify-center font-bold text-center ${className}`}
>
{children}
</div>
);
/**
* A standard two-column layout: small sidebar and full main area.
*/
export const TwoColumnsSidebarLeft: Story = {
args: {
gap: 8,
children: (
<>
<PageColumn size="small">
<Box className="bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800">
Small Sidebar
</Box>
</PageColumn>
<PageColumn size="full">
<Box className="min-h-[200px]">Main Full Content</Box>
</PageColumn>
</>
),
},
};
/**
* A standard two-column layout with sidebar on the right.
*/
export const TwoColumnsSidebarRight: Story = {
args: {
gap: 8,
children: (
<>
<PageColumn size="full">
<Box className="min-h-[200px]">Main Full Content</Box>
</PageColumn>
<PageColumn size="small">
<Box className="bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800">
Small Sidebar
</Box>
</PageColumn>
</>
),
},
};
/**
* Three columns: small sidebar on both sides.
*/
export const ThreeColumns: Story = {
args: {
gap: 6,
children: (
<>
<PageColumn size="small">
<Box className="bg-blue-50 dark:bg-blue-950 border-blue-200">Left Sidebar</Box>
</PageColumn>
<PageColumn size="full">
<Box className="min-h-[200px]">Primary Content</Box>
</PageColumn>
<PageColumn size="small">
<Box className="bg-blue-50 dark:bg-blue-950 border-blue-200">Right Sidebar</Box>
</PageColumn>
</>
),
},
};
/**
* Two full-width columns sharing space equally.
*/
export const TwoFullColumns: Story = {
args: {
gap: 8,
children: (
<>
<PageColumn size="full">
<Box className="min-h-[200px]">Column A (full)</Box>
</PageColumn>
<PageColumn size="full">
<Box className="min-h-[200px]">Column B (full)</Box>
</PageColumn>
</>
),
},
};
/**
* A sidebar that sticks to the top when scrolling.
*/
export const StickySidebar: Story = {
render: (args) => (
<div className="h-[800px] overflow-y-auto border-2 border-dashed p-4 rounded-lg">
<p className="mb-4 text-center italic text-slate-500">Scroll down to see the sidebar stick</p>
<PageColumns {...args} gap={8}>
<PageColumn size="full">
<div className="flex flex-col gap-4">
{Array.from({ length: 15 }).map((_, i) => (
// eslint-disable-next-line react/no-array-index-key
<Box key={i} className="min-h-[100px]">
Main Content Block {i + 1}
</Box>
))}
</div>
</PageColumn>
<PageColumn sticky size="small">
<Box className="bg-yellow-50 dark:bg-yellow-950 border-yellow-200 min-h-[300px]">
I am a Sticky Sidebar!
</Box>
</PageColumn>
</PageColumns>
</div>
),
};
@@ -0,0 +1,31 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { describe, it, expect } from 'vitest';
import { PageColumns } from './PageColumns';
describe('PageColumns', () => {
it('renders children correctly', () => {
render(
<PageColumns>
<div data-testid="child">Test Content</div>
</PageColumns>
);
expect(screen.getByTestId('child')).toBeInTheDocument();
});
it('applies gap class', () => {
const { container: gap8 } = render(<PageColumns gap={8}>Content</PageColumns>);
expect(gap8.firstChild).toHaveClass('gap-8');
const { container: gapCustom } = render(<PageColumns gap="gap-[20px]">Content</PageColumns>);
expect(gapCustom.firstChild).toHaveClass('gap-[20px]');
});
it('has base responsive classes', () => {
const { container } = render(<PageColumns>Content</PageColumns>);
expect(container.firstChild).toHaveClass('flex');
expect(container.firstChild).toHaveClass('flex-col');
expect(container.firstChild).toHaveClass('lg:flex-row');
});
});
@@ -0,0 +1,51 @@
import { cn } from '@ermnvldmr/stl';
import React from 'react';
/**
* Props for the PageColumns component.
*/
export interface PageColumnsProps {
/** The columns to be displayed within the grid. */
children: React.ReactNode;
/**
* Spacing between columns.
* @default 6
*/
gap?: number | string;
/**
* Optional additional class names.
*/
className?: string;
}
/**
/**
* Host component for a responsive column system.
* It handles the flex/grid layout for page columns.
*
* @param props - The component props.
* @returns A PageColumns component.
* @example
* ```tsx
* <PageColumns gap={8}>
* <PageColumn size="small">Sidebar</PageColumn>
* <PageColumn size="full">Main Content</PageColumn>
* </PageColumns>
* ```
*/
export const PageColumns = ({ children, gap = 6, className }: PageColumnsProps) => {
return (
<div
className={cn(
'flex flex-col lg:flex-row w-full',
{
[`gap-${gap}`]: typeof gap === 'number',
[String(gap)]: typeof gap === 'string',
},
className
)}
>
{children}
</div>
);
};
@@ -0,0 +1,120 @@
import React from 'react';
import { PageContainer } from './PageContainer';
import type { Meta, StoryObj } from '@storybook/react';
const meta: Meta<typeof PageContainer> = {
title: 'components/Page/PageContainer',
component: PageContainer,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
argTypes: {
width: {
control: 'select',
options: ['slim', 'default', 'wide', 'full'],
},
paddingX: {
control: 'select',
options: ['none', 'small', 'medium', 'large'],
},
paddingY: {
control: 'select',
options: ['none', 'small', 'medium', 'large'],
},
centerVertically: {
control: 'boolean',
},
},
};
export default meta;
type Story = StoryObj<typeof PageContainer>;
const Box = ({ children, className }: { children: React.ReactNode; className?: string }) => (
<div
className={`p-4 bg-slate-200 dark:bg-slate-800 border-2 border-slate-400 dark:border-slate-600 rounded-lg ${className}`}
>
{children}
</div>
);
/**
* Standard container with default width and padding.
*/
export const Default: Story = {
args: {
children: <Box>Default Container Content</Box>,
width: 'default',
paddingY: 'medium',
},
};
/**
* Narrower slim container, ideal for articles.
*/
export const Slim: Story = {
args: {
children: <Box>Slim Container Content (1100px Max-Width)</Box>,
width: 'slim',
paddingY: 'medium',
},
};
/**
* Wider wide container, useful for dashboards.
*/
export const Wide: Story = {
args: {
children: <Box>Wide Container Content (1920px Max-Width)</Box>,
width: 'wide',
paddingY: 'medium',
},
};
/**
* Vertically centered container.
*/
export const VerticallyCentered: Story = {
render: (args) => (
<div className="h-[400px] border-2 border-dashed border-slate-300 dark:border-slate-700 m-4 flex flex-col">
<PageContainer {...args} centerVertically className="flex-1">
<Box className="text-center font-bold">I am vertically centered!</Box>
</PageContainer>
</div>
),
};
/**
* Demonstration of different horizontal padding scales.
*/
export const PaddingScales: Story = {
render: () => (
<div className="flex flex-col gap-8 py-8">
<div>
<h3 className="mb-2 font-bold px-4">paddingX: small</h3>
<PageContainer paddingX="small" width="full">
<Box className="bg-blue-100 dark:bg-blue-900 border-blue-300">Small Padding Content</Box>
</PageContainer>
</div>
<div>
<h3 className="mb-2 font-bold px-4">paddingX: medium (Default)</h3>
<PageContainer paddingX="medium" width="full">
<Box className="bg-green-100 dark:bg-green-900 border-green-300">
Medium Padding Content
</Box>
</PageContainer>
</div>
<div>
<h3 className="mb-2 font-bold px-4">paddingX: large</h3>
<PageContainer paddingX="large" width="full">
<Box className="bg-purple-100 dark:bg-purple-900 border-purple-300">
Large Padding Content
</Box>
</PageContainer>
</div>
</div>
),
};
@@ -0,0 +1,60 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { describe, it, expect } from 'vitest';
import { PageContainer } from './PageContainer';
describe('PageContainer', () => {
it('renders children correctly', () => {
render(
<PageContainer>
<div data-testid="child">Test Content</div>
</PageContainer>
);
expect(screen.getByTestId('child')).toBeInTheDocument();
});
it('applies correct width classes', () => {
const { container: slim } = render(<PageContainer width="slim">Content</PageContainer>);
expect(slim.firstChild).toHaveClass('max-w-[var(--rb-page-width-slim,1100px)]');
const { container: def } = render(<PageContainer width="default">Content</PageContainer>);
expect(def.firstChild).toHaveClass('max-w-[var(--rb-page-width-default,1600px)]');
const { container: wide } = render(<PageContainer width="wide">Content</PageContainer>);
expect(wide.firstChild).toHaveClass('max-w-[var(--rb-page-width-wide,1920px)]');
const { container: full } = render(<PageContainer width="full">Content</PageContainer>);
expect(full.firstChild).toHaveClass('max-w-full');
});
it('applies padding classes', () => {
const { container: small } = render(
<PageContainer paddingX="small" paddingY="small">
Content
</PageContainer>
);
expect(small.firstChild).toHaveClass('px-2');
expect(small.firstChild).toHaveClass('py-4');
const { container: large } = render(
<PageContainer paddingX="large" paddingY="large">
Content
</PageContainer>
);
expect(large.firstChild).toHaveClass('px-6');
expect(large.firstChild).toHaveClass('py-12');
});
it('handles centerVertically prop', () => {
const { container } = render(<PageContainer centerVertically>Content</PageContainer>);
expect(container.firstChild).toHaveClass('flex-1');
expect(container.firstChild).toHaveClass('flex-col');
expect(container.firstChild).toHaveClass('justify-center');
});
it('renders as different element', () => {
render(<PageContainer as="main">Content</PageContainer>);
expect(screen.getByRole('main')).toBeInTheDocument();
});
});
@@ -0,0 +1,119 @@
import { cn } from '@ermnvldmr/stl';
import React from 'react';
/**
* Supported width configurations for the PageContainer.
* - `slim`: 1100px
* - `default`: 1600px
* - `wide`: 1920px
* - `full`: 100%
*/
export type PageWidth = 'slim' | 'default' | 'wide' | 'full';
/**
* Supported padding scales for the PageContainer.
* - `none`: No padding.
* - `small`: Standard small padding.
* - `medium`: Standard medium padding.
* - `large`: Standard large padding.
*/
export type PagePadding = 'none' | 'small' | 'medium' | 'large';
/**
* Props for the PageContainer.
*/
export interface PageContainerProps {
/** The content of the container. */
children: React.ReactNode;
/**
* The maximum width of the container on desktop.
* @default 'default'
*/
width?: PageWidth;
/**
* Horizontal padding scale.
* @default 'medium'
*/
paddingX?: PagePadding;
/**
* Vertical padding scale.
* @default 'none'
*/
paddingY?: PagePadding;
/**
* If true, vertically centers the content on the page.
* Has no effect if the content is taller than the viewport.
* @default false
*/
centerVertically?: boolean;
/**
* Optional additional class names.
*/
className?: string;
/**
* The HTML element to use for the root of the component.
* @default 'div'
*/
as?: React.ElementType;
}
/**
/**
* Manages the horizontal and vertical constraints for page content.
*
* @param props - The component props.
* @returns A PageContainer component.
* @example
* ```tsx
* <PageContainer width="slim" paddingY="medium">
* Main Content
* </PageContainer>
* ```
*/
export const PageContainer = ({
children,
width = 'default',
paddingX = 'medium',
paddingY = 'none',
centerVertically = false,
className,
as: Component = 'div',
}: PageContainerProps) => {
const widthClasses = {
slim: 'max-w-[var(--rb-page-width-slim,1100px)]',
default: 'max-w-[var(--rb-page-width-default,1600px)]',
wide: 'max-w-[var(--rb-page-width-wide,1920px)]',
full: 'max-w-full',
};
const pxClasses = {
none: 'px-0',
small: 'px-2 sm:px-4',
medium: 'px-4 sm:px-6 lg:px-8',
large: 'px-6 sm:px-8 lg:px-12',
};
const pyClasses = {
none: 'py-0',
small: 'py-4 sm:py-6',
medium: 'py-8 sm:py-12',
large: 'py-12 sm:py-24',
};
return (
<Component
className={cn(
'w-full mx-auto',
widthClasses[width],
pxClasses[paddingX],
pyClasses[paddingY],
{
'flex-1 flex flex-col justify-center': centerVertically,
},
className
)}
>
{children}
</Component>
);
};
@@ -1,8 +1,8 @@
import React from 'react';
import { PageHead } from './PageHead';
import { Breadcrumbs } from '../Breadcrumbs/Breadcrumbs';
import { Button } from '../Button/Button';
import { Breadcrumbs } from '../../Breadcrumbs/Breadcrumbs';
import { Button } from '../../Button/Button';
import type { Meta, StoryObj } from '@storybook/react';
@@ -1,10 +1,11 @@
import { cn, useScroll, useResizeObserver } from '@ermnvldmr/stl';
import React, { type ReactNode, useRef, useEffect, useState } from 'react';
import { Header } from '../Header/Header';
import { HStack } from '../HStack/HStack';
import { Text } from '../Text/Text';
import { VStack } from '../VStack/VStack';
import { Header } from '../../Header/Header';
import { HStack } from '../../HStack/HStack';
import { Text } from '../../Text/Text';
import { VStack } from '../../VStack/VStack';
import { PageContainer } from '../PageContainer/PageContainer';
/**
* Defines the positioning and behavior of the PageHead component.
@@ -28,7 +29,7 @@ export interface PageHeadProps {
/** The main heading text. */
heading: string;
/** The caption text displayed below the heading. */
caption?: string;
caption?: ReactNode;
/** The breadcrumbs navigation component. */
breadcrumbs?: ReactNode;
/** A component to be displayed on the left side of the header. */
@@ -77,55 +78,69 @@ export const PageHead = ({
}, [scrollY, strategy]);
const rootClasses = cn(
'page-head w-full transition-all duration-200 bg-[var(--rb-background)] fixed top-0 z-10',
'page-head w-full transition-all duration-200 bg-[var(--rb-background)] fixed top-0 z-10 border-b border-[var(--rb-outline)]/10',
{
'px-6 py-4': !isCollapsed,
'px-4 py-2': isCollapsed,
'shadow-md': isCollapsed,
'shadow-md bg-[var(--rb-background)]/90 backdrop-blur-md': isCollapsed,
},
className
);
const expandedContent = (
<PageContainer paddingY="medium">
<VStack align="stretch" gap={6}>
<HStack align="center" justify="between">
<HStack align="center" gap={4}>
{addonLeft && <div>{addonLeft}</div>}
{breadcrumbs && <div className="hidden sm:block">{breadcrumbs}</div>}
</HStack>
{addonRight && <div>{addonRight}</div>}
</HStack>
<VStack gap={2}>
<Header level={1}>{heading}</Header>
{typeof caption === 'string' ? (
<Text color="muted" size="l" type="body">
{caption}
</Text>
) : (
caption
)}
</VStack>
</VStack>
</PageContainer>
);
const collapsedContent = (
<PageContainer paddingY="none">
<HStack align="center" className="h-14" justify="between">
<HStack align="center" className="min-w-0 flex-1" gap={4}>
{addonLeft && <div className="flex-shrink-0">{addonLeft}</div>}
<Header level={4} overflow="ellipsis">
{heading}
</Header>
</HStack>
{addonRight && <div className="flex-shrink-0 ml-4">{addonRight}</div>}
</HStack>
</PageContainer>
);
return (
<>
<header ref={headerRef} className={rootClasses}>
<div
className={cn('transition-opacity duration-200', {
'opacity-0 hidden': isCollapsed,
'opacity-100': !isCollapsed,
className={cn('transition-all duration-300', {
'opacity-0 invisible h-0 overflow-hidden': isCollapsed,
'opacity-100 visible': !isCollapsed,
})}
>
<VStack align="stretch" gap={2}>
<HStack align="center" justify="between">
<HStack align="center" gap={4}>
{addonLeft && <div>{addonLeft}</div>}
{breadcrumbs && <div>{breadcrumbs}</div>}
</HStack>
{addonRight && <div>{addonRight}</div>}
</HStack>
<VStack gap={2}>
<Header level={1}>{heading}</Header>
{caption && (
<Text color="muted" size="l" type="body">
{caption}
</Text>
)}
</VStack>
</VStack>
{expandedContent}
</div>
<div
className={cn('transition-opacity duration-200', {
'opacity-100': isCollapsed,
'opacity-0 hidden': !isCollapsed,
className={cn('transition-all duration-300', {
'opacity-100 visible': isCollapsed,
'opacity-0 invisible h-0 overflow-hidden': !isCollapsed,
})}
>
<HStack align="center" justify="between">
<HStack align="center" gap={2}>
{addonLeft && <div>{addonLeft}</div>}
<Header level={3}>{heading}</Header>
</HStack>
{addonRight && <div>{addonRight}</div>}
</HStack>
{collapsedContent}
</div>
</header>
<div style={{ height }} />
@@ -0,0 +1,51 @@
import React from 'react';
import { PageRoot } from './PageRoot';
import type { Meta, StoryObj } from '@storybook/react';
const meta: Meta<typeof PageRoot> = {
title: 'components/Page/PageRoot',
component: PageRoot,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof PageRoot>;
/**
* The default PageRoot story showing the base technical container.
*/
export const Default: Story = {
args: {
children: (
<div className="p-8">
<h1 className="text-2xl font-bold">Page Root Content</h1>
<p className="mt-4">
This is the base technical container that handles theme initialization and core
background/text colors.
</p>
</div>
),
},
};
/**
* PageRoot with custom background classes.
*/
export const CustomStyling: Story = {
args: {
className: 'bg-slate-100 dark:bg-slate-900',
children: (
<div className="p-8">
<h1 className="text-2xl font-bold">Custom Styled Root</h1>
<p className="mt-4 text-slate-600 dark:text-slate-400">
PageRoot accepts a className prop to override or extend base styles.
</p>
</div>
),
},
};
@@ -0,0 +1,35 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { describe, it, expect } from 'vitest';
import { PageRoot } from './PageRoot';
describe('PageRoot', () => {
it('renders children correctly', () => {
render(
<PageRoot>
<div data-testid="child">Test Content</div>
</PageRoot>
);
expect(screen.getByTestId('child')).toBeInTheDocument();
expect(screen.getByText('Test Content')).toBeInTheDocument();
});
it('applies custom className', () => {
const { container } = render(
<PageRoot className="custom-class">
<div>Content</div>
</PageRoot>
);
expect(container.firstChild).toHaveClass('custom-class');
});
it('has base technical classes', () => {
const { container } = render(
<PageRoot>
<div>Content</div>
</PageRoot>
);
expect(container.firstChild).toHaveClass('w-full');
});
});
@@ -0,0 +1,34 @@
import { cn } from '@ermnvldmr/stl';
import React from 'react';
import { useTheme } from '../../../lib/theme';
/**
* Props for the PageRoot component.
*/
export interface PageRootProps {
/** The main content slot. */
children: React.ReactNode;
/** Additional CSS classes for the root container. */
className?: string;
}
/**
/**
* The technical bootstrap layer for all layouts.
* It handles theme initialization and base background/text colors.
*
* @param props - The component props.
* @returns A PageRoot component.
* @example
* ```tsx
* <PageRoot>
* <PageContainer>Content</PageContainer>
* </PageRoot>
* ```
*/
export const PageRoot = ({ children, className }: PageRootProps) => {
useTheme();
return <div className={cn('w-full transition-colors duration-200', className)}>{children}</div>;
};
+5
View File
@@ -0,0 +1,5 @@
export * from './PageRoot/PageRoot';
export * from './PageContainer/PageContainer';
export * from './PageColumns/PageColumns';
export * from './PageColumn/PageColumn';
export * from './PageHead/PageHead';
+1 -1
View File
@@ -11,7 +11,7 @@ export * from './Link/Link';
export * from './List/List';
export * from './Markdown/Markdown';
export * from './Paragraph/Paragraph';
export * from './PageHead/PageHead';
export * from './Page/index';
export * from './Separator/Separator';
export * from './Stack/Stack';
export * from './Stub/Stub';