create-template-html-css 2.0.3 → 2.1.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +305 -0
  2. package/HTML-VS-REACT.md +289 -0
  3. package/QUICKSTART-REACT.md +293 -0
  4. package/REACT-SUPPORT-SUMMARY.md +235 -0
  5. package/README.md +193 -12
  6. package/bin/cli.js +98 -759
  7. package/bin/commands/create.js +272 -0
  8. package/bin/commands/gallery.js +42 -0
  9. package/bin/commands/insert.js +123 -0
  10. package/bin/commands/list.js +73 -0
  11. package/package.json +10 -3
  12. package/src/component-choices.js +7 -0
  13. package/src/components-registry.js +112 -0
  14. package/src/format-utils.js +49 -0
  15. package/src/generator.js +83 -594
  16. package/src/generators/color-schemes.js +78 -0
  17. package/src/generators/color-utils.js +108 -0
  18. package/src/generators/component-filters.js +151 -0
  19. package/src/generators/html-generators.js +180 -0
  20. package/src/generators/validation.js +43 -0
  21. package/src/index.js +2 -1
  22. package/src/inserter.js +55 -233
  23. package/src/inserters/backup-utils.js +20 -0
  24. package/src/inserters/component-loader.js +68 -0
  25. package/src/inserters/html-utils.js +31 -0
  26. package/src/inserters/indentation-utils.js +90 -0
  27. package/src/inserters/validation-utils.js +49 -0
  28. package/src/react-component-choices.js +45 -0
  29. package/src/react-file-operations.js +172 -0
  30. package/src/react-generator.js +208 -0
  31. package/src/react-templates.js +350 -0
  32. package/src/utils/file-utils.js +97 -0
  33. package/src/utils/path-utils.js +32 -0
  34. package/src/utils/string-utils.js +51 -0
  35. package/src/utils/template-loader.js +91 -0
  36. package/templates/_shared/PATTERNS.md +246 -0
  37. package/templates/_shared/README.md +74 -0
  38. package/templates/_shared/base.css +18 -0
  39. package/templates/blackjack/index.html +1 -1
  40. package/templates/blackjack/script.js +9 -9
  41. package/templates/breakout/index.html +1 -1
  42. package/templates/breakout/script.js +6 -6
  43. package/templates/connect-four/index.html +1 -1
  44. package/templates/connect-four/script.js +5 -5
  45. package/templates/dice-game/index.html +1 -1
  46. package/templates/dice-game/script.js +20 -20
  47. package/templates/flappy-bird/index.html +1 -1
  48. package/templates/flappy-bird/script.js +10 -10
  49. package/templates/pong/index.html +1 -1
  50. package/templates/pong/script.js +8 -8
  51. package/templates/skeleton/index.html +4 -4
  52. package/templates/slot-machine/index.html +1 -1
  53. package/templates/slot-machine/script.js +6 -6
  54. package/templates/tetris/index.html +1 -1
  55. package/templates/tetris/script.js +5 -5
  56. package/templates-react/README.md +126 -0
  57. package/templates-react/button/Button.css +88 -0
  58. package/templates-react/button/Button.example.jsx +40 -0
  59. package/templates-react/button/Button.jsx +29 -0
  60. package/templates-react/card/Card.css +86 -0
  61. package/templates-react/card/Card.example.jsx +49 -0
  62. package/templates-react/card/Card.jsx +35 -0
  63. package/templates-react/counter/Counter.css +99 -0
  64. package/templates-react/counter/Counter.example.jsx +45 -0
  65. package/templates-react/counter/Counter.jsx +70 -0
  66. package/templates-react/form/Form.css +128 -0
  67. package/templates-react/form/Form.example.jsx +65 -0
  68. package/templates-react/form/Form.jsx +125 -0
  69. package/templates-react/modal/Modal.css +152 -0
  70. package/templates-react/modal/Modal.example.jsx +90 -0
  71. package/templates-react/modal/Modal.jsx +46 -0
  72. package/templates-react/todo-list/TodoList.css +236 -0
  73. package/templates-react/todo-list/TodoList.example.jsx +15 -0
  74. package/templates-react/todo-list/TodoList.jsx +84 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Color scheme presets for templates
