diff --git a/packages/design-system/src/components/Button/Button.stories.tsx b/packages/design-system/src/components/Button/Button.stories.tsx new file mode 100644 index 0000000..a876afa --- /dev/null +++ b/packages/design-system/src/components/Button/Button.stories.tsx @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from './Button'; + +const meta: Meta = { + title: 'Actions/Button', + component: Button, + argTypes: { + variant: { + control: 'select', + options: ['primary', 'secondary', 'tertiary', 'danger'], + }, + size: { + control: 'select', + options: ['small', 'medium'], + }, + loading: { + control: 'boolean', + }, + disabled: { + control: 'boolean', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + variant: 'primary', + children: 'Купить', + }, +}; + +export const Secondary: Story = { + args: { + variant: 'secondary', + children: 'Отмена', + }, +}; + +export const Tertiary: Story = { + args: { + variant: 'tertiary', + children: 'Подробнее', + }, +}; + +export const Danger: Story = { + args: { + variant: 'danger', + children: 'Удалить портфель', + }, +}; + +export const Small: Story = { + args: { + size: 'small', + children: 'Применить', + }, +}; + +export const Loading: Story = { + args: { + loading: true, + children: 'Отправка...', + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + children: 'Недоступно', + }, +}; + +export const AllVariants: Story = { + render: () => ( +
+ + + + +
+ ), +}; + +export const AllSizes: Story = { + render: () => ( +
+ + +
+ ), +}; + +export const AllStates: Story = { + render: () => ( +
+ + + +
+ ), +}; diff --git a/packages/design-system/src/components/Button/Button.test.tsx b/packages/design-system/src/components/Button/Button.test.tsx new file mode 100644 index 0000000..6103dd0 --- /dev/null +++ b/packages/design-system/src/components/Button/Button.test.tsx @@ -0,0 +1,105 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Button } from './Button'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Button', () => { + it('renders children', () => { + renderWithTheme(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('renders as a button element by default', () => { + renderWithTheme(); + expect(screen.getByRole('button', { name: /click/i })).toBeInTheDocument(); + }); + + it('renders primary variant by default', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-contained')).toBe(true); + }); + + it('renders secondary variant', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-outlined')).toBe(true); + }); + + it('renders tertiary variant', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-text')).toBe(true); + }); + + it('renders danger variant with contained style', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-contained')).toBe(true); + }); + + it('does not pass color prop to DOM', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn).not.toHaveAttribute('color'); + }); + + it('accepts className and style props', () => { + renderWithTheme( + , + ); + const btn = screen.getByText('Click'); + expect(btn.classList.contains('custom')).toBe(true); + }); + + it('calls onClick when clicked', async () => { + const handleClick = vi.fn(); + const user = userEvent.setup(); + renderWithTheme(); + await user.click(screen.getByRole('button')); + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + it('shows aria-busy when loading', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn).toHaveAttribute('aria-busy', 'true'); + }); + + it('disables button when loading', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn).toBeDisabled(); + }); + + it('does not call onClick when loading', () => { + const handleClick = vi.fn(); + const { container } = renderWithTheme( + , + ); + const btn = container.querySelector('button')!; + btn.click(); + expect(handleClick).not.toHaveBeenCalled(); + }); + + it('renders small size', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-sizeSmall')).toBe(true); + }); + + it('renders medium size by default', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-sizeMedium')).toBe(true); + }); +}); diff --git a/packages/design-system/src/components/Button/Button.tsx b/packages/design-system/src/components/Button/Button.tsx new file mode 100644 index 0000000..788c08f --- /dev/null +++ b/packages/design-system/src/components/Button/Button.tsx @@ -0,0 +1,48 @@ +import { + Button as MuiButton, + CircularProgress, + type ButtonProps as MuiButtonProps, +} from '@mui/material'; +import type { ReactNode } from 'react'; + +type ActionVariant = 'primary' | 'secondary' | 'tertiary' | 'danger'; + +export interface ButtonProps extends Omit { + variant?: ActionVariant; + size?: 'small' | 'medium'; + loading?: boolean; + children: ReactNode; +} + +const VARIANT_MAP: Record = { + primary: 'contained', + secondary: 'outlined', + tertiary: 'text', + danger: 'contained', +}; + +export function Button({ + variant = 'primary', + size = 'medium', + loading = false, + disabled, + children, + ...props +}: ButtonProps) { + const muiVariant = VARIANT_MAP[variant]; + const muiColor = variant === 'danger' ? 'error' : undefined; + + return ( + + {loading && } + {children} + + ); +} diff --git a/packages/design-system/src/components/Button/index.ts b/packages/design-system/src/components/Button/index.ts new file mode 100644 index 0000000..fa3c8a5 --- /dev/null +++ b/packages/design-system/src/components/Button/index.ts @@ -0,0 +1,2 @@ +export { Button } from './Button'; +export type { ButtonProps } from './Button'; diff --git a/packages/design-system/src/components/Heading/Heading.stories.tsx b/packages/design-system/src/components/Heading/Heading.stories.tsx new file mode 100644 index 0000000..b75a6a2 --- /dev/null +++ b/packages/design-system/src/components/Heading/Heading.stories.tsx @@ -0,0 +1,92 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Heading } from './Heading'; + +const meta: Meta = { + title: 'Typography/Heading', + component: Heading, + argTypes: { + level: { + control: 'select', + options: [1, 2, 3, 4, 5, 6], + }, + size: { + control: 'select', + options: ['display', 'title', 'section', 'subsection'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Level1: Story = { + args: { + level: 1, + children: 'Обзор рынка', + }, +}; + +export const Level2: Story = { + args: { + level: 2, + children: 'Акции', + }, +}; + +export const Level3: Story = { + args: { + level: 3, + children: 'Голубые фишки', + }, +}; + +export const DisplaySize: Story = { + args: { + level: 1, + size: 'display', + children: 'Московская Биржа', + }, +}; + +export const TitleSize: Story = { + args: { + level: 2, + size: 'title', + children: 'Индекс Мосбиржи обновил максимум', + }, +}; + +export const SectionSize: Story = { + args: { + level: 3, + size: 'section', + children: 'Нефтегазовый сектор', + }, +}; + +export const SubsectionSize: Story = { + args: { + level: 4, + size: 'subsection', + children: 'Лукойл', + }, +}; + +export const AllLevels: Story = { + render: () => ( +
+ + H1 — Заголовок страницы + + + H2 — Раздел + + + H3 — Подраздел + + H4 — Группа + H5 — Элемент + H6 — Мелкий заголовок +
+ ), +}; diff --git a/packages/design-system/src/components/Heading/Heading.test.tsx b/packages/design-system/src/components/Heading/Heading.test.tsx new file mode 100644 index 0000000..cb98adb --- /dev/null +++ b/packages/design-system/src/components/Heading/Heading.test.tsx @@ -0,0 +1,55 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Heading } from './Heading'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Heading', () => { + it('renders children', () => { + renderWithTheme(Title); + expect(screen.getByText('Title')).toBeInTheDocument(); + }); + + it('renders h1 for level 1', () => { + renderWithTheme(H1); + expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); + }); + + it('renders h2 for level 2', () => { + renderWithTheme(H2); + expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument(); + }); + + it('renders h3 for level 3', () => { + renderWithTheme(H3); + expect(screen.getByRole('heading', { level: 3 })).toBeInTheDocument(); + }); + + it('renders h4 for level 4', () => { + renderWithTheme(H4); + expect(screen.getByRole('heading', { level: 4 })).toBeInTheDocument(); + }); + + it('renders h5 for level 5', () => { + renderWithTheme(H5); + expect(screen.getByRole('heading', { level: 5 })).toBeInTheDocument(); + }); + + it('renders h6 for level 6', () => { + renderWithTheme(H6); + expect(screen.getByRole('heading', { level: 6 })).toBeInTheDocument(); + }); + + it('accepts className and style props', () => { + renderWithTheme( + + Hello + , + ); + const el = screen.getByText('Hello'); + expect(el.classList.contains('custom')).toBe(true); + }); +}); diff --git a/packages/design-system/src/components/Heading/Heading.tsx b/packages/design-system/src/components/Heading/Heading.tsx new file mode 100644 index 0000000..3b7664e --- /dev/null +++ b/packages/design-system/src/components/Heading/Heading.tsx @@ -0,0 +1,25 @@ +import { Typography, type TypographyProps } from '@mui/material'; +import type { ReactNode } from 'react'; + +type Size = 'display' | 'title' | 'section' | 'subsection'; + +export interface HeadingProps extends Omit { + level: 1 | 2 | 3 | 4 | 5 | 6; + size?: Size; + children: ReactNode; +} + +const SIZE_VARIANT: Record = { + display: 'h1', + title: 'h2', + section: 'h3', + subsection: 'h4', +}; + +export function Heading({ level, size = 'title', children, ...props }: HeadingProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/Heading/index.ts b/packages/design-system/src/components/Heading/index.ts new file mode 100644 index 0000000..f617246 --- /dev/null +++ b/packages/design-system/src/components/Heading/index.ts @@ -0,0 +1,2 @@ +export { Heading } from './Heading'; +export type { HeadingProps } from './Heading'; diff --git a/packages/design-system/src/components/IconButton/IconButton.stories.tsx b/packages/design-system/src/components/IconButton/IconButton.stories.tsx new file mode 100644 index 0000000..6671912 --- /dev/null +++ b/packages/design-system/src/components/IconButton/IconButton.stories.tsx @@ -0,0 +1,88 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { IconButton } from './IconButton'; +import DeleteIcon from '@mui/icons-material/Delete'; +import SettingsIcon from '@mui/icons-material/Settings'; +import SearchIcon from '@mui/icons-material/Search'; +import CloseIcon from '@mui/icons-material/Close'; + +const meta: Meta = { + title: 'Actions/IconButton', + component: IconButton, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Search: Story = { + args: { + label: 'Поиск', + children: , + }, +}; + +export const Close: Story = { + args: { + label: 'Закрыть', + children: , + }, +}; + +export const Delete: Story = { + args: { + label: 'Удалить', + children: , + }, +}; + +export const Settings: Story = { + args: { + label: 'Настройки', + children: , + }, +}; + +export const Small: Story = { + args: { + size: 'small', + label: 'Поиск', + children: , + }, +}; + +export const AllSizes: Story = { + render: () => ( +
+ + + + + + +
+ ), +}; + +export const AllExamples: Story = { + render: () => ( +
+ + + + + + + + + + + + +
+ ), +}; diff --git a/packages/design-system/src/components/IconButton/IconButton.test.tsx b/packages/design-system/src/components/IconButton/IconButton.test.tsx new file mode 100644 index 0000000..45df86e --- /dev/null +++ b/packages/design-system/src/components/IconButton/IconButton.test.tsx @@ -0,0 +1,68 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { IconButton } from './IconButton'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('IconButton', () => { + it('renders with aria-label', () => { + renderWithTheme( + + X + , + ); + expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); + }); + + it('renders children', () => { + renderWithTheme( + + + , + ); + expect(screen.getByText('☰')).toBeInTheDocument(); + }); + + it('does not pass color prop to DOM', () => { + renderWithTheme( + + 🔍 + , + ); + const btn = screen.getByRole('button'); + expect(btn).not.toHaveAttribute('color'); + }); + + it('accepts className and style props', () => { + renderWithTheme( + + + , + ); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('custom')).toBe(true); + }); + + it('renders small size', () => { + renderWithTheme( + + S + , + ); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiIconButton-sizeSmall')).toBe(true); + }); + + it('renders medium size by default', () => { + renderWithTheme( + + M + , + ); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiIconButton-sizeMedium')).toBe(true); + }); +}); diff --git a/packages/design-system/src/components/IconButton/IconButton.tsx b/packages/design-system/src/components/IconButton/IconButton.tsx new file mode 100644 index 0000000..d1e2bca --- /dev/null +++ b/packages/design-system/src/components/IconButton/IconButton.tsx @@ -0,0 +1,22 @@ +import { + IconButton as MuiIconButton, + type IconButtonProps as MuiIconButtonProps, +} from '@mui/material'; +import type { ReactNode } from 'react'; + +export interface IconButtonProps extends Omit< + MuiIconButtonProps, + 'color' | 'variant' | 'sx' | 'aria-label' +> { + label: string; + size?: 'small' | 'medium'; + children: ReactNode; +} + +export function IconButton({ label, size = 'medium', children, ...props }: IconButtonProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/IconButton/index.ts b/packages/design-system/src/components/IconButton/index.ts new file mode 100644 index 0000000..5f509c9 --- /dev/null +++ b/packages/design-system/src/components/IconButton/index.ts @@ -0,0 +1,2 @@ +export { IconButton } from './IconButton'; +export type { IconButtonProps } from './IconButton'; diff --git a/packages/design-system/src/components/Link/Link.stories.tsx b/packages/design-system/src/components/Link/Link.stories.tsx new file mode 100644 index 0000000..a41b7f7 --- /dev/null +++ b/packages/design-system/src/components/Link/Link.stories.tsx @@ -0,0 +1,70 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Link } from './Link'; + +const meta: Meta = { + title: 'Navigation/Link', + component: Link, + argTypes: { + tone: { + control: 'select', + options: ['default', 'secondary', 'positive', 'negative', 'muted'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + href: '#', + children: 'Перейти к котировкам', + }, +}; + +export const External: Story = { + args: { + href: 'https://www.moex.com', + target: '_blank', + rel: 'noopener noreferrer', + children: 'Московская Биржа', + }, +}; + +export const Positive: Story = { + args: { + href: '#', + tone: 'positive', + children: 'Рост акций', + }, +}; + +export const Negative: Story = { + args: { + href: '#', + tone: 'negative', + children: 'Падение индекса', + }, +}; + +export const AllTones: Story = { + render: () => ( +
+ + Основная ссылка + + + Второстепенная ссылка + + + Рост +5.2% + + + Падение -2.1% + + + Неактивная ссылка + +
+ ), +}; diff --git a/packages/design-system/src/components/Link/Link.test.tsx b/packages/design-system/src/components/Link/Link.test.tsx new file mode 100644 index 0000000..507cae8 --- /dev/null +++ b/packages/design-system/src/components/Link/Link.test.tsx @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Link } from './Link'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Link', () => { + it('renders children', () => { + renderWithTheme(Click); + expect(screen.getByText('Click')).toBeInTheDocument(); + }); + + it('renders as an anchor with href', () => { + renderWithTheme(Click); + const el = screen.getByText('Click'); + expect(el.tagName).toBe('A'); + expect(el).toHaveAttribute('href', '/test'); + }); + + it('does not pass color prop to DOM', () => { + renderWithTheme( + + Click + , + ); + const el = screen.getByText('Click'); + expect(el).not.toHaveAttribute('color'); + }); + + it('accepts className and style props', () => { + renderWithTheme( + + Click + , + ); + const el = screen.getByText('Click'); + expect(el.classList.contains('custom')).toBe(true); + }); + + it('renders with target and rel for external links', () => { + renderWithTheme( + + External + , + ); + const el = screen.getByText('External'); + expect(el).toHaveAttribute('target', '_blank'); + expect(el).toHaveAttribute('rel', 'noopener'); + }); +}); diff --git a/packages/design-system/src/components/Link/Link.tsx b/packages/design-system/src/components/Link/Link.tsx new file mode 100644 index 0000000..d784605 --- /dev/null +++ b/packages/design-system/src/components/Link/Link.tsx @@ -0,0 +1,25 @@ +import { Link as MuiLink, type LinkProps as MuiLinkProps } from '@mui/material'; +import type { ReactNode } from 'react'; + +type Tone = 'default' | 'secondary' | 'positive' | 'negative' | 'muted'; + +export interface LinkProps extends Omit { + tone?: Tone; + children: ReactNode; +} + +const TONE_MAP: Record = { + default: 'primary', + secondary: 'text.secondary', + positive: 'success.main', + negative: 'error.main', + muted: 'text.disabled', +}; + +export function Link({ tone = 'default', children, ...props }: LinkProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/Link/index.ts b/packages/design-system/src/components/Link/index.ts new file mode 100644 index 0000000..ed48706 --- /dev/null +++ b/packages/design-system/src/components/Link/index.ts @@ -0,0 +1,2 @@ +export { Link } from './Link'; +export type { LinkProps } from './Link'; diff --git a/packages/design-system/src/components/Text/Text.stories.tsx b/packages/design-system/src/components/Text/Text.stories.tsx new file mode 100644 index 0000000..d08e553 --- /dev/null +++ b/packages/design-system/src/components/Text/Text.stories.tsx @@ -0,0 +1,99 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Text } from './Text'; + +const meta: Meta = { + title: 'Typography/Text', + component: Text, + argTypes: { + variant: { + control: 'select', + options: ['body', 'caption', 'label', 'numeric'], + }, + tone: { + control: 'select', + options: ['default', 'secondary', 'positive', 'negative', 'muted'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Body: Story = { + args: { + children: + 'Акции Московской биржи выросли на 2.3% по итогам торговой сессии. Индекс Мосбиржи обновил исторический максимум, достигнув отметки 4500 пунктов.', + }, +}; + +export const Caption: Story = { + args: { + variant: 'caption', + children: 'Данные предоставлены Московской биржей. Цены указаны в рублях.', + }, +}; + +export const Label: Story = { + args: { + variant: 'label', + children: 'Объём торгов', + }, +}; + +export const Numeric: Story = { + args: { + variant: 'numeric', + children: '1 234 567.89 ₽', + }, +}; + +export const PositiveTone: Story = { + args: { + tone: 'positive', + children: '+5.23%', + }, +}; + +export const NegativeTone: Story = { + args: { + tone: 'negative', + children: '-2.15%', + }, +}; + +export const SecondaryTone: Story = { + args: { + tone: 'secondary', + children: 'Дополнительная информация о ценной бумаге', + }, +}; + +export const MutedTone: Story = { + args: { + tone: 'muted', + children: 'Обновлено 5 минут назад', + }, +}; + +export const AllVariants: Story = { + render: () => ( +
+ Body: основной текст страницы + Caption: вспомогательный текст + Label: подпись к полю или метрике + Numeric: 1 234.56 +
+ ), +}; + +export const AllTones: Story = { + render: () => ( +
+ Default — основной текст + Secondary — второстепенный текст + Positive — рост, прибыль + Negative — падение, убыток + Muted — неактивный, подсказка +
+ ), +}; diff --git a/packages/design-system/src/components/Text/Text.test.tsx b/packages/design-system/src/components/Text/Text.test.tsx new file mode 100644 index 0000000..5951612 --- /dev/null +++ b/packages/design-system/src/components/Text/Text.test.tsx @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Text } from './Text'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Text', () => { + it('renders children', () => { + renderWithTheme(Hello); + expect(screen.getByText('Hello')).toBeInTheDocument(); + }); + + it('renders with body variant by default', () => { + renderWithTheme(Body); + const el = screen.getByText('Body'); + expect(el.tagName).toBe('P'); + }); + + it('renders caption variant as MUI caption', () => { + renderWithTheme(Caption); + const el = screen.getByText('Caption'); + expect(el.classList.contains('MuiTypography-caption')).toBe(true); + }); + + it('renders label variant', () => { + renderWithTheme(Label); + expect(screen.getByText('Label')).toBeInTheDocument(); + }); + + it('renders numeric variant', () => { + renderWithTheme(123); + expect(screen.getByText('123')).toBeInTheDocument(); + }); + + it('applies default tone by default', () => { + renderWithTheme(Default); + const el = screen.getByText('Default'); + expect(el.classList.contains('MuiTypography-body1')).toBe(true); + }); + + it('accepts className and style props', () => { + renderWithTheme( + + Hello + , + ); + const el = screen.getByText('Hello'); + expect(el.classList.contains('custom')).toBe(true); + }); +}); diff --git a/packages/design-system/src/components/Text/Text.tsx b/packages/design-system/src/components/Text/Text.tsx new file mode 100644 index 0000000..ded9fee --- /dev/null +++ b/packages/design-system/src/components/Text/Text.tsx @@ -0,0 +1,35 @@ +import { Typography, type TypographyProps } from '@mui/material'; +import type { ReactNode } from 'react'; + +type Tone = 'default' | 'secondary' | 'positive' | 'negative' | 'muted'; + +type TextVariant = 'body' | 'caption' | 'label' | 'numeric'; + +export interface TextProps extends Omit { + variant?: TextVariant; + tone?: Tone; + children: ReactNode; +} + +const VARIANT_MAP: Record = { + body: 'body1', + caption: 'caption', + label: 'subtitle2', + numeric: 'body1', +}; + +const TONE_MAP: Record = { + default: 'text.primary', + secondary: 'text.secondary', + positive: 'success.main', + negative: 'error.main', + muted: 'text.disabled', +}; + +export function Text({ variant = 'body', tone = 'default', children, ...props }: TextProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/Text/index.ts b/packages/design-system/src/components/Text/index.ts new file mode 100644 index 0000000..3e79f02 --- /dev/null +++ b/packages/design-system/src/components/Text/index.ts @@ -0,0 +1,2 @@ +export { Text } from './Text'; +export type { TextProps } from './Text'; diff --git a/packages/design-system/src/components/index.ts b/packages/design-system/src/components/index.ts new file mode 100644 index 0000000..7f583ee --- /dev/null +++ b/packages/design-system/src/components/index.ts @@ -0,0 +1,14 @@ +export { Text } from './Text'; +export type { TextProps } from './Text'; + +export { Heading } from './Heading'; +export type { HeadingProps } from './Heading'; + +export { Link } from './Link'; +export type { LinkProps } from './Link'; + +export { Button } from './Button'; +export type { ButtonProps } from './Button'; + +export { IconButton } from './IconButton'; +export type { IconButtonProps } from './IconButton'; diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts new file mode 100644 index 0000000..07635cb --- /dev/null +++ b/packages/design-system/src/index.ts @@ -0,0 +1 @@ +export * from './components';