| name | react-component-builder |
| description | Build production-ready React components with TypeScript, tests, and documentation using modern best practices |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash |
React Component Builder
Build complete React components with TypeScript, tests, Storybook stories, and documentation.
When to Use
- Creating new React components
- Building component libraries
- Developing reusable UI elements
- Implementing design systems
Component Structure
components/
ComponentName/
├── index.ts # Barrel export
├── ComponentName.tsx # Main component
├── ComponentName.test.tsx # Unit tests
├── ComponentName.stories.tsx # Storybook stories
├── ComponentName.styles.ts # Styled-components or CSS modules
├── types.ts # TypeScript interfaces
└── README.md # Component documentation
Workflow
1. Define Component Requirements
// Gather requirements:
// - What props does it need?
// - What state will it manage?
// - What events will it emit?
// - What styles are needed?
// - What variants exist?
2. Create TypeScript Interfaces
// types.ts
export interface ComponentNameProps {
/** Primary content to display */
children: React.ReactNode;
/** Component variant */
variant?: 'primary' | 'secondary' | 'outline';
/** Size of the component */
size?: 'sm' | 'md' | 'lg';
/** Whether component is disabled */
disabled?: boolean;
/** Click handler */
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
/** Additional CSS class */
className?: string;
/** Test ID for testing */
testId?: string;
}
3. Build Component
// ComponentName.tsx
import React from 'react';
import { ComponentNameProps } from './types';
import * as S from './ComponentName.styles';
export const ComponentName: React.FC<ComponentNameProps> = ({
children,
variant = 'primary',
size = 'md',
disabled = false,
onClick,
className,
testId = 'component-name',
}) => {
// State management
const [isActive, setIsActive] = React.useState(false);
// Event handlers
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
if (disabled) return;
setIsActive(!isActive);
onClick?.(event);
};
// Render
return (
<S.Container
variant={variant}
size={size}
disabled={disabled}
isActive={isActive}
onClick={handleClick}
className={className}
data-testid={testId}
>
{children}
</S.Container>
);
};
ComponentName.displayName = 'ComponentName';
4. Add Styling
Option A: Styled-Components
// ComponentName.styles.ts
import styled, { css } from 'styled-components';
interface ContainerProps {
variant: 'primary' | 'secondary' | 'outline';
size: 'sm' | 'md' | 'lg';
disabled: boolean;
isActive: boolean;
}
const sizeStyles = {
sm: css`
padding: 0.5rem 1rem;
font-size: 0.875rem;
`,
md: css`
padding: 0.75rem 1.5rem;
font-size: 1rem;
`,
lg: css`
padding: 1rem 2rem;
font-size: 1.125rem;
`,
};
const variantStyles = {
primary: css`
background: #0066cc;
color: white;
border: 2px solid #0066cc;
&:hover:not(:disabled) {
background: #0052a3;
}
`,
secondary: css`
background: #6c757d;
color: white;
border: 2px solid #6c757d;
&:hover:not(:disabled) {
background: #5a6268;
}
`,
outline: css`
background: transparent;
color: #0066cc;
border: 2px solid #0066cc;
&:hover:not(:disabled) {
background: #0066cc;
color: white;
}
`,
};
export const Container = styled.button<ContainerProps>`
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 0.375rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
${({ size }) => sizeStyles[size]}
${({ variant }) => variantStyles[variant]}
${({ disabled }) =>
disabled &&
css`
opacity: 0.5;
cursor: not-allowed;
`}
${({ isActive }) =>
isActive &&
css`
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.3);
`}
`;
Option B: Tailwind CSS
// ComponentName.tsx
import clsx from 'clsx';
const sizeClasses = {
sm: 'px-4 py-2 text-sm',
md: 'px-6 py-3 text-base',
lg: 'px-8 py-4 text-lg',
};
const variantClasses = {
primary: 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700',
secondary: 'bg-gray-600 text-white border-gray-600 hover:bg-gray-700',
outline: 'bg-transparent text-blue-600 border-blue-600 hover:bg-blue-600 hover:text-white',
};
// In component:
<button
className={clsx(
'inline-flex items-center justify-center rounded-md font-semibold border-2 transition-all',
sizeClasses[size],
variantClasses[variant],
disabled && 'opacity-50 cursor-not-allowed',
isActive && 'ring-4 ring-blue-300',
className
)}
>
{children}
</button>
5. Write Tests
// ComponentName.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { ComponentName } from './ComponentName';
describe('ComponentName', () => {
it('renders children correctly', () => {
render(<ComponentName>Click me</ComponentName>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('applies correct variant styles', () => {
render(<ComponentName variant="primary">Button</ComponentName>);
const button = screen.getByTestId('component-name');
// Add assertions for styles
});
it('handles click events', () => {
const handleClick = jest.fn();
render(<ComponentName onClick={handleClick}>Click</ComponentName>);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('does not trigger onClick when disabled', () => {
const handleClick = jest.fn();
render(
<ComponentName disabled onClick={handleClick}>
Disabled
</ComponentName>
);
fireEvent.click(screen.getByText('Disabled'));
expect(handleClick).not.toHaveBeenCalled();
});
it('renders all size variants', () => {
const { rerender } = render(<ComponentName size="sm">Small</ComponentName>);
// Assert sm size
rerender(<ComponentName size="md">Medium</ComponentName>);
// Assert md size
rerender(<ComponentName size="lg">Large</ComponentName>);
// Assert lg size
});
});
6. Create Storybook Stories
// ComponentName.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from './ComponentName';
const meta: Meta<typeof ComponentName> = {
title: 'Components/ComponentName',
component: ComponentName,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'outline'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
disabled: {
control: 'boolean',
},
},
};
export default meta;
type Story = StoryObj<typeof ComponentName>;
export const Primary: Story = {
args: {
children: 'Primary Button',
variant: 'primary',
},
};
export const Secondary: Story = {
args: {
children: 'Secondary Button',
variant: 'secondary',
},
};
export const Outline: Story = {
args: {
children: 'Outline Button',
variant: 'outline',
},
};
export const Small: Story = {
args: {
children: 'Small Button',
size: 'sm',
},
};
export const Large: Story = {
args: {
children: 'Large Button',
size: 'lg',
},
};
export const Disabled: Story = {
args: {
children: 'Disabled Button',
disabled: true,
},
};
export const AllVariants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem', flexDirection: 'column' }}>
<div style={{ display: 'flex', gap: '1rem' }}>
<ComponentName variant="primary">Primary</ComponentName>
<ComponentName variant="secondary">Secondary</ComponentName>
<ComponentName variant="outline">Outline</ComponentName>
</div>
<div style={{ display: 'flex', gap: '1rem' }}>
<ComponentName size="sm">Small</ComponentName>
<ComponentName size="md">Medium</ComponentName>
<ComponentName size="lg">Large</ComponentName>
</div>
</div>
),
};
7. Add Documentation
# ComponentName
Brief description of what this component does.
## Usage
\`\`\`tsx
import { ComponentName } from './components/ComponentName';
function App() {
return (
<ComponentName variant="primary" onClick={() => console.log('Clicked!')}>
Click Me
</ComponentName>
);
}
\`\`\`
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| children | ReactNode | - | Content to display |
| variant | 'primary' \| 'secondary' \| 'outline' | 'primary' | Visual style variant |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Component size |
| disabled | boolean | false | Whether component is disabled |
| onClick | function | - | Click event handler |
| className | string | - | Additional CSS class |
| testId | string | 'component-name' | Test ID for testing |
## Examples
### Basic Usage
\`\`\`tsx
<ComponentName>Click me</ComponentName>
\`\`\`
### With Variant
\`\`\`tsx
<ComponentName variant="secondary">Secondary Action</ComponentName>
\`\`\`
### With Click Handler
\`\`\`tsx
<ComponentName onClick={() => alert('Clicked!')}>
Interactive Button
</ComponentName>
\`\`\`
## Accessibility
- Uses semantic HTML button element
- Supports keyboard navigation
- Proper focus states
- ARIA attributes for screen readers
## Testing
Run tests:
\`\`\`bash
npm test ComponentName.test.tsx
\`\`\`
View in Storybook:
\`\`\`bash
npm run storybook
\`\`\`
8. Create Barrel Export
// index.ts
export { ComponentName } from './ComponentName';
export type { ComponentNameProps } from './types';
Best Practices
✅ DO:
- Use TypeScript for type safety
- Write comprehensive tests (aim for >80% coverage)
- Document all props
- Create Storybook stories
- Follow naming conventions
- Use semantic HTML
- Support accessibility
- Handle edge cases
❌ DON'T:
- Skip prop validation
- Forget error boundaries
- Ignore accessibility
- Leave console.logs in code
- Skip tests for edge cases
- Use inline styles without reason
Quick Templates
Functional Component
export const ComponentName: React.FC<Props> = (props) => {
return <div>{props.children}</div>;
};
With Hooks
export const ComponentName: React.FC<Props> = (props) => {
const [state, setState] = useState();
useEffect(() => {
// Effect logic
}, []);
return <div></div>;
};
Forwarding Refs
export const ComponentName = forwardRef<HTMLDivElement, Props>(
(props, ref) => {
return <div ref={ref}>{props.children}</div>;
}
);
Related Tools
- ESLint: Linting
- Prettier: Code formatting
- Jest: Testing framework
- React Testing Library: Component testing
- Storybook: Component development
- TypeScript: Type checking