Skip to content

Commit 05f9df9

Browse files
feat: react component template and create-component script (#139)
## **Description** This PR implements a reusable react component template system with an accompanying CLI tool for the MetaMask Design System. It addresses two main improvements: 1. Creates a standardized react component template structure 2. Adds the create-component script 3. Adds comprehensive documentation for the component creation process Key changes: - Established a consistent react component template with required files (index.ts, ComponentName.tsx, etc.) - Implemented CLI tool to automate component creation with proper naming - Created detailed documentation in `docs/create-component.md` ## **Related issues** Fixes: #13 #36 ## **Manual testing steps** 1. Read the documentation at `docs/create-component.md` for full usage details 2. Run the example command: ```bash yarn create-component:react --name TestComponent --description "Test component" ``` 3. Verify that the following files are created as documented: ``` testcomponent/ ├── TestComponent.constants.ts ├── TestComponent.stories.tsx ├── TestComponent.test.tsx ├── TestComponent.tsx ├── TestComponent.types.ts ├── README.mdx └── index.ts ``` 4. Verify all template content is properly updated with the new component name 5. Verify the component is created in the correct location under `packages/design-system-react/src/components` ## **Documentation Updates** Added new documentation that covers: - Script usage and required arguments - Generated file structure and descriptions - Best practices for component creation - Example usage with real-world cases - File-by-file explanation of the template structure See `docs/create-component.md` for the complete documentation. ## **Screenshots/Recordings** ### **Before** Error when running create-component script: ``` Error: ENOENT: no such file or directory, scandir '.../templates/ComponentName' ``` ### **After** 1. Provides clear documentation for usage. 2. Initially fails to create `Button` because it already exists 3. Successfully creates `Icon` with standardized structure https://github.com/user-attachments/assets/02fa403b-6c2a-4ad9-866d-76ed13426b42 ## **Pre-merge author checklist** - [x] I've followed MetaMask Contributor Docs - [x] I've completed the PR template - [x] I've included error handling and validation - [x] I've documented the code changes and component structure - [x] I've ensured the template follows the specified structure from issue #13 - [x] I've added comprehensive documentation in `docs/create-component.md` --------- Co-authored-by: Brian August Nguyen <[email protected]>
1 parent 4cc9f98 commit 05f9df9

File tree

16 files changed

+469
-3
lines changed

16 files changed

+469
-3
lines changed

docs/create-component.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Create Component Script
2+
3+
The create-component script is a utility for generating new React components(React Native coming soon!) in the MetaMask Design System with consistent structure and boilerplate code.
4+
5+
## Usage
6+
7+
From the root directory, run:
8+
9+
```bash
10+
yarn create-component:react --name ComponentName --description "Brief description of the component"
11+
```
12+
13+
### Required Arguments
14+
15+
- `--name`: The name of the component in PascalCase (e.g., Button, TextField, Modal)
16+
- `--description`: A brief description of the component's purpose in quotes
17+
18+
### Example
19+
20+
```bash
21+
yarn create-component:react --name Button --description "A reusable button component that supports different variants and sizes"
22+
```
23+
24+
## Generated Files
25+
26+
The script will create a new directory under `packages/design-system-react/src/components` with the following structure:
27+
28+
```
29+
button/
30+
├── Button.constants.ts
31+
├── Button.stories.tsx
32+
├── Button.test.tsx
33+
├── Button.tsx
34+
├── Button.types.ts
35+
├── README.mdx
36+
└── index.ts
37+
```
38+
39+
### File Descriptions
40+
41+
- `ComponentName.constants.ts`: Constants and classname mappings
42+
- `ComponentName.stories.tsx`: Storybook stories for component documentation
43+
- `ComponentName.test.tsx`: Jest test suite
44+
- `ComponentName.tsx`: Main component implementation
45+
- `ComponentName.types.ts`: TypeScript interfaces and types
46+
- `README.mdx`: Component documentation and usage examples
47+
- `index.ts`: Exports for the component
48+
49+
The script will also automatically:
50+
51+
1. Create the component directory with all necessary files
52+
2. Update `src/components/index.ts` to export the new component
53+
3. Add proper TypeScript types and basic tests
54+
4. Set up Storybook documentation
55+
56+
## Best Practices
57+
58+
1. Use PascalCase for the component name
59+
2. Provide a clear, concise description
60+
3. After generating the component:
61+
- Add proper styling using Tailwind classes
62+
- Implement comprehensive tests
63+
- Add meaningful Storybook stories
64+
- Update the README.mdx with usage examples
65+
66+
## Example Component Creation
67+
68+
```bash
69+
yarn create-component:react --name Button --description "A reusable button component that supports different variants and sizes"
70+
```
71+
72+
This will create a new TextField component with all the necessary files and boilerplate code.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"build:types": "tsc --build tsconfig.build.json --verbose",
1919
"changelog:update": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run changelog:update",
2020
"changelog:validate": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run changelog:validate",
21+
"create-component:react": "yarn workspace @metamask/design-system-react create-component",
2122
"create-package": "ts-node scripts/create-package",
2223
"lint": "yarn lint:eslint && yarn lint:misc --check && yarn constraints && yarn lint:dependencies",
2324
"lint:dependencies": "depcheck && yarn dedupe --check",

packages/design-system-react/jest.config.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ module.exports = merge(baseConfig, {
1515
displayName,
1616

1717
// Add coverage ignore patterns
18-
coveragePathIgnorePatterns: ['index.ts', '.d.ts'],
18+
coveragePathIgnorePatterns: [
19+
'index.ts',
20+
'.d.ts',
21+
'scripts/create-component/ComponentName/',
22+
],
23+
24+
// Add test match ignore patterns
25+
testPathIgnorePatterns: ['scripts/create-component/ComponentName/'],
1926

2027
// An object that configures minimum threshold enforcement for coverage results
2128
coverageThreshold: {

packages/design-system-react/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references",
3939
"changelog:update": "../../scripts/update-changelog.sh @metamask/design-system-react",
4040
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/design-system-react",
41+
"create-component": "ts-node scripts/create-component",
4142
"publish:preview": "yarn npm publish --tag preview",
4243
"since-latest-release": "../../scripts/since-latest-release.sh",
4344
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
@@ -58,12 +59,14 @@
5859
"@testing-library/react": "^16.0.1",
5960
"@ts-bridge/cli": "^0.5.1",
6061
"@types/jest": "^27.4.1",
62+
"@types/node": "^16.18.54",
6163
"@types/react": "^18.2.0",
6264
"@types/react-dom": "^18.2.0",
6365
"deepmerge": "^4.2.2",
6466
"jest": "^29.7.0",
6567
"jest-environment-jsdom": "^29.7.0",
6668
"ts-jest": "^29.2.5",
69+
"ts-node": "^10.9.1",
6770
"typescript": "~5.2.2"
6871
},
6972
"peerDependencies": {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Remove this file if it's not needed
2+
export const COMPONENT_NAME_CLASSMAP = {};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import type { Meta, StoryObj } from '@storybook/react';
3+
import { ComponentName } from './ComponentName';
4+
import README from './README.mdx';
5+
6+
const meta: Meta<typeof ComponentName> = {
7+
title: 'Components/ComponentName',
8+
component: ComponentName,
9+
parameters: {
10+
docs: {
11+
page: README,
12+
},
13+
},
14+
};
15+
16+
export default meta;
17+
type Story = StoryObj<typeof ComponentName>;
18+
19+
export const Default: Story = {
20+
args: {
21+
children: 'Default ComponentName',
22+
},
23+
};
24+
25+
// You can add more stories as needed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { ComponentName } from './ComponentName';
4+
import '@testing-library/jest-dom';
5+
6+
describe('ComponentName Component', () => {
7+
it('renders children correctly', () => {
8+
render(<ComponentName>Hello, World!</ComponentName>);
9+
expect(screen.getByText('Hello, World!')).toBeInTheDocument();
10+
});
11+
12+
it('applies the correct classes', () => {
13+
render(
14+
<ComponentName className="custom-class">Styled Content</ComponentName>,
15+
);
16+
expect(screen.getByText('Styled Content')).toHaveClass('custom-class');
17+
// Add more class-related tests as needed
18+
});
19+
20+
// Add more tests as needed
21+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import { twMerge } from '../../utils/tw-merge';
3+
import { ComponentNameProps } from './ComponentName.types';
4+
5+
export const ComponentName: React.FC<ComponentNameProps> = ({
6+
children,
7+
className,
8+
}) => {
9+
const mergedClassName = twMerge('your-default-classes', className);
10+
11+
return <div className={mergedClassName}>{children}</div>;
12+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type ComponentNameProps = {
2+
/**
3+
* The content to be rendered within the ComponentName.
4+
*/
5+
children: React.ReactNode;
6+
/**
7+
* Additional CSS classes to be applied to the ComponentName component.
8+
*/
9+
className?: string;
10+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Controls, Canvas } from '@storybook/blocks';
2+
3+
import * as ComponentNameStories from './ComponentName.stories';
4+
5+
# ComponentName
6+
7+
ComponentName is used to render standardized elements within an interface.
8+
9+
<Canvas of={ComponentNameStories.Default} />
10+
11+
## Props
12+
13+
### Children
14+
15+
The content of the `ComponentName` component.
16+
17+
### Class Name
18+
19+
Adds an additional class to the `ComponentName` component.
20+
21+
### Component API
22+
23+
<Controls of={ComponentNameStories.Default} />
24+
25+
### References
26+
27+
[MetaMask Design System Guides](https://www.notion.so/MetaMask-Design-System-Guides-Design-f86ecc914d6b4eb6873a122b83c12940)

0 commit comments

Comments
 (0)