Component Library¶
The ui-component-library provides shared React components following the Atomic
Design pattern.
Overview¶
The component library is organized hierarchically:
graph TD
A[Atoms<br/>Button, Input, Icon] --> B[Molecules<br/>SearchBar, FormField]
B --> C[Organisms<br/>Header, Card, Form]
C --> D[Templates<br/>PageLayout]
D --> E[Pages<br/>App-specific]
style A fill:#e1f5ff
style B fill:#b3e5fc
style C fill:#81d4fa
style D fill:#4fc3f7
style E fill:#29b6f6
Installation¶
Components are already available in the monorepo:
Available Components¶
Atoms¶
Basic building blocks that can't be broken down further.
Button¶
import { Button } from '@ui-component-library';
export function Example() {
return (
<>
<Button variant="contained">Primary</Button>
<Button variant="outlined">Secondary</Button>
<Button variant="text">Text</Button>
</>
);
}
Props:
variant: 'contained' | 'outlined' | 'text'color: 'primary' | 'secondary'size: 'small' | 'medium' | 'large'disabled: booleanonClick: () => void
Input¶
import { Input } from '@ui-component-library';
export function Example() {
return (
<Input
label="Email"
type="email"
placeholder="user@example.com"
required
/>
);
}
Molecules¶
Simple combinations of atoms.
SearchBar¶
import { SearchBar } from '@ui-component-library';
export function Example() {
const handleSearch = (query: string) => {
console.log('Searching for:', query);
};
return <SearchBar onSearch={handleSearch} placeholder="Search..." />;
}
Organisms¶
Complex components composed of molecules and atoms.
!!! info "Coming Soon" Organism components are being developed. Check Storybook for the latest additions.
Viewing in Storybook¶
All components are documented in Storybook:
Visit http://localhost:4400 to:
- Browse all components
- View component props
- Test component variants
- Check accessibility
- Copy code examples
Creating New Components¶
1. Create Component Directory¶
2. Create Component Files¶
import { FC } from 'react';
export interface AvatarProps {
src?: string;
alt: string;
size?: 'small' | 'medium' | 'large';
}
export const Avatar: FC<AvatarProps> = ({ src, alt, size = 'medium' }) => {
return (
<img
src={src || '/default-avatar.png'}
alt={alt}
className={`avatar avatar-${size}`}
/>
);
};
3. Create Test¶
import { render, screen } from '@testing-library/react';
import { Avatar } from './Avatar';
describe('Avatar', () => {
it('renders with alt text', () => {
render(<Avatar alt="User avatar" />);
expect(screen.getByAltText('User avatar')).toBeInTheDocument();
});
it('uses provided src', () => {
render(<Avatar src="/user.jpg" alt="User" />);
const img = screen.getByAltText('User') as HTMLImageElement;
expect(img.src).toContain('/user.jpg');
});
it('applies size className', () => {
render(<Avatar alt="User" size="large" />);
const img = screen.getByAltText('User');
expect(img).toHaveClass('avatar-large');
});
});
4. Create Story¶
import type { Meta, StoryObj } from '@storybook/react';
import { Avatar } from './Avatar';
const meta: Meta<typeof Avatar> = {
title: 'Atoms/Avatar',
component: Avatar,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
size: {
control: 'select',
options: ['small', 'medium', 'large'],
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
alt: 'User avatar',
size: 'medium',
},
};
export const Small: Story = {
args: {
alt: 'User avatar',
size: 'small',
},
};
export const Large: Story = {
args: {
alt: 'User avatar',
size: 'large',
},
};
export const WithImage: Story = {
args: {
src: 'https://via.placeholder.com/150',
alt: 'User avatar',
size: 'medium',
},
};
5. Export Component¶
Add to libs/ui-component-library/src/index.ts:
6. Test and Document¶
# Run tests
npx nx test ui-component-library
# View in Storybook
npx nx storybook ui-component-library
# Lint
npx nx lint ui-component-library
Component Guidelines¶
Naming Conventions¶
- PascalCase for component names
- camelCase for props
- Descriptive names (e.g.,
UserProfileCard, notUPC)
Props Interface¶
Always export props interface:
export interface ButtonProps {
variant?: 'contained' | 'outlined' | 'text';
children: React.ReactNode;
onClick?: () => void;
}
export const Button: FC<ButtonProps> = ({
variant = 'contained',
children,
onClick,
}) => {
// ...
};
Default Props¶
Use default parameters:
// ✅ Good
export const Button: FC<ButtonProps> = ({
variant = 'contained',
size = 'medium',
}) => {
// ...
};
// ❌ Avoid (React 18.3+ deprecated defaultProps)
Button.defaultProps = {
variant: 'contained',
size: 'medium',
};
Accessibility¶
Ensure components are accessible:
export const Button: FC<ButtonProps> = ({ children, disabled, ...props }) => {
return (
<button
{...props}
disabled={disabled}
aria-disabled={disabled}
role="button"
>
{children}
</button>
);
};
TypeScript Strict Mode¶
All components must work with strict TypeScript:
// ✅ Good - explicit types
export const Avatar: FC<AvatarProps> = ({ src, alt }) => {
return <img src={src} alt={alt} />;
};
// ❌ Bad - implicit any
export const Avatar = ({ src, alt }: any) => {
return <img src={src} alt={alt} />;
};
Styled Components¶
Use Emotion for styling:
import styled from '@emotion/styled';
import { ButtonProps } from './Button';
export const StyledButton = styled.button<ButtonProps>`
padding: ${({ size }) => {
switch (size) {
case 'small':
return '4px 8px';
case 'large':
return '12px 24px';
default:
return '8px 16px';
}
}};
background-color: ${({ variant, theme }) =>
variant === 'contained' ? theme.palette.primary.main : 'transparent'};
&:hover {
opacity: 0.8;
}
`;
Theme Integration¶
Components should use the theme:
import { useTheme } from '@util-global-theme';
export const ThemedButton: FC<ButtonProps> = ({ children }) => {
const theme = useTheme();
return (
<button style={{ color: theme.palette.primary.main }}>
{children}
</button>
);
};
Testing Components¶
See Testing Guide for comprehensive testing strategies.
Next Steps¶
- Theme System - Working with MUI themes
- Storybook Documentation - Official Storybook docs
- Material UI - MUI component library reference