create-template-html-css 2.0.4 → 2.2.0
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/CHANGELOG.md +436 -0
- package/CODE-SPLITTING-GUIDE.md +274 -0
- package/COMPONENTS-GALLERY.html +143 -8
- package/HTML-VS-REACT.md +289 -0
- package/QUICKSTART-REACT.md +293 -0
- package/REACT-SUPPORT-SUMMARY.md +235 -0
- package/README.md +261 -12
- package/bin/cli.js +100 -759
- package/bin/commands/create.js +288 -0
- package/bin/commands/gallery.js +42 -0
- package/bin/commands/insert.js +123 -0
- package/bin/commands/list.js +73 -0
- package/package.json +10 -3
- package/src/component-choices.js +7 -0
- package/src/components-registry.js +112 -0
- package/src/format-utils.js +49 -0
- package/src/generator.js +83 -594
- package/src/generators/color-schemes.js +78 -0
- package/src/generators/color-utils.js +108 -0
- package/src/generators/component-filters.js +151 -0
- package/src/generators/html-generators.js +180 -0
- package/src/generators/validation.js +43 -0
- package/src/index.js +2 -1
- package/src/inserter.js +55 -233
- package/src/inserters/backup-utils.js +20 -0
- package/src/inserters/component-loader.js +68 -0
- package/src/inserters/html-utils.js +31 -0
- package/src/inserters/indentation-utils.js +90 -0
- package/src/inserters/validation-utils.js +49 -0
- package/src/react-component-choices.js +97 -0
- package/src/react-component-templates.js +182 -0
- package/src/react-file-operations.js +172 -0
- package/src/react-generator.js +219 -0
- package/src/react-templates.js +418 -0
- package/src/templates/basic-components-templates.js +157 -0
- package/src/templates/form-components-templates.js +194 -0
- package/src/templates/interactive-components-templates.js +139 -0
- package/src/utils/file-utils.js +97 -0
- package/src/utils/path-utils.js +32 -0
- package/src/utils/string-utils.js +51 -0
- package/src/utils/template-loader.js +91 -0
- package/templates/_shared/PATTERNS.md +246 -0
- package/templates/_shared/README.md +74 -0
- package/templates/_shared/base.css +18 -0
- package/templates/blackjack/index.html +1 -1
- package/templates/breakout/index.html +1 -1
- package/templates/connect-four/index.html +1 -1
- package/templates/dice-game/index.html +1 -1
- package/templates/flappy-bird/index.html +1 -1
- package/templates/pong/index.html +1 -1
- package/templates/skeleton/index.html +4 -4
- package/templates/slot-machine/index.html +1 -1
- package/templates/tetris/index.html +1 -1
- package/templates-react/README.md +126 -0
- package/templates-react/alert/Alert.css +158 -0
- package/templates-react/alert/Alert.example.jsx +106 -0
- package/templates-react/alert/Alert.jsx +61 -0
- package/templates-react/badge/Badge.css +196 -0
- package/templates-react/badge/Badge.example.jsx +182 -0
- package/templates-react/badge/Badge.jsx +44 -0
- package/templates-react/button/Button.css +88 -0
- package/templates-react/button/Button.example.jsx +40 -0
- package/templates-react/button/Button.jsx +29 -0
- package/templates-react/card/Card.css +86 -0
- package/templates-react/card/Card.example.jsx +49 -0
- package/templates-react/card/Card.jsx +35 -0
- package/templates-react/checkbox/Checkbox.css +217 -0
- package/templates-react/checkbox/Checkbox.example.jsx +141 -0
- package/templates-react/checkbox/Checkbox.jsx +82 -0
- package/templates-react/counter/Counter.css +99 -0
- package/templates-react/counter/Counter.example.jsx +45 -0
- package/templates-react/counter/Counter.jsx +70 -0
- package/templates-react/dropdown/Dropdown.css +237 -0
- package/templates-react/dropdown/Dropdown.example.jsx +98 -0
- package/templates-react/dropdown/Dropdown.jsx +154 -0
- package/templates-react/form/Form.css +128 -0
- package/templates-react/form/Form.example.jsx +64 -0
- package/templates-react/form/Form.jsx +125 -0
- package/templates-react/input/Input.css +113 -0
- package/templates-react/input/Input.example.jsx +82 -0
- package/templates-react/input/Input.jsx +87 -0
- package/templates-react/modal/Modal.css +152 -0
- package/templates-react/modal/Modal.example.jsx +90 -0
- package/templates-react/modal/Modal.jsx +46 -0
- package/templates-react/navbar/Navbar.css +139 -0
- package/templates-react/navbar/Navbar.example.jsx +37 -0
- package/templates-react/navbar/Navbar.jsx +62 -0
- package/templates-react/progress/Progress.css +247 -0
- package/templates-react/progress/Progress.example.jsx +244 -0
- package/templates-react/progress/Progress.jsx +79 -0
- package/templates-react/switch/Switch.css +244 -0
- package/templates-react/switch/Switch.example.jsx +221 -0
- package/templates-react/switch/Switch.jsx +98 -0
- package/templates-react/todo-list/TodoList.css +236 -0
- package/templates-react/todo-list/TodoList.example.jsx +15 -0
- package/templates-react/todo-list/TodoList.jsx +84 -0
- package/templates-react/tooltip/Tooltip.css +165 -0
- package/templates-react/tooltip/Tooltip.example.jsx +166 -0
- package/templates-react/tooltip/Tooltip.jsx +176 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Component choices for interactive CLI prompts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const REACT_COMPONENT_CHOICES = [
|
|
6
|
+
{
|
|
7
|
+
name: "� Alert - Alert/notification component with variants",
|
|
8
|
+
value: "alert",
|
|
9
|
+
short: "Alert",
|
|
10
|
+
},
|
|
11
|
+
{ name: "🏷️ Badge - Status badge with counts, dots, and variants",
|
|
12
|
+
value: "badge",
|
|
13
|
+
short: "Badge",
|
|
14
|
+
},
|
|
15
|
+
{ name: "🔘 Button - Customizable button with variants and sizes",
|
|
16
|
+
value: "button",
|
|
17
|
+
short: "Button",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "🎴 Card - Display content in an elegant card",
|
|
21
|
+
value: "card",
|
|
22
|
+
short: "Card",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "☑️ Checkbox - Checkbox with sizes, colors, and states",
|
|
26
|
+
value: "checkbox",
|
|
27
|
+
short: "Checkbox",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "🔢 Counter - Interactive counter with increment/decrement",
|
|
31
|
+
value: "counter",
|
|
32
|
+
short: "Counter",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "📋 Dropdown - Select dropdown with search and multi-select",
|
|
36
|
+
value: "dropdown",
|
|
37
|
+
short: "Dropdown",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "📝 Form - Flexible form with validation",
|
|
41
|
+
value: "form",
|
|
42
|
+
short: "Form",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "📄 Input - Text input with validation and states",
|
|
46
|
+
value: "input",
|
|
47
|
+
short: "Input",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "🪟 Modal - Dialog modal component",
|
|
51
|
+
value: "modal",
|
|
52
|
+
short: "Modal",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "🧭 Navbar - Navigation bar with mobile menu",
|
|
56
|
+
value: "navbar",
|
|
57
|
+
short: "Navbar",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "📊 Progress - Progress bar with variants and animations",
|
|
61
|
+
value: "progress",
|
|
62
|
+
short: "Progress",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "🔀 Switch - Toggle switch for on/off states",
|
|
66
|
+
value: "switch",
|
|
67
|
+
short: "Switch",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "✅ Todo List - Complete todo list with CRUD operations",
|
|
71
|
+
value: "todo-list",
|
|
72
|
+
short: "Todo List",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "💬 Tooltip - Contextual tooltip with positions and triggers",
|
|
76
|
+
value: "tooltip",
|
|
77
|
+
short: "Tooltip",
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
export const REACT_COMPONENT_DESCRIPTIONS = {
|
|
82
|
+
alert: "An alert component for displaying notifications with different types (success, error, warning, info). Supports dismissible and custom icons",
|
|
83
|
+
badge: "A status badge component for displaying labels, counts, and indicators. Supports sizes, colors, pill shape, dot badges, and position variants",
|
|
84
|
+
button: "A customizable button component with multiple variants (primary, secondary, success, danger) and sizes (small, medium, large)",
|
|
85
|
+
card: "A versatile card component for displaying content with image, title, description, and footer support",
|
|
86
|
+
checkbox: "A checkbox component with different sizes, colors (primary, secondary, success, error), indeterminate state, and validation support",
|
|
87
|
+
counter: "A simple counter component with increment, decrement, and reset functionality. Supports min/max limits and custom steps",
|
|
88
|
+
dropdown: "A dropdown select component with searchable options, multi-select support, custom positioning, and keyboard navigation",
|
|
89
|
+
form: "A flexible form component with built-in validation, supporting multiple field types (text, email, textarea, select)",
|
|
90
|
+
input: "A text input component with validation, error messages, icons, and different states (focused, error, disabled)",
|
|
91
|
+
modal: "A modal dialog component with overlay, customizable sizes, and close handlers",
|
|
92
|
+
navbar: "A responsive navigation bar with logo, menu items, and mobile hamburger menu. Supports sticky positioning",
|
|
93
|
+
progress: "A progress bar component with colors, sizes, labels, striped patterns, animations, and indeterminate state for loading",
|
|
94
|
+
switch: "A toggle switch component for on/off states. Supports colors, sizes, icons, disabled and loading states",
|
|
95
|
+
"todo-list": "A complete todo list component with add, toggle, and delete functionality. Includes task statistics",
|
|
96
|
+
tooltip: "A contextual tooltip component with multiple positions (top, bottom, left, right), triggers (hover, click, focus), and customizable delays",
|
|
97
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Component Templates Module
|
|
3
|
+
*
|
|
4
|
+
* Central registry for all React component templates organized by category.
|
|
5
|
+
* This module aggregates templates from specialized sub-modules and provides
|
|
6
|
+
* a unified API for accessing component templates and their metadata.
|
|
7
|
+
*
|
|
8
|
+
* @module react-component-templates
|
|
9
|
+
* @author create-template-html-css
|
|
10
|
+
* @version 2.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { BASIC_TEMPLATES } from './templates/basic-components-templates.js';
|
|
14
|
+
import { FORM_TEMPLATES } from './templates/form-components-templates.js';
|
|
15
|
+
import { INTERACTIVE_TEMPLATES } from './templates/interactive-components-templates.js';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// COMPONENT TEMPLATES REGISTRY
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Unified component template registry
|
|
23
|
+
*
|
|
24
|
+
* Aggregates all component templates from different categories into a single
|
|
25
|
+
* accessible object. Templates are organized by component name (kebab-case).
|
|
26
|
+
*
|
|
27
|
+
* Categories:
|
|
28
|
+
* - Basic components: alert, badge, button, card, counter
|
|
29
|
+
* - Form components: form, input, checkbox, dropdown, switch
|
|
30
|
+
* - Interactive components: modal, navbar, tooltip, progress, todo-list
|
|
31
|
+
*
|
|
32
|
+
* @constant {Object.<string, string>}
|
|
33
|
+
*/
|
|
34
|
+
export const COMPONENT_TEMPLATES = {
|
|
35
|
+
...BASIC_TEMPLATES,
|
|
36
|
+
...FORM_TEMPLATES,
|
|
37
|
+
...INTERACTIVE_TEMPLATES,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// REACT IMPORTS MAPPING
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* React hooks import requirements for components
|
|
46
|
+
*
|
|
47
|
+
* Maps component names (kebab-case) to the React hooks they require.
|
|
48
|
+
* Components not listed here don't need any React hooks.
|
|
49
|
+
*
|
|
50
|
+
* @constant {Object.<string, string>}
|
|
51
|
+
* @property {string} modal - Requires useState for open/close state
|
|
52
|
+
* @property {string} input - Requires useState for controlled inputs
|
|
53
|
+
* @property {string} checkbox - Requires useState for checked state
|
|
54
|
+
* @property {string} dropdown - Requires useState for selection state
|
|
55
|
+
* @property {string} switch - Requires useState for toggle state
|
|
56
|
+
* @property {string} progress - Requires useState and useEffect for animations
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* // Get imports for a component
|
|
60
|
+
* const imports = COMPONENT_IMPORTS['modal']; // Returns 'useState'
|
|
61
|
+
*/
|
|
62
|
+
export const COMPONENT_IMPORTS = {
|
|
63
|
+
modal: 'useState',
|
|
64
|
+
input: 'useState',
|
|
65
|
+
checkbox: 'useState',
|
|
66
|
+
dropdown: 'useState',
|
|
67
|
+
switch: 'useState',
|
|
68
|
+
progress: 'useState, useEffect',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// HELPER FUNCTIONS
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get template content for a component with placeholders replaced
|
|
77
|
+
*
|
|
78
|
+
* Retrieves the JSX template for the specified component and replaces
|
|
79
|
+
* placeholder tokens with the actual component name.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} componentKebab - Component name in kebab-case (e.g., 'my-button')
|
|
82
|
+
* @param {string} componentName - Component name in PascalCase (e.g., 'MyButton')
|
|
83
|
+
* @returns {string|null} Template content with placeholders replaced, or null if not found
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* const template = getComponentTemplate('button', 'Button');
|
|
87
|
+
* // Returns JSX string with <Button> tags instead of {ComponentName}
|
|
88
|
+
*/
|
|
89
|
+
export function getComponentTemplate(componentKebab, componentName) {
|
|
90
|
+
const template = COMPONENT_TEMPLATES[componentKebab];
|
|
91
|
+
if (!template) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Replace {ComponentName} placeholder with actual component name
|
|
96
|
+
return template.replace(/\{ComponentName\}/g, componentName);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get required React imports for a component
|
|
101
|
+
*
|
|
102
|
+
* Returns a comma-separated string of React hooks that the component needs.
|
|
103
|
+
* If the component doesn't require any hooks, returns an empty string.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} componentKebab - Component name in kebab-case
|
|
106
|
+
* @returns {string} Comma-separated imports (e.g., 'useState, useEffect') or empty string
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* const imports = getComponentImports('progress');
|
|
110
|
+
* // Returns 'useState, useEffect'
|
|
111
|
+
*
|
|
112
|
+
* const imports2 = getComponentImports('button');
|
|
113
|
+
* // Returns '' (no hooks needed)
|
|
114
|
+
*/
|
|
115
|
+
export function getComponentImports(componentKebab) {
|
|
116
|
+
return COMPONENT_IMPORTS[componentKebab] || '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get all available component names
|
|
121
|
+
*
|
|
122
|
+
* Returns an array of all component names (in kebab-case) that have templates available.
|
|
123
|
+
* Useful for validation, CLI autocomplete, or displaying available options.
|
|
124
|
+
*
|
|
125
|
+
* @returns {string[]} Array of component names in kebab-case
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const components = getAllComponentNames();
|
|
129
|
+
* // Returns ['alert', 'badge', 'button', 'card', ...]
|
|
130
|
+
*/
|
|
131
|
+
export function getAllComponentNames() {
|
|
132
|
+
return Object.keys(COMPONENT_TEMPLATES);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if a component template exists
|
|
137
|
+
*
|
|
138
|
+
* Validates whether a template is available for the specified component name.
|
|
139
|
+
* Case-sensitive - expects kebab-case.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} componentKebab - Component name in kebab-case
|
|
142
|
+
* @returns {boolean} True if template exists, false otherwise
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* if (hasComponent('button')) {
|
|
146
|
+
* console.log('Button template is available');
|
|
147
|
+
* }
|
|
148
|
+
*/
|
|
149
|
+
export function hasComponent(componentKebab) {
|
|
150
|
+
return componentKebab in COMPONENT_TEMPLATES;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get all components that require React imports
|
|
155
|
+
*
|
|
156
|
+
* Returns an array of component names that need React hooks.
|
|
157
|
+
* Useful for documenting which components have state management.
|
|
158
|
+
*
|
|
159
|
+
* @returns {string[]} Array of component names that require React imports
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* const statefulComponents = getComponentsWithImports();
|
|
163
|
+
* // Returns ['modal', 'input', 'checkbox', 'dropdown', 'switch', 'progress']
|
|
164
|
+
*/
|
|
165
|
+
export function getComponentsWithImports() {
|
|
166
|
+
return Object.keys(COMPONENT_IMPORTS);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get component count
|
|
171
|
+
*
|
|
172
|
+
* Returns the total number of available component templates.
|
|
173
|
+
*
|
|
174
|
+
* @returns {number} Total number of components
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* console.log(`${getComponentCount()} components available`);
|
|
178
|
+
* // Output: "15 components available"
|
|
179
|
+
*/
|
|
180
|
+
export function getComponentCount() {
|
|
181
|
+
return Object.keys(COMPONENT_TEMPLATES).length;
|
|
182
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { getDirname } from "./utils/path-utils.js";
|
|
4
|
+
import { COLOR_SCHEMES } from "./generators/color-schemes.js";
|
|
5
|
+
import { applyCustomColors } from "./generators/color-utils.js";
|
|
6
|
+
|
|
7
|
+
const __dirname = getDirname(import.meta.url);
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// CONSTANTS
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
// Default colors
|
|
14
|
+
const DEFAULT_PRIMARY_COLOR = "#667eea";
|
|
15
|
+
const DEFAULT_SECONDARY_COLOR = "#764ba2";
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// PATH UTILITIES
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get template path for React components
|
|
23
|
+
* @param {string} component - Component name
|
|
24
|
+
* @returns {string} Absolute path to template
|
|
25
|
+
*/
|
|
26
|
+
function getReactTemplatePath(component) {
|
|
27
|
+
return path.join(__dirname, "..", "templates-react", component);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// FILE READING
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read React component file (.jsx)
|
|
36
|
+
* @param {string} component - Component name
|
|
37
|
+
* @param {string} filename - File name
|
|
38
|
+
* @returns {Promise<string>} File content
|
|
39
|
+
*/
|
|
40
|
+
async function readReactFile(component, filename) {
|
|
41
|
+
const templatePath = getReactTemplatePath(component);
|
|
42
|
+
const filePath = path.join(templatePath, filename);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
return await fs.readFile(filePath, "utf-8");
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error.code === "ENOENT") {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read and process component template files
|
|
56
|
+
* @param {string} component - Component name (kebab-case)
|
|
57
|
+
* @param {string} componentName - Component name (PascalCase)
|
|
58
|
+
* @param {string} primaryColor - Primary color
|
|
59
|
+
* @param {string} secondaryColor - Secondary color
|
|
60
|
+
* @returns {Promise<Object>} Processed JSX and CSS content
|
|
61
|
+
*/
|
|
62
|
+
async function readComponentFiles(component, componentName, primaryColor, secondaryColor) {
|
|
63
|
+
const jsxContent = await readReactFile(component, `${componentName}.jsx`);
|
|
64
|
+
let cssContent = await readReactFile(component, `${componentName}.css`);
|
|
65
|
+
|
|
66
|
+
if (!jsxContent) {
|
|
67
|
+
throw new Error(`React template not found for component: ${component}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Apply custom colors to CSS
|
|
71
|
+
if (cssContent && (primaryColor || secondaryColor)) {
|
|
72
|
+
cssContent = applyCustomColors(cssContent, primaryColor, secondaryColor);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { jsxContent, cssContent };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// FILE WRITING
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Write component files to directory
|
|
84
|
+
* @param {string} componentDir - Component directory path
|
|
85
|
+
* @param {string} componentName - Component name (PascalCase)
|
|
86
|
+
* @param {string} jsxContent - JSX file content
|
|
87
|
+
* @param {string} cssContent - CSS file content
|
|
88
|
+
*/
|
|
89
|
+
async function writeComponentFiles(componentDir, componentName, jsxContent, cssContent) {
|
|
90
|
+
await fs.writeFile(
|
|
91
|
+
path.join(componentDir, `${componentName}.jsx`),
|
|
92
|
+
jsxContent,
|
|
93
|
+
"utf-8"
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (cssContent) {
|
|
97
|
+
await fs.writeFile(
|
|
98
|
+
path.join(componentDir, `${componentName}.css`),
|
|
99
|
+
cssContent,
|
|
100
|
+
"utf-8"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// PROJECT STRUCTURE
|
|
107
|
+
// ============================================================================
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create React project structure
|
|
111
|
+
* @param {string} outputDir - Output directory path
|
|
112
|
+
* @returns {Promise<Object>} Directory paths
|
|
113
|
+
*/
|
|
114
|
+
async function createReactProjectStructure(outputDir) {
|
|
115
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
116
|
+
|
|
117
|
+
const srcDir = path.join(outputDir, "src");
|
|
118
|
+
await fs.mkdir(srcDir, { recursive: true });
|
|
119
|
+
|
|
120
|
+
const componentsDir = path.join(srcDir, "components");
|
|
121
|
+
await fs.mkdir(componentsDir, { recursive: true });
|
|
122
|
+
|
|
123
|
+
return { outputDir, srcDir, componentsDir };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// UTILITY FUNCTIONS
|
|
128
|
+
// ============================================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Convert component name to PascalCase
|
|
132
|
+
* @param {string} name - Component name (kebab-case)
|
|
133
|
+
* @returns {string} PascalCase name
|
|
134
|
+
*/
|
|
135
|
+
function toPascalCase(name) {
|
|
136
|
+
return name
|
|
137
|
+
.split("-")
|
|
138
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
139
|
+
.join("");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolve color scheme to actual colors
|
|
144
|
+
* @param {Object} options - Color options
|
|
145
|
+
* @returns {Object} Resolved colors
|
|
146
|
+
*/
|
|
147
|
+
function resolveColors(options) {
|
|
148
|
+
const { colorScheme, primaryColor, secondaryColor } = options;
|
|
149
|
+
|
|
150
|
+
let finalPrimaryColor = primaryColor || DEFAULT_PRIMARY_COLOR;
|
|
151
|
+
let finalSecondaryColor = secondaryColor || DEFAULT_SECONDARY_COLOR;
|
|
152
|
+
|
|
153
|
+
if (colorScheme && COLOR_SCHEMES[colorScheme]) {
|
|
154
|
+
const scheme = COLOR_SCHEMES[colorScheme];
|
|
155
|
+
finalPrimaryColor = scheme.primary;
|
|
156
|
+
finalSecondaryColor = scheme.secondary;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { finalPrimaryColor, finalSecondaryColor };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// EXPORTS
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
readComponentFiles,
|
|
168
|
+
writeComponentFiles,
|
|
169
|
+
createReactProjectStructure,
|
|
170
|
+
toPascalCase,
|
|
171
|
+
resolveColors,
|
|
172
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { sanitizeFilename } from "./generators/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
readComponentFiles,
|
|
6
|
+
writeComponentFiles,
|
|
7
|
+
createReactProjectStructure,
|
|
8
|
+
toPascalCase,
|
|
9
|
+
resolveColors,
|
|
10
|
+
} from "./react-file-operations.js";
|
|
11
|
+
import {
|
|
12
|
+
generateAppJsx,
|
|
13
|
+
generateIndexJs,
|
|
14
|
+
generateIndexHtml,
|
|
15
|
+
generatePackageJson,
|
|
16
|
+
generateGitignore,
|
|
17
|
+
generateViteConfig,
|
|
18
|
+
generateReadme,
|
|
19
|
+
} from "./react-templates.js";
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// CONSTANTS
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
// Valid React components
|
|
26
|
+
export const VALID_REACT_COMPONENTS = [
|
|
27
|
+
"alert",
|
|
28
|
+
"badge",
|
|
29
|
+
"button",
|
|
30
|
+
"card",
|
|
31
|
+
"checkbox",
|
|
32
|
+
"counter",
|
|
33
|
+
"dropdown",
|
|
34
|
+
"form",
|
|
35
|
+
"input",
|
|
36
|
+
"modal",
|
|
37
|
+
"navbar",
|
|
38
|
+
"progress",
|
|
39
|
+
"switch",
|
|
40
|
+
"todo-list",
|
|
41
|
+
"tooltip",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// VALIDATORS
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate React component name
|
|
50
|
+
* @param {string} component - Component name to validate
|
|
51
|
+
* @throws {Error} If component is invalid
|
|
52
|
+
*/
|
|
53
|
+
function validateComponent(component) {
|
|
54
|
+
if (!VALID_REACT_COMPONENTS.includes(component)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Invalid React component: ${component}. Must be one of: ${VALID_REACT_COMPONENTS.join(", ")}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// MAIN EXPORT FUNCTIONS
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate React component files
|
|
67
|
+
* @param {Object} options - Generation options
|
|
68
|
+
* @returns {Promise<string>} Path to generated directory
|
|
69
|
+
*/
|
|
70
|
+
async function generateReactTemplate(options) {
|
|
71
|
+
const {
|
|
72
|
+
component,
|
|
73
|
+
name,
|
|
74
|
+
colorScheme,
|
|
75
|
+
primaryColor,
|
|
76
|
+
secondaryColor,
|
|
77
|
+
darkMode,
|
|
78
|
+
lazyLoad = false,
|
|
79
|
+
optimizeBuild = false,
|
|
80
|
+
} = options;
|
|
81
|
+
|
|
82
|
+
// Resolve colors
|
|
83
|
+
const { finalPrimaryColor, finalSecondaryColor } = resolveColors({
|
|
84
|
+
colorScheme,
|
|
85
|
+
primaryColor,
|
|
86
|
+
secondaryColor,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Security: Validate component name
|
|
90
|
+
validateComponent(component);
|
|
91
|
+
|
|
92
|
+
// Security: Sanitize name to prevent path traversal
|
|
93
|
+
const safeName = sanitizeFilename(name);
|
|
94
|
+
if (!safeName || safeName.length === 0) {
|
|
95
|
+
throw new Error("Invalid name provided");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Create project structure
|
|
99
|
+
const outputDir = path.join(process.cwd(), safeName);
|
|
100
|
+
const { srcDir, componentsDir } = await createReactProjectStructure(outputDir);
|
|
101
|
+
|
|
102
|
+
// Get component name in PascalCase
|
|
103
|
+
const componentName = toPascalCase(component);
|
|
104
|
+
const componentDir = path.join(componentsDir, componentName);
|
|
105
|
+
await fs.mkdir(componentDir, { recursive: true });
|
|
106
|
+
|
|
107
|
+
// Read and process component files
|
|
108
|
+
const { jsxContent, cssContent } = await readComponentFiles(
|
|
109
|
+
component,
|
|
110
|
+
componentName,
|
|
111
|
+
finalPrimaryColor,
|
|
112
|
+
finalSecondaryColor
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Write component files
|
|
116
|
+
await writeComponentFiles(componentDir, componentName, jsxContent, cssContent);
|
|
117
|
+
|
|
118
|
+
// Create App.jsx (with optional lazy loading)
|
|
119
|
+
const appContent = generateAppJsx(componentName, component, { lazyLoad });
|
|
120
|
+
await fs.writeFile(path.join(srcDir, "App.jsx"), appContent, "utf-8");
|
|
121
|
+
|
|
122
|
+
// Create index.jsx
|
|
123
|
+
const indexContent = generateIndexJs();
|
|
124
|
+
await fs.writeFile(path.join(srcDir, "index.jsx"), indexContent, "utf-8");
|
|
125
|
+
|
|
126
|
+
// Create index.html
|
|
127
|
+
const htmlContent = generateIndexHtml(safeName);
|
|
128
|
+
await fs.writeFile(path.join(outputDir, "index.html"), htmlContent, "utf-8");
|
|
129
|
+
|
|
130
|
+
// Create package.json
|
|
131
|
+
const packageJson = generatePackageJson(safeName);
|
|
132
|
+
await fs.writeFile(
|
|
133
|
+
path.join(outputDir, "package.json"),
|
|
134
|
+
JSON.stringify(packageJson, null, 2),
|
|
135
|
+
"utf-8"
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Create .gitignore
|
|
139
|
+
const gitignoreContent = generateGitignore();
|
|
140
|
+
await fs.writeFile(path.join(outputDir, ".gitignore"), gitignoreContent, "utf-8");
|
|
141
|
+
|
|
142
|
+
// Create vite.config.js (with optional optimizations)
|
|
143
|
+
const viteConfig = generateViteConfig(optimizeBuild);
|
|
144
|
+
await fs.writeFile(path.join(outputDir, "vite.config.js"), viteConfig, "utf-8");
|
|
145
|
+
|
|
146
|
+
// Create README.md
|
|
147
|
+
const readmeContent = generateReadme(safeName, componentName);
|
|
148
|
+
await fs.writeFile(path.join(outputDir, "README.md"), readmeContent, "utf-8");
|
|
149
|
+
|
|
150
|
+
return outputDir;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Add React component only (without full project structure)
|
|
155
|
+
* @param {Object} options - Generation options
|
|
156
|
+
* @returns {Promise<string>} Path to generated component directory
|
|
157
|
+
*/
|
|
158
|
+
async function addReactComponentOnly(options) {
|
|
159
|
+
const {
|
|
160
|
+
component,
|
|
161
|
+
outputDir = process.cwd(),
|
|
162
|
+
colorScheme,
|
|
163
|
+
primaryColor,
|
|
164
|
+
secondaryColor,
|
|
165
|
+
} = options;
|
|
166
|
+
|
|
167
|
+
// Resolve colors
|
|
168
|
+
const { finalPrimaryColor, finalSecondaryColor } = resolveColors({
|
|
169
|
+
colorScheme,
|
|
170
|
+
primaryColor,
|
|
171
|
+
secondaryColor,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Security: Validate component name
|
|
175
|
+
validateComponent(component);
|
|
176
|
+
|
|
177
|
+
// Get component name in PascalCase
|
|
178
|
+
const componentName = toPascalCase(component);
|
|
179
|
+
const componentDir = path.join(outputDir, componentName);
|
|
180
|
+
|
|
181
|
+
// Check if component already exists
|
|
182
|
+
try {
|
|
183
|
+
await fs.access(componentDir);
|
|
184
|
+
throw new Error(`Component directory already exists: ${componentDir}`);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error.code !== "ENOENT") {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await fs.mkdir(componentDir, { recursive: true });
|
|
192
|
+
|
|
193
|
+
// Read and process component files
|
|
194
|
+
const { jsxContent, cssContent } = await readComponentFiles(
|
|
195
|
+
component,
|
|
196
|
+
componentName,
|
|
197
|
+
finalPrimaryColor,
|
|
198
|
+
finalSecondaryColor
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Write component files
|
|
202
|
+
await writeComponentFiles(componentDir, componentName, jsxContent, cssContent);
|
|
203
|
+
|
|
204
|
+
console.log(`✓ Created ${componentName} component in ${componentDir}`);
|
|
205
|
+
console.log(`\nFiles created:`);
|
|
206
|
+
console.log(` - ${componentName}.jsx`);
|
|
207
|
+
console.log(` - ${componentName}.css`);
|
|
208
|
+
console.log(`\nUsage example:`);
|
|
209
|
+
console.log(` import ${componentName} from './${componentName}/${componentName}';`);
|
|
210
|
+
console.log(` import './${componentName}/${componentName}.css';`);
|
|
211
|
+
|
|
212
|
+
return componentDir;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// EXPORTS
|
|
217
|
+
// ============================================================================
|
|
218
|
+
|
|
219
|
+
export { generateReactTemplate, addReactComponentOnly };
|