@dhayalesh/create-component 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/generators/create-component/files/component/component.spec.tsx.template +58 -0
- package/generators/create-component/files/component/component.stories.tsx.template +142 -0
- package/generators/create-component/files/component/component.tsx.template +98 -0
- package/generators/create-component/files/library/README.md.template +15 -0
- package/generators/create-component/files/library/eslint.config.mjs.template +17 -0
- package/generators/create-component/files/library/package.json.template +20 -0
- package/generators/create-component/files/library/src/index.ts.template +1 -0
- package/generators/create-component/files/library/src/styles.css.template +50 -0
- package/generators/create-component/files/library/src/test-setup.ts.template +8 -0
- package/generators/create-component/files/library/tsconfig.json.template +25 -0
- package/generators/create-component/files/library/tsconfig.lib.json.template +27 -0
- package/generators/create-component/files/library/tsconfig.spec.json.template +17 -0
- package/generators/create-component/files/library/vite.config.ts.template +49 -0
- package/generators/create-component/generator.d.ts +12 -0
- package/generators/create-component/generator.d.ts.map +1 -0
- package/generators/create-component/generator.js +220 -0
- package/generators/create-component/schema.d.ts +6 -0
- package/generators/create-component/schema.json +34 -0
- package/generators.json +9 -0
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -0
- package/index.js +2 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# create-component
|
|
2
|
+
|
|
3
|
+
This library was generated with [Nx](https://nx.dev).
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
Run `nx build create-component` to build the library.
|
|
8
|
+
|
|
9
|
+
## Running unit tests
|
|
10
|
+
|
|
11
|
+
Run `nx test create-component` to execute the unit tests via [Jest](https://jestjs.io).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { <%= className %> } from './<%= className %>';
|
|
3
|
+
|
|
4
|
+
describe('<%= className %>', () => {
|
|
5
|
+
it('should render successfully', () => {
|
|
6
|
+
render(<<%= className %>>Test</<%= className %>>);
|
|
7
|
+
expect(screen.getByText('Test')).toBeInTheDocument();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should apply the correct variant class', () => {
|
|
11
|
+
const { rerender } = render(<<%= className %> variant="primary">Test</<%= className %>>);
|
|
12
|
+
const button = screen.getByText('Test');
|
|
13
|
+
expect(button).toHaveClass('bg-blue-600');
|
|
14
|
+
|
|
15
|
+
rerender(<<%= className %> variant="secondary">Test</<%= className %>>);
|
|
16
|
+
expect(button).toHaveClass('bg-purple-600');
|
|
17
|
+
|
|
18
|
+
rerender(<<%= className %> variant="accent">Test</<%= className %>>);
|
|
19
|
+
expect(button).toHaveClass('bg-green-600');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should apply the correct size class', () => {
|
|
23
|
+
const { rerender } = render(<<%= className %> size="sm">Test</<%= className %>>);
|
|
24
|
+
const button = screen.getByText('Test');
|
|
25
|
+
expect(button).toHaveClass('px-3');
|
|
26
|
+
|
|
27
|
+
rerender(<<%= className %> size="md">Test</<%= className %>>);
|
|
28
|
+
expect(button).toHaveClass('px-4');
|
|
29
|
+
|
|
30
|
+
rerender(<<%= className %> size="lg">Test</<%= className %>>);
|
|
31
|
+
expect(button).toHaveClass('px-6');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle click events', () => {
|
|
35
|
+
const handleClick = vi.fn();
|
|
36
|
+
render(<<%= className %> onClick={handleClick}>Test</<%= className %>>);
|
|
37
|
+
|
|
38
|
+
const button = screen.getByText('Test');
|
|
39
|
+
fireEvent.click(button);
|
|
40
|
+
|
|
41
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should be disabled when disabled prop is true', () => {
|
|
45
|
+
render(<<%= className %> disabled>Test</<%= className %>>);
|
|
46
|
+
const button = screen.getByText('Test');
|
|
47
|
+
|
|
48
|
+
expect(button).toBeDisabled();
|
|
49
|
+
expect(button).toHaveClass('opacity-50');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should apply custom className', () => {
|
|
53
|
+
render(<<%= className %> className="custom-class">Test</<%= className %>>);
|
|
54
|
+
const button = screen.getByText('Test');
|
|
55
|
+
|
|
56
|
+
expect(button).toHaveClass('custom-class');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { <%= className %> } from './<%= className %>';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <%= className %> Component Stories
|
|
6
|
+
*
|
|
7
|
+
* Demonstrates the component in various states and configurations.
|
|
8
|
+
* This follows the Separation of Concerns principle - stories are separate from implementation.
|
|
9
|
+
*/
|
|
10
|
+
const meta: Meta<typeof <%= className %>> = {
|
|
11
|
+
title: 'Components/<%= className %>',
|
|
12
|
+
component: <%= className %>,
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
argTypes: {
|
|
15
|
+
variant: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: ['primary', 'secondary', 'accent'],
|
|
18
|
+
description: 'Visual variant of the component',
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
control: 'select',
|
|
22
|
+
options: ['sm', 'md', 'lg'],
|
|
23
|
+
description: 'Size of the component',
|
|
24
|
+
},
|
|
25
|
+
disabled: {
|
|
26
|
+
control: 'boolean',
|
|
27
|
+
description: 'Whether the component is disabled',
|
|
28
|
+
},
|
|
29
|
+
onClick: {
|
|
30
|
+
action: 'clicked',
|
|
31
|
+
description: 'Click event handler',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof <%= className %>>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default state of the component
|
|
41
|
+
*/
|
|
42
|
+
export const Default: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
children: 'Click me',
|
|
45
|
+
variant: 'primary',
|
|
46
|
+
size: 'md',
|
|
47
|
+
disabled: false,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Primary variant
|
|
53
|
+
*/
|
|
54
|
+
export const Primary: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
children: 'Primary Button',
|
|
57
|
+
variant: 'primary',
|
|
58
|
+
size: 'md',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Secondary variant
|
|
64
|
+
*/
|
|
65
|
+
export const Secondary: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
children: 'Secondary Button',
|
|
68
|
+
variant: 'secondary',
|
|
69
|
+
size: 'md',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Accent variant
|
|
75
|
+
*/
|
|
76
|
+
export const Accent: Story = {
|
|
77
|
+
args: {
|
|
78
|
+
children: 'Accent Button',
|
|
79
|
+
variant: 'accent',
|
|
80
|
+
size: 'md',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Small size
|
|
86
|
+
*/
|
|
87
|
+
export const Small: Story = {
|
|
88
|
+
args: {
|
|
89
|
+
children: 'Small Button',
|
|
90
|
+
variant: 'primary',
|
|
91
|
+
size: 'sm',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Large size
|
|
97
|
+
*/
|
|
98
|
+
export const Large: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
children: 'Large Button',
|
|
101
|
+
variant: 'primary',
|
|
102
|
+
size: 'lg',
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Disabled state
|
|
108
|
+
*/
|
|
109
|
+
export const Disabled: Story = {
|
|
110
|
+
args: {
|
|
111
|
+
children: 'Disabled Button',
|
|
112
|
+
variant: 'primary',
|
|
113
|
+
size: 'md',
|
|
114
|
+
disabled: true,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* All variants side by side
|
|
120
|
+
*/
|
|
121
|
+
export const AllVariants: Story = {
|
|
122
|
+
render: () => (
|
|
123
|
+
<div className="flex gap-4">
|
|
124
|
+
<<%= className %> variant="primary">Primary</<%= className %>>
|
|
125
|
+
<<%= className %> variant="secondary">Secondary</<%= className %>>
|
|
126
|
+
<<%= className %> variant="accent">Accent</<%= className %>>
|
|
127
|
+
</div>
|
|
128
|
+
),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* All sizes side by side
|
|
133
|
+
*/
|
|
134
|
+
export const AllSizes: Story = {
|
|
135
|
+
render: () => (
|
|
136
|
+
<div className="flex gap-4 items-center">
|
|
137
|
+
<<%= className %> size="sm">Small</<%= className %>>
|
|
138
|
+
<<%= className %> size="md">Medium</<%= className %>>
|
|
139
|
+
<<%= className %> size="lg">Large</<%= className %>>
|
|
140
|
+
</div>
|
|
141
|
+
),
|
|
142
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Props interface for <%= className %> component
|
|
5
|
+
* Following Inversion of Control principle - component behavior is configured via props
|
|
6
|
+
*/
|
|
7
|
+
export interface <%= className %>Props {
|
|
8
|
+
/**
|
|
9
|
+
* The content to display inside the component
|
|
10
|
+
*/
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Optional CSS class name for custom styling
|
|
15
|
+
*/
|
|
16
|
+
className?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Variant of the component
|
|
20
|
+
*/
|
|
21
|
+
variant?: 'primary' | 'secondary' | 'accent';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Size of the component
|
|
25
|
+
*/
|
|
26
|
+
size?: 'sm' | 'md' | 'lg';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether the component is disabled
|
|
30
|
+
*/
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Click handler
|
|
35
|
+
*/
|
|
36
|
+
onClick?: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* <%= className %> Component
|
|
41
|
+
*
|
|
42
|
+
* A reusable UI component built with React and Tailwind CSS.
|
|
43
|
+
* Follows Clean Architecture principles:
|
|
44
|
+
* - Dependency Rule: Depends only on React and design tokens (CSS variables)
|
|
45
|
+
* - Separation of Concerns: Presentation logic separated from business logic
|
|
46
|
+
* - Inversion of Control: Behavior configured via props interface
|
|
47
|
+
*/
|
|
48
|
+
export const <%= className %>: React.FC<<%= className %>Props> = ({
|
|
49
|
+
children,
|
|
50
|
+
className = '',
|
|
51
|
+
variant = 'primary',
|
|
52
|
+
size = 'md',
|
|
53
|
+
disabled = false,
|
|
54
|
+
onClick,
|
|
55
|
+
}) => {
|
|
56
|
+
// Variant styles - using Tailwind classes that reference design tokens
|
|
57
|
+
const variantClasses = {
|
|
58
|
+
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
|
|
59
|
+
secondary: 'bg-purple-600 hover:bg-purple-700 text-white',
|
|
60
|
+
accent: 'bg-green-600 hover:bg-green-700 text-white',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Size styles
|
|
64
|
+
const sizeClasses = {
|
|
65
|
+
sm: 'px-3 py-1.5 text-sm',
|
|
66
|
+
md: 'px-4 py-2 text-base',
|
|
67
|
+
lg: 'px-6 py-3 text-lg',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Combine classes
|
|
71
|
+
const classes = [
|
|
72
|
+
'rounded-lg',
|
|
73
|
+
'font-medium',
|
|
74
|
+
'transition-all',
|
|
75
|
+
'duration-200',
|
|
76
|
+
'focus:outline-none',
|
|
77
|
+
'focus:ring-2',
|
|
78
|
+
'focus:ring-offset-2',
|
|
79
|
+
'focus:ring-offset-gray-900',
|
|
80
|
+
variantClasses[variant],
|
|
81
|
+
sizeClasses[size],
|
|
82
|
+
disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
|
|
83
|
+
className,
|
|
84
|
+
].join(' ');
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<button
|
|
88
|
+
className={classes}
|
|
89
|
+
onClick={onClick}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
type="button"
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</button>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export default <%= className %>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# <%= className %>
|
|
2
|
+
|
|
3
|
+
This library was generated with [Nx](https://nx.dev).
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
Run `nx build <%= projectName %>` to build the library.
|
|
8
|
+
|
|
9
|
+
## Running unit tests
|
|
10
|
+
|
|
11
|
+
Run `nx test <%= projectName %>` to execute the unit tests via [Vitest](https://vitest.dev/).
|
|
12
|
+
|
|
13
|
+
## Running Storybook
|
|
14
|
+
|
|
15
|
+
Run `nx storybook <%= projectName %>` to start the Storybook development server.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
|
7
|
+
rules: {},
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
11
|
+
rules: {},
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
files: ['**/*.js', '**/*.jsx'],
|
|
15
|
+
rules: {},
|
|
16
|
+
},
|
|
17
|
+
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dhayalesh/<%= projectName %>",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@testing-library/react": "^14.0.0",
|
|
9
|
+
"@testing-library/jest-dom": "^6.1.5",
|
|
10
|
+
"@types/react": "^18.2.0",
|
|
11
|
+
"@types/react-dom": "^18.2.0",
|
|
12
|
+
"vitest": "^1.0.0",
|
|
13
|
+
"@vitest/ui": "^1.0.0",
|
|
14
|
+
"jsdom": "^23.0.0"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"react": "^18.2.0",
|
|
18
|
+
"react-dom": "^18.2.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/<%= className %>/<%= className %>';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
/* Design System Tokens */
|
|
6
|
+
:root {
|
|
7
|
+
/* Colors */
|
|
8
|
+
--color-primary: #3b82f6;
|
|
9
|
+
--color-secondary: #8b5cf6;
|
|
10
|
+
--color-accent: #10b981;
|
|
11
|
+
--color-background: #0a0a0a;
|
|
12
|
+
--color-surface: #1a1a1a;
|
|
13
|
+
--color-text: #ffffff;
|
|
14
|
+
--color-text-secondary: #a3a3a3;
|
|
15
|
+
|
|
16
|
+
/* Spacing */
|
|
17
|
+
--spacing-xs: 0.25rem;
|
|
18
|
+
--spacing-sm: 0.5rem;
|
|
19
|
+
--spacing-md: 1rem;
|
|
20
|
+
--spacing-lg: 1.5rem;
|
|
21
|
+
--spacing-xl: 2rem;
|
|
22
|
+
|
|
23
|
+
/* Border Radius */
|
|
24
|
+
--radius-sm: 0.25rem;
|
|
25
|
+
--radius-md: 0.5rem;
|
|
26
|
+
--radius-lg: 0.75rem;
|
|
27
|
+
--radius-xl: 1rem;
|
|
28
|
+
|
|
29
|
+
/* Shadows */
|
|
30
|
+
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
31
|
+
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
|
32
|
+
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Base Styles */
|
|
36
|
+
* {
|
|
37
|
+
box-sizing: border-box;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
body {
|
|
41
|
+
margin: 0;
|
|
42
|
+
padding: 0;
|
|
43
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
44
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
45
|
+
sans-serif;
|
|
46
|
+
-webkit-font-smoothing: antialiased;
|
|
47
|
+
-moz-osx-font-smoothing: grayscale;
|
|
48
|
+
background-color: var(--color-background);
|
|
49
|
+
color: var(--color-text);
|
|
50
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"outDir": "../../dist/out-tsc",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"types": ["node", "vitest"],
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"esModuleInterop": true
|
|
14
|
+
},
|
|
15
|
+
"files": [],
|
|
16
|
+
"include": [],
|
|
17
|
+
"references": [
|
|
18
|
+
{
|
|
19
|
+
"path": "./tsconfig.lib.json"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "./tsconfig.spec.json"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"types": ["node"]
|
|
6
|
+
},
|
|
7
|
+
"include": [
|
|
8
|
+
"src/**/*.ts",
|
|
9
|
+
"src/**/*.tsx",
|
|
10
|
+
"src/**/*.js",
|
|
11
|
+
"src/**/*.jsx"
|
|
12
|
+
],
|
|
13
|
+
"exclude": [
|
|
14
|
+
"src/**/*.spec.ts",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"src/**/*.spec.tsx",
|
|
17
|
+
"src/**/*.test.tsx",
|
|
18
|
+
"src/**/*.spec.js",
|
|
19
|
+
"src/**/*.test.js",
|
|
20
|
+
"src/**/*.spec.jsx",
|
|
21
|
+
"src/**/*.test.jsx",
|
|
22
|
+
"**/*.stories.ts",
|
|
23
|
+
"**/*.stories.js",
|
|
24
|
+
"**/*.stories.jsx",
|
|
25
|
+
"**/*.stories.tsx"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"types": ["vitest/globals", "vitest/importMeta", "node"]
|
|
6
|
+
},
|
|
7
|
+
"include": [
|
|
8
|
+
"src/**/*.test.ts",
|
|
9
|
+
"src/**/*.spec.ts",
|
|
10
|
+
"src/**/*.test.tsx",
|
|
11
|
+
"src/**/*.spec.tsx",
|
|
12
|
+
"src/**/*.test.js",
|
|
13
|
+
"src/**/*.spec.js",
|
|
14
|
+
"src/**/*.test.jsx",
|
|
15
|
+
"src/**/*.spec.jsx"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
|
4
|
+
import dts from 'vite-plugin-dts';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
root: __dirname,
|
|
9
|
+
cacheDir: '../../node_modules/.vite/<%= projectName %>',
|
|
10
|
+
|
|
11
|
+
plugins: [
|
|
12
|
+
react(),
|
|
13
|
+
nxViteTsPaths(),
|
|
14
|
+
dts({
|
|
15
|
+
entryRoot: 'src',
|
|
16
|
+
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
|
|
20
|
+
build: {
|
|
21
|
+
outDir: '../../dist/<%= projectRoot %>',
|
|
22
|
+
emptyOutDir: true,
|
|
23
|
+
reportCompressedSize: true,
|
|
24
|
+
commonjsOptions: {
|
|
25
|
+
transformMixedEsModules: true,
|
|
26
|
+
},
|
|
27
|
+
lib: {
|
|
28
|
+
entry: 'src/index.ts',
|
|
29
|
+
name: '<%= className %>',
|
|
30
|
+
fileName: 'index',
|
|
31
|
+
formats: ['es', 'cjs'],
|
|
32
|
+
},
|
|
33
|
+
rollupOptions: {
|
|
34
|
+
external: ['react', 'react-dom', 'react/jsx-runtime'],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
test: {
|
|
39
|
+
globals: true,
|
|
40
|
+
environment: 'jsdom',
|
|
41
|
+
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
42
|
+
setupFiles: ['src/test-setup.ts'],
|
|
43
|
+
reporters: ['default'],
|
|
44
|
+
coverage: {
|
|
45
|
+
reportsDirectory: '../../coverage/<%= projectRoot %>',
|
|
46
|
+
provider: 'v8',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Tree } from '@nx/devkit';
|
|
2
|
+
import type { UIComponentGeneratorSchema } from './schema';
|
|
3
|
+
/**
|
|
4
|
+
* UI Component Generator
|
|
5
|
+
*
|
|
6
|
+
* This generator follows Clean Architecture principles:
|
|
7
|
+
* 1. Dependency Rule: Dependencies point inward (UI components depend on design tokens, not vice versa)
|
|
8
|
+
* 2. Inversion of Control: Components are configured via props/interfaces, not hardcoded dependencies
|
|
9
|
+
* 3. Separation of Concerns: Component logic, styles, and stories are separated
|
|
10
|
+
*/
|
|
11
|
+
export default function (tree: Tree, options: UIComponentGeneratorSchema): Promise<() => void>;
|
|
12
|
+
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../../../packages/create-component/src/generators/create-component/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AASvC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AAE3D;;;;;;;GAOG;AACH,yBACE,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,0BAA0B,uBA+BpC"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = default_1;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
/**
|
|
8
|
+
* UI Component Generator
|
|
9
|
+
*
|
|
10
|
+
* This generator follows Clean Architecture principles:
|
|
11
|
+
* 1. Dependency Rule: Dependencies point inward (UI components depend on design tokens, not vice versa)
|
|
12
|
+
* 2. Inversion of Control: Components are configured via props/interfaces, not hardcoded dependencies
|
|
13
|
+
* 3. Separation of Concerns: Component logic, styles, and stories are separated
|
|
14
|
+
*/
|
|
15
|
+
async function default_1(tree, options) {
|
|
16
|
+
const normalizedOptions = normalizeOptions(tree, options);
|
|
17
|
+
// Step 1: Generate library structure
|
|
18
|
+
addLibraryFiles(tree, normalizedOptions);
|
|
19
|
+
// Step 2: Update workspace configuration
|
|
20
|
+
updateWorkspaceConfig(tree, normalizedOptions);
|
|
21
|
+
// Step 3: Add Storybook configuration
|
|
22
|
+
addStorybookConfig(tree, normalizedOptions);
|
|
23
|
+
// Step 4: Generate initial component template
|
|
24
|
+
addComponentTemplate(tree, normalizedOptions);
|
|
25
|
+
// Step 5: Format files
|
|
26
|
+
if (!options.skipFormat) {
|
|
27
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
28
|
+
}
|
|
29
|
+
return () => {
|
|
30
|
+
console.log(`
|
|
31
|
+
✅ Successfully created UI component library: ${normalizedOptions.projectName}
|
|
32
|
+
|
|
33
|
+
📍 Location: ${normalizedOptions.projectRoot}
|
|
34
|
+
|
|
35
|
+
💡 Your component is ready to customize at:
|
|
36
|
+
${normalizedOptions.projectRoot}/src/components/${normalizedOptions.className}/${normalizedOptions.className}.tsx
|
|
37
|
+
`);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function normalizeOptions(tree, options) {
|
|
41
|
+
const basePath = 'packages/ui';
|
|
42
|
+
const name = (0, devkit_1.names)(options.name).fileName;
|
|
43
|
+
const projectDirectory = options.directory
|
|
44
|
+
? `${(0, devkit_1.names)(options.directory).fileName}/${name}`
|
|
45
|
+
: name;
|
|
46
|
+
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
|
|
47
|
+
const projectRoot = `${basePath}/${name}`;
|
|
48
|
+
const parsedTags = [];
|
|
49
|
+
const componentNames = (0, devkit_1.names)(options.componentName);
|
|
50
|
+
return {
|
|
51
|
+
...options,
|
|
52
|
+
projectName,
|
|
53
|
+
projectRoot,
|
|
54
|
+
projectDirectory,
|
|
55
|
+
parsedTags,
|
|
56
|
+
fileName: componentNames.fileName,
|
|
57
|
+
className: componentNames.className,
|
|
58
|
+
constantName: componentNames.constantName,
|
|
59
|
+
propertyName: componentNames.propertyName,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function addLibraryFiles(tree, options) {
|
|
63
|
+
const templateOptions = {
|
|
64
|
+
...options,
|
|
65
|
+
...(0, devkit_1.names)(options.name),
|
|
66
|
+
offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.projectRoot),
|
|
67
|
+
template: '',
|
|
68
|
+
};
|
|
69
|
+
(0, devkit_1.generateFiles)(tree, path.join(__dirname, 'files/library'), options.projectRoot, templateOptions);
|
|
70
|
+
}
|
|
71
|
+
function updateWorkspaceConfig(tree, options) {
|
|
72
|
+
// Create project.json
|
|
73
|
+
const projectJson = {
|
|
74
|
+
name: options.projectName,
|
|
75
|
+
$schema: `${(0, devkit_1.offsetFromRoot)(options.projectRoot)}node_modules/nx/schemas/project-schema.json`,
|
|
76
|
+
sourceRoot: `${options.projectRoot}/src`,
|
|
77
|
+
projectType: 'library',
|
|
78
|
+
tags: options.parsedTags,
|
|
79
|
+
targets: {
|
|
80
|
+
build: {
|
|
81
|
+
executor: '@nx/vite:build',
|
|
82
|
+
outputs: ['{options.outputPath}'],
|
|
83
|
+
options: {
|
|
84
|
+
outputPath: `dist/${options.projectRoot}`,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
lint: {
|
|
88
|
+
executor: '@nx/eslint:lint',
|
|
89
|
+
},
|
|
90
|
+
test: {
|
|
91
|
+
executor: '@nx/vitest:test',
|
|
92
|
+
outputs: ['{workspaceRoot}/coverage/{projectRoot}'],
|
|
93
|
+
options: {
|
|
94
|
+
passWithNoTests: true,
|
|
95
|
+
reportsDirectory: `../../coverage/${options.projectRoot}`,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
storybook: {
|
|
99
|
+
executor: '@nx/storybook:storybook',
|
|
100
|
+
options: {
|
|
101
|
+
port: 4400,
|
|
102
|
+
configDir: `${options.projectRoot}/.storybook`,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
'build-storybook': {
|
|
106
|
+
executor: '@nx/storybook:build',
|
|
107
|
+
outputs: ['{options.outputDir}'],
|
|
108
|
+
options: {
|
|
109
|
+
outputDir: `dist/storybook/${options.projectName}`,
|
|
110
|
+
configDir: `${options.projectRoot}/.storybook`,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
tree.write(`${options.projectRoot}/project.json`, JSON.stringify(projectJson, null, 2));
|
|
116
|
+
// Update tsconfig.base.json paths
|
|
117
|
+
(0, devkit_1.updateJson)(tree, 'tsconfig.base.json', (json) => {
|
|
118
|
+
const paths = json.compilerOptions?.paths || {};
|
|
119
|
+
paths[`@dhayalesh/${options.projectName}`] = [
|
|
120
|
+
`${options.projectRoot}/src/index.ts`,
|
|
121
|
+
];
|
|
122
|
+
json.compilerOptions = {
|
|
123
|
+
...json.compilerOptions,
|
|
124
|
+
paths,
|
|
125
|
+
};
|
|
126
|
+
return json;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function addStorybookConfig(tree, options) {
|
|
130
|
+
const storybookDir = `${options.projectRoot}/.storybook`;
|
|
131
|
+
// Main Storybook configuration
|
|
132
|
+
const mainConfig = `
|
|
133
|
+
import type { StorybookConfig } from '@storybook/react-vite';
|
|
134
|
+
|
|
135
|
+
const config: StorybookConfig = {
|
|
136
|
+
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
|
137
|
+
addons: [
|
|
138
|
+
'@storybook/addon-essentials',
|
|
139
|
+
'@storybook/addon-interactions',
|
|
140
|
+
'@storybook/addon-a11y',
|
|
141
|
+
],
|
|
142
|
+
framework: {
|
|
143
|
+
name: '@storybook/react-vite',
|
|
144
|
+
options: {},
|
|
145
|
+
},
|
|
146
|
+
docs: {
|
|
147
|
+
autodocs: true,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export default config;
|
|
152
|
+
`.trim();
|
|
153
|
+
tree.write(`${storybookDir}/main.ts`, mainConfig);
|
|
154
|
+
// Preview configuration with Tailwind
|
|
155
|
+
const previewConfig = `
|
|
156
|
+
import type { Preview } from '@storybook/react';
|
|
157
|
+
import '../src/styles.css';
|
|
158
|
+
|
|
159
|
+
const preview: Preview = {
|
|
160
|
+
parameters: {
|
|
161
|
+
controls: {
|
|
162
|
+
matchers: {
|
|
163
|
+
color: /(background|color)$/i,
|
|
164
|
+
date: /Date$/,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
backgrounds: {
|
|
168
|
+
default: 'dark',
|
|
169
|
+
values: [
|
|
170
|
+
{
|
|
171
|
+
name: 'dark',
|
|
172
|
+
value: '#0a0a0a',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'light',
|
|
176
|
+
value: '#ffffff',
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default preview;
|
|
184
|
+
`.trim();
|
|
185
|
+
tree.write(`${storybookDir}/preview.ts`, previewConfig);
|
|
186
|
+
}
|
|
187
|
+
function addComponentTemplate(tree, options) {
|
|
188
|
+
const componentDir = `${options.projectRoot}/src/components/${options.className}`;
|
|
189
|
+
const templateOptions = {
|
|
190
|
+
...options,
|
|
191
|
+
template: '',
|
|
192
|
+
};
|
|
193
|
+
// Generate component files from templates
|
|
194
|
+
(0, devkit_1.generateFiles)(tree, path.join(__dirname, 'files/component'), componentDir, templateOptions);
|
|
195
|
+
// Rename the template files to use the actual component name
|
|
196
|
+
const componentFile = `${componentDir}/component.tsx`;
|
|
197
|
+
const storiesFile = `${componentDir}/component.stories.tsx`;
|
|
198
|
+
const specFile = `${componentDir}/component.spec.tsx`;
|
|
199
|
+
if (tree.exists(componentFile)) {
|
|
200
|
+
const content = tree.read(componentFile, 'utf-8');
|
|
201
|
+
if (content) {
|
|
202
|
+
tree.delete(componentFile);
|
|
203
|
+
tree.write(`${componentDir}/${options.className}.tsx`, content);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (tree.exists(storiesFile)) {
|
|
207
|
+
const content = tree.read(storiesFile, 'utf-8');
|
|
208
|
+
if (content) {
|
|
209
|
+
tree.delete(storiesFile);
|
|
210
|
+
tree.write(`${componentDir}/${options.className}.stories.tsx`, content);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (tree.exists(specFile)) {
|
|
214
|
+
const content = tree.read(specFile, 'utf-8');
|
|
215
|
+
if (content) {
|
|
216
|
+
tree.delete(specFile);
|
|
217
|
+
tree.write(`${componentDir}/${options.className}.spec.tsx`, content);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "UIComponent",
|
|
4
|
+
"title": "Create a UI Component Library",
|
|
5
|
+
"description": "Creates a new UI component library with Storybook and Tailwind CSS",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "The name of the component library",
|
|
11
|
+
"$default": {
|
|
12
|
+
"$source": "argv",
|
|
13
|
+
"index": 0
|
|
14
|
+
},
|
|
15
|
+
"x-prompt": "What name would you like to use for the component library?"
|
|
16
|
+
},
|
|
17
|
+
"directory": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "The directory where the library will be created",
|
|
20
|
+
"default": "libs"
|
|
21
|
+
},
|
|
22
|
+
"componentName": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "The name of the initial component to create",
|
|
25
|
+
"x-prompt": "What should the initial component be called?"
|
|
26
|
+
},
|
|
27
|
+
"skipFormat": {
|
|
28
|
+
"type": "boolean",
|
|
29
|
+
"description": "Skip formatting files",
|
|
30
|
+
"default": false
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"required": ["name", "componentName"]
|
|
34
|
+
}
|
package/generators.json
ADDED
package/index.d.ts
ADDED
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../packages/create-component/src/index.ts"],"names":[],"mappings":""}
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dhayalesh/create-component",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"module": "./index.js",
|
|
7
|
+
"types": "./index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"@org/source": "./src/index.ts",
|
|
12
|
+
"types": "./index.d.ts",
|
|
13
|
+
"import": "./index.js",
|
|
14
|
+
"default": "./index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"nx": {
|
|
18
|
+
"targets": {
|
|
19
|
+
"build": {
|
|
20
|
+
"executor": "@nx/js:tsc",
|
|
21
|
+
"outputs": [
|
|
22
|
+
"{options.outputPath}"
|
|
23
|
+
],
|
|
24
|
+
"options": {
|
|
25
|
+
"outputPath": "dist/packages/create-component",
|
|
26
|
+
"main": "packages/create-component/src/index.ts",
|
|
27
|
+
"tsConfig": "packages/create-component/tsconfig.lib.json",
|
|
28
|
+
"rootDir": "packages/create-component/src",
|
|
29
|
+
"generatePackageJson": false,
|
|
30
|
+
"assets": [
|
|
31
|
+
{
|
|
32
|
+
"input": "./packages/create-component/src",
|
|
33
|
+
"glob": "**/!(*.ts)",
|
|
34
|
+
"output": "."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"input": "./packages/create-component/src",
|
|
38
|
+
"glob": "**/*.d.ts",
|
|
39
|
+
"output": "."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"input": "./packages/create-component",
|
|
43
|
+
"glob": "generators.json",
|
|
44
|
+
"output": "."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"input": "./packages/create-component",
|
|
48
|
+
"glob": "package.json",
|
|
49
|
+
"output": "."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"input": "./packages/create-component",
|
|
53
|
+
"glob": "README.md",
|
|
54
|
+
"output": "."
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"generators": "./generators.json",
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"tslib": "^2.3.0",
|
|
64
|
+
"@nx/devkit": "^22.3.3"
|
|
65
|
+
}
|
|
66
|
+
}
|