3
+ */
4
+
5
+ export const COLOR_SCHEMES = {
6
+ vibrant: {
7
+ name: "Vibrant",
8
+ primary: "#FF6B6B",
9
+ secondary: "#4ECDC4",
10
+ description: "Bold reds and teals - energetic and modern",
11
+ },
12
+ pastel: {
13
+ name: "Pastel",
14
+ primary: "#FFB3BA",
15
+ secondary: "#FFDFBA",
16
+ description: "Soft pinks and peaches - calm and friendly",
17
+ },
18
+ ocean: {
19
+ name: "Ocean",
20
+ primary: "#0066CC",
21
+ secondary: "#00CCFF",
22
+ description: "Deep blues and cyans - professional and cool",
23
+ },
24
+ sunset: {
25
+ name: "Sunset",
26
+ primary: "#FF6B35",
27
+ secondary: "#FFA500",
28
+ description: "Warm oranges and reds - energetic glow",
29
+ },
30
+ forest: {
31
+ name: "Forest",
32
+ primary: "#2D6A4F",
33
+ secondary: "#40916C",
34
+ description: "Green tones - natural and organic",
35
+ },
36
+ purple: {
37
+ name: "Purple",
38
+ primary: "#7209B7",
39
+ secondary: "#B5179E",
40
+ description: "Deep purples - elegant and premium",
41
+ },
42
+ minimal: {
43
+ name: "Minimal",
44
+ primary: "#1A1A1A",
45
+ secondary: "#666666",
46
+ description: "Grays and blacks - clean and simple",
47
+ },
48
+ coral: {
49
+ name: "Coral",
50
+ primary: "#FF6B9D",
51
+ secondary: "#FFA07A",
52
+ description: "Coral pinks and salmon - warm and playful",
53
+ },
54
+ teal: {
55
+ name: "Teal",
56
+ primary: "#008B8B",
57
+ secondary: "#20B2AA",
58
+ description: "Teal hues - balanced and professional",
59
+ },
60
+ neon: {
61
+ name: "Neon",
62
+ primary: "#FF006E",
63
+ secondary: "#00D9FF",
64
+ description: "Bright neon colors - bold and futuristic",
65
+ },
66
+ };
67
+
68
+ /**
69
+ * Get color scheme by preset name
70
+ * @param {string} schemeName - Name of the color scheme
71
+ * @returns {Object|null} Color scheme object or null if not found
72
+ */
73
+ export function getColorScheme(schemeName) {
74
+ if (COLOR_SCHEMES[schemeName]) {
75
+ return COLOR_SCHEMES[schemeName];
76
+ }
77
+ return null;
78
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Color utility functions for CSS styling
3
+ */
4
+
5
+ /**
6
+ * Convert hex color to RGB values
7
+ * @param {string} hex - Hex color code (e.g., "#FF6B6B")
8
+ * @returns {string} RGB values as string (e.g., "255, 107, 107")
9
+ */
10
+ export function hexToRgb(hex) {
11
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
12
+ if (result) {
13
+ const r = parseInt(result[1], 16);
14
+ const g = parseInt(result[2], 16);
15
+ const b = parseInt(result[3], 16);
16
+ return `${r}, ${g}, ${b}`;
17
+ }
18
+ return "102, 126, 234"; // fallback to default primary
19
+ }
20
+
21
+ /**
22
+ * Apply custom colors to CSS content using CSS variables
23
+ * @param {string} cssContent - Original CSS content
24
+ * @param {string} primaryColor - Primary color hex code
25
+ * @param {string} secondaryColor - Secondary color hex code
26
+ * @returns {string} Modified CSS with custom colors
27
+ */
28
+ export function applyCustomColors(cssContent, primaryColor, secondaryColor) {
29
+ if (!primaryColor && !secondaryColor) {
30
+ return cssContent;
31
+ }
32
+
33
+ // Create CSS variable overrides
34
+ let colorVariables = ":root {\n";
35
+
36
+ if (primaryColor) {
37
+ colorVariables += ` --primary-color: ${primaryColor};\n`;
38
+ // Also add as root variable that can override gradients
39
+ colorVariables += ` --primary-rgb: ${hexToRgb(primaryColor)};\n`;
40
+ }
41
+
42
+ if (secondaryColor) {
43
+ colorVariables += ` --secondary-color: ${secondaryColor};\n`;
44
+ colorVariables += ` --secondary-rgb: ${hexToRgb(secondaryColor)};\n`;
45
+ }
46
+
47
+ colorVariables += "}\n\n";
48
+
49
+ // If CSS doesn't have :root, add it
50
+ if (!cssContent.includes(":root")) {
51
+ return colorVariables + cssContent;
52
+ }
53
+
54
+ // Replace existing :root with extended one
55
+ return cssContent.replace(/:root\s*{/, colorVariables.trim() + "\n\n:root {");
56
+ }
57
+
58
+ /**
59
+ * Add dark mode styles to CSS
60
+ * @param {string} cssContent - Original CSS content
61
+ * @returns {string} CSS with dark mode media query added
62
+ */
63
+ export function addDarkModeStyles(cssContent) {
64
+ if (cssContent.includes("prefers-color-scheme")) {
65
+ return cssContent; // Already has dark mode
66
+ }
67
+
68
+ // Create dark mode media query
69
+ const darkModeStyles = `
70
+ /* Dark Mode Support */
71
+ @media (prefers-color-scheme: dark) {
72
+ :root {
73
+ --bg-primary: #1a1a1a;
74
+ --bg-secondary: #2d2d2d;
75
+ --text-primary: #ffffff;
76
+ --text-secondary: #b0b0b0;
77
+ --border-color: #404040;
78
+ }
79
+
80
+ body {
81
+ background-color: var(--bg-primary, #1a1a1a);
82
+ color: var(--text-primary, #ffffff);
83
+ }
84
+
85
+ .container,
86
+ .card,
87
+ .modal-content,
88
+ .form-container {
89
+ background-color: var(--bg-secondary, #2d2d2d);
90
+ color: var(--text-primary, #ffffff);
91
+ border-color: var(--border-color, #404040);
92
+ }
93
+
94
+ input,
95
+ textarea,
96
+ select {
97
+ background-color: var(--bg-primary, #1a1a1a);
98
+ color: var(--text-primary, #ffffff);
99
+ border-color: var(--border-color, #404040);
100
+ }
101
+
102
+ input::placeholder {
103
+ color: var(--text-secondary, #b0b0b0);
104
+ }
105
+ }`;
106
+
107
+ return cssContent + darkModeStyles;
108
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Component filtering utilities
3
+ * Filters variations for buttons, cards, and spinners
4
+ */
5
+
6
+ /**
7
+ * Filter button variations based on user selection
8
+ * @param {string} htmlContent - Original HTML content
9
+ * @param {string} buttonVariations - "all" or "select"
10
+ * @param {Array<string>} selectedButtons - Array of selected button types
11
+ * @returns {string} Filtered HTML content
12
+ */
13
+ export function filterButtonVariations(
14
+ htmlContent,
15
+ buttonVariations,
16
+ selectedButtons
17
+ ) {
18
+ if (
19
+ buttonVariations === "all" ||
20
+ !selectedButtons ||
21
+ selectedButtons.length === 0
22
+ ) {
23
+ return htmlContent; // Return all buttons
24
+ }
25
+
26
+ // Map selection to button classes
27
+ const buttonMap = {
28
+ primary: "btn-primary",
29
+ secondary: "btn-secondary",
30
+ success: "btn-success",
31
+ danger: "btn-danger",
32
+ outlined: "btn-outlined",
33
+ disabled: "disabled",
34
+ };
35
+
36
+ let filteredHtml = htmlContent;
37
+
38
+ // Remove buttons that weren't selected
39
+ for (const [key, className] of Object.entries(buttonMap)) {
40
+ if (!selectedButtons.includes(key)) {
41
+ // Remove button and its comment
42
+ const patterns = [
43
+ new RegExp(
44
+ `\\s*<!-- ${key.charAt(0).toUpperCase() + key.slice(1)} Button -->\\s*\\n\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`,
45
+ "gis"
46
+ ),
47
+ new RegExp(
48
+ `\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`,
49
+ "gis"
50
+ ),
51
+ ];
52
+
53
+ patterns.forEach((pattern) => {
54
+ filteredHtml = filteredHtml.replace(pattern, "\n ");
55
+ });
56
+ }
57
+ }
58
+
59
+ return filteredHtml;
60
+ }
61
+
62
+ /**
63
+ * Filter card variations based on user selection
64
+ * @param {string} htmlContent - Original HTML content
65
+ * @param {string} cardVariations - "all" or "select"
66
+ * @param {Array<string>} selectedCards - Array of selected card types
67
+ * @returns {string} Filtered HTML content
68
+ */
69
+ export function filterCardVariations(
70
+ htmlContent,
71
+ cardVariations,
72
+ selectedCards
73
+ ) {
74
+ if (
75
+ cardVariations === "all" ||
76
+ !selectedCards ||
77
+ selectedCards.length === 0
78
+ ) {
79
+ return htmlContent; // Return all cards
80
+ }
81
+
82
+ const cardComments = {
83
+ modern: "Card 1 - Basic",
84
+ premium: "Card 2 - With Price",
85
+ blog: "Card 3 - With Tags",
86
+ minimal: "Card 4 - Minimal",
87
+ user: "Card 5 - With Avatar",
88
+ interactive: "Card 6 - Interactive",
89
+ };
90
+
91
+ let filteredHtml = htmlContent;
92
+
93
+ // Remove cards that weren't selected
94
+ for (const [key, comment] of Object.entries(cardComments)) {
95
+ if (!selectedCards.includes(key)) {
96
+ // Remove entire card div including comment
97
+ const pattern = new RegExp(
98
+ `\\s*<!-- ${comment.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")} -->\\s*\\n\\s*<div class="card[^>]*>.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`,
99
+ "gis"
100
+ );
101
+ filteredHtml = filteredHtml.replace(pattern, "\n ");
102
+ }
103
+ }
104
+
105
+ return filteredHtml;
106
+ }
107
+
108
+ /**
109
+ * Filter spinner variations based on user selection
110
+ * @param {string} htmlContent - Original HTML content
111
+ * @param {string} spinnerVariations - "all" or "select"
112
+ * @param {Array<string>} selectedSpinners - Array of selected spinner types
113
+ * @returns {string} Filtered HTML content
114
+ */
115
+ export function filterSpinnerVariations(
116
+ htmlContent,
117
+ spinnerVariations,
118
+ selectedSpinners
119
+ ) {
120
+ if (
121
+ spinnerVariations === "all" ||
122
+ !selectedSpinners ||
123
+ selectedSpinners.length === 0
124
+ ) {
125
+ return htmlContent; // Return all spinners
126
+ }
127
+
128
+ const spinnerTypes = {
129
+ circle: "Circle Spinner",
130
+ dots: "Bouncing Dots",
131
+ pulse: "Pulse Loader",
132
+ bars: "Bar Loader",
133
+ gradient: "Gradient Ring",
134
+ };
135
+
136
+ let filteredHtml = htmlContent;
137
+
138
+ // Remove spinners that weren't selected
139
+ for (const [key, title] of Object.entries(spinnerTypes)) {
140
+ if (!selectedSpinners.includes(key)) {
141
+ // Remove entire spinner-group div
142
+ const pattern = new RegExp(
143
+ `\\s*<!-- ${title} Spinner -->\\s*\\n\\s*<div class="spinner-group">.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`,
144
+ "gis"
145
+ );
146
+ filteredHtml = filteredHtml.replace(pattern, "\n ");
147
+ }
148
+ }
149
+
150
+ return filteredHtml;
151
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * HTML content generators for navigation and forms
3
+ */
4
+
5
+ import { textToId, parseCommaSeparated, parseKeyValuePairs } from "../utils/string-utils.js";
6
+
7
+ /**
8
+ * Generate custom navigation items based on user input
9
+ * @param {string} htmlContent - Original HTML content
10
+ * @param {string} navItems - Comma-separated list of navigation items
11
+ * @returns {string} HTML with custom navigation
12
+ */
13
+ export function generateNavigationItems(htmlContent, navItems) {
14
+ // Parse the comma-separated navigation items
15
+ const items = parseCommaSeparated(navItems);
16
+
17
+ // Generate navigation HTML
18
+ let navHtml = "";
19
+ items.forEach((item, index) => {
20
+ const itemId = textToId(item);
21
+ const activeClass = index === 0 ? " active" : "";
22
+ navHtml += ` <li class="nav-item">
23
+ <a href="#${itemId}" class="nav-link${activeClass}">${item}</a>
24
+ </li>\n`;
25
+ });
26
+
27
+ // Replace the navigation items in the HTML
28
+ const navMenuRegex = /<ul class="nav-menu"[^>]*>[\s\S]*?<\/ul>/;
29
+ const replacement = `<ul class="nav-menu" id="navMenu">
30
+ ${navHtml} </ul>`;
31
+
32
+ htmlContent = htmlContent.replace(navMenuRegex, replacement);
33
+
34
+ // Generate sections for each navigation item
35
+ let sectionsHtml = "";
36
+ items.forEach((item) => {
37
+ const itemId = textToId(item);
38
+ sectionsHtml += ` <section id="${itemId}" class="section">
39
+ <h1>${item}</h1>
40
+ <p>Content for ${item} section</p>
41
+ </section>
42
+
43
+ `;
44
+ });
45
+
46
+ // Replace the sections in the HTML
47
+ const mainContentRegex = /<main class="main-content">[\s\S]*?<\/main>/;
48
+ const sectionsReplacement = `<main class="main-content">
49
+ ${sectionsHtml} </main>`;
50
+
51
+ htmlContent = htmlContent.replace(mainContentRegex, sectionsReplacement);
52
+
53
+ return htmlContent;
54
+ }
55
+
56
+ /**
57
+ * Generate custom form fields based on user selection
58
+ * @param {string} htmlContent - Original HTML content
59
+ * @param {Array<string>} formFields - Array of standard field types
60
+ * @param {string} customFormFields - Custom fields in format "type:label"
61
+ * @returns {string} HTML with custom form fields
62
+ */
63
+ export function generateFormFields(htmlContent, formFields, customFormFields) {
64
+ let fieldsHtml = "";
65
+
66
+ // Add standard fields
67
+ formFields.forEach((field) => {
68
+ switch (field) {
69
+ case "name":
70
+ fieldsHtml += ` <div class="form-group">
71
+ <label for="name">Full Name</label>
72
+ <input type="text" id="name" name="name" required>
73
+ </div>
74
+
75
+ `;
76
+ break;
77
+ case "email":
78
+ fieldsHtml += ` <div class="form-group">
79
+ <label for="email">Email</label>
80
+ <input type="email" id="email" name="email" required>
81
+ </div>
82
+
83
+ `;
84
+ break;
85
+ case "phone":
86
+ fieldsHtml += ` <div class="form-group">
87
+ <label for="phone">Phone</label>
88
+ <input type="tel" id="phone" name="phone">
89
+ </div>
90
+
91
+ `;
92
+ break;
93
+ case "subject":
94
+ fieldsHtml += ` <div class="form-group">
95
+ <label for="subject">Subject</label>
96
+ <select id="subject" name="subject" required>
97
+ <option value="">Select a subject</option>
98
+ <option value="support">Technical Support</option>
99
+ <option value="sales">Sales</option>
100
+ <option value="general">General</option>
101
+ </select>
102
+ </div>
103
+
104
+ `;
105
+ break;
106
+ case "message":
107
+ fieldsHtml += ` <div class="form-group">
108
+ <label for="message">Message</label>
109
+ <textarea id="message" name="message" rows="5" required></textarea>
110
+ </div>
111
+
112
+ `;
113
+ break;
114
+ case "terms":
115
+ fieldsHtml += ` <div class="form-group checkbox-group">
116
+ <input type="checkbox" id="terms" name="terms" required>
117
+ <label for="terms">I agree to the terms of service</label>
118
+ </div>
119
+
120
+ `;
121
+ break;
122
+ }
123
+ });
124
+
125
+ // Add custom fields if provided
126
+ if (customFormFields && customFormFields.trim().length > 0) {
127
+ const customFields = parseKeyValuePairs(customFormFields);
128
+
129
+ customFields.forEach(({ key: type, value: label }) => {
130
+ const fieldId = textToId(label);
131
+
132
+ if (type === "textarea") {
133
+ fieldsHtml += ` <div class="form-group">
134
+ <label for="${fieldId}">${label}</label>
135
+ <textarea id="${fieldId}" name="${fieldId}" rows="5"></textarea>
136
+ </div>
137
+
138
+ `;
139
+ } else if (type === "checkbox") {
140
+ fieldsHtml += ` <div class="form-group checkbox-group">
141
+ <input type="checkbox" id="${fieldId}" name="${fieldId}">
142
+ <label for="${fieldId}">${label}</label>
143
+ </div>
144
+
145
+ `;
146
+ } else if (type === "select") {
147
+ fieldsHtml += ` <div class="form-group">
148
+ <label for="${fieldId}">${label}</label>
149
+ <select id="${fieldId}" name="${fieldId}">
150
+ <option value="">Select an option</option>
151
+ <option value="option1">Option 1</option>
152
+ <option value="option2">Option 2</option>
153
+ </select>
154
+ </div>
155
+
156
+ `;
157
+ } else {
158
+ // Default to input with specified type
159
+ fieldsHtml += ` <div class="form-group">
160
+ <label for="${fieldId}">${label}</label>
161
+ <input type="${type}" id="${fieldId}" name="${fieldId}">
162
+ </div>
163
+
164
+ `;
165
+ }
166
+ });
167
+ }
168
+
169
+ // Replace the form fields in the HTML
170
+ const formRegex = /<form class="form"[^>]*>[\s\S]*?<button type="submit"/;
171
+ const replacement = `<form class="form" id="contactForm">
172
+ <h1 class="form-title">Contact Us</h1>
173
+ <p class="form-subtitle">Fill in the details and we'll get back to you soon</p>
174
+
175
+ ${fieldsHtml} <button type="submit"`;
176
+
177
+ htmlContent = htmlContent.replace(formRegex, replacement);
178
+
179
+ return htmlContent;
180
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Component validation and security utilities
3
+ */
4
+
5
+ import { VALID_COMPONENTS } from "../components-registry.js";
6
+ import { sanitizeForFilename } from "../utils/string-utils.js";
7
+
8
+ export { VALID_COMPONENTS };
9
+
10
+ /**
11
+ * Sanitize filename to prevent path traversal
12
+ * @param {string} filename - The filename to sanitize
13
+ * @returns {string|null} Sanitized filename or null if invalid
14
+ */
15
+ export function sanitizeFilename(filename) {
16
+ // Check for path traversal attempts
17
+ if (
18
+ filename.includes("/") ||
19
+ filename.includes("\\") ||
20
+ filename === ".." ||
21
+ filename.startsWith("../") ||
22
+ filename.startsWith("..\\") ||
23
+ filename.includes("/../") ||
24
+ filename.includes("\\..\\")
25
+ ) {
26
+ return null;
27
+ }
28
+
29
+ // Additional validation: ensure name contains at least one alphanumeric character
30
+ if (!filename || !/[a-zA-Z0-9]/.test(filename)) {
31
+ return null;
32
+ }
33
+
34
+ // Remove any remaining dangerous characters
35
+ const sanitized = filename.replace(/[<>:"|?*]/g, "").trim();
36
+
37
+ // Final check: ensure not empty after sanitization
38
+ if (!sanitized || sanitized.length === 0) {
39
+ return null;
40
+ }
41
+
42
+ return sanitized;
43
+ }
package/src/index.js CHANGED
@@ -1 +1,2 @@
1
- module.exports = { generateTemplate } = require("./generator");
1
+ export { generateTemplate } from "./generator.js";
2
+ export { generateReactTemplate } from "./react-generator.js";