create-ng-tailwind 3.0.1 → 4.0.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 +81 -344
- package/README.md +93 -157
- package/lib/cli/index.js +29 -3
- package/lib/cli/interactive.js +26 -1
- package/lib/managers/ProjectManager.js +0 -4
- package/lib/templates/base/components.js +243 -0
- package/lib/templates/base/index.js +207 -0
- package/lib/templates/base/infrastructure.js +314 -0
- package/lib/templates/base/linting.js +359 -0
- package/lib/templates/base/pwa.js +103 -0
- package/lib/templates/base/services.js +362 -0
- package/lib/templates/blog/app.js +250 -0
- package/lib/templates/blog/components.js +360 -0
- package/lib/templates/blog/i18n.js +77 -0
- package/lib/templates/blog/index.js +126 -0
- package/lib/templates/blog/pages.js +554 -0
- package/lib/templates/blog/services.js +390 -0
- package/lib/templates/dashboard/app.js +320 -0
- package/lib/templates/dashboard/charts.js +305 -0
- package/lib/templates/dashboard/components.js +410 -0
- package/lib/templates/dashboard/i18n.js +340 -0
- package/lib/templates/dashboard/index.js +141 -0
- package/lib/templates/dashboard/layout.js +310 -0
- package/lib/templates/dashboard/pages.js +681 -0
- package/lib/templates/ecommerce/app.js +315 -0
- package/lib/templates/ecommerce/components.js +496 -0
- package/lib/templates/ecommerce/i18n.js +389 -0
- package/lib/templates/ecommerce/index.js +152 -0
- package/lib/templates/ecommerce/layout.js +270 -0
- package/lib/templates/ecommerce/pages.js +969 -0
- package/lib/templates/ecommerce/services.js +300 -0
- package/lib/templates/index.js +12 -0
- package/lib/templates/landing/index.js +1117 -0
- package/lib/templates/portfolio/index.js +1160 -0
- package/lib/templates/saas/index.js +1371 -0
- package/lib/templates/starter/app.js +364 -0
- package/lib/templates/starter/i18n.js +856 -0
- package/lib/templates/starter/index.js +53 -4055
- package/lib/templates/starter/layout.js +852 -0
- package/lib/templates/starter/pages.js +1241 -0
- package/package.json +1 -1
- package/lib/templates/starter/features.js +0 -867
- package/lib/utils/ai-config.js +0 -641
- /package/lib/templates/{starter → base}/advanced-features.js +0 -0
- /package/lib/templates/{starter → base}/seo-assets.js +0 -0
- /package/lib/templates/{starter → base}/seo-features.js +0 -0
- /package/lib/templates/{starter → base}/ui-features.js +0 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
const fs = require("fs-extra");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create chart components (Bar, Line, Donut)
|
|
6
|
+
*/
|
|
7
|
+
async function createCharts(config) {
|
|
8
|
+
// Bar Chart Component (Pure CSS/SVG - no external dependencies)
|
|
9
|
+
const barChartComponent = `import { Component, Input, OnChanges } from '@angular/core';
|
|
10
|
+
import { DecimalPipe } from '@angular/common';
|
|
11
|
+
|
|
12
|
+
export interface BarChartData {
|
|
13
|
+
label: string;
|
|
14
|
+
value: number;
|
|
15
|
+
color?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@Component({
|
|
19
|
+
selector: 'app-chart-bar',
|
|
20
|
+
standalone: true,
|
|
21
|
+
imports: [DecimalPipe],
|
|
22
|
+
template: \`
|
|
23
|
+
<div class="w-full">
|
|
24
|
+
@if (title) {
|
|
25
|
+
<h3 class="mb-4 text-lg font-semibold text-gray-900">{{ title }}</h3>
|
|
26
|
+
}
|
|
27
|
+
<div class="space-y-3">
|
|
28
|
+
@for (item of data; track item.label) {
|
|
29
|
+
<div class="flex items-center gap-4">
|
|
30
|
+
<span class="w-24 truncate text-sm text-gray-600">{{ item.label }}</span>
|
|
31
|
+
<div class="flex-1">
|
|
32
|
+
<div class="h-8 w-full overflow-hidden rounded-lg bg-gray-100">
|
|
33
|
+
<div
|
|
34
|
+
class="flex h-full items-center justify-end rounded-lg px-2 transition-all duration-500"
|
|
35
|
+
[style.width.%]="getPercentage(item.value)"
|
|
36
|
+
[style.backgroundColor]="item.color || defaultColor">
|
|
37
|
+
<span class="text-xs font-medium text-white">{{ item.value | number }}</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
\`,
|
|
46
|
+
})
|
|
47
|
+
export class ChartBarComponent implements OnChanges {
|
|
48
|
+
@Input() data: BarChartData[] = [];
|
|
49
|
+
@Input() title?: string;
|
|
50
|
+
@Input() defaultColor = '#3b82f6';
|
|
51
|
+
|
|
52
|
+
private maxValue = 0;
|
|
53
|
+
|
|
54
|
+
ngOnChanges(): void {
|
|
55
|
+
this.maxValue = Math.max(...this.data.map((d) => d.value), 1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getPercentage(value: number): number {
|
|
59
|
+
return (value / this.maxValue) * 100;
|
|
60
|
+
}
|
|
61
|
+
}`;
|
|
62
|
+
|
|
63
|
+
await fs.writeFile(
|
|
64
|
+
path.join(
|
|
65
|
+
config.fullPath,
|
|
66
|
+
"src/app/shared/components/chart-bar/chart-bar.component.ts"
|
|
67
|
+
),
|
|
68
|
+
barChartComponent
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Line Chart Component (SVG-based)
|
|
72
|
+
const lineChartComponent = `import { Component, Input, OnChanges, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
|
73
|
+
|
|
74
|
+
export interface LineChartData {
|
|
75
|
+
label: string;
|
|
76
|
+
value: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Component({
|
|
80
|
+
selector: 'app-chart-line',
|
|
81
|
+
standalone: true,
|
|
82
|
+
template: \`
|
|
83
|
+
<div class="w-full">
|
|
84
|
+
@if (title) {
|
|
85
|
+
<h3 class="mb-4 text-lg font-semibold text-gray-900">{{ title }}</h3>
|
|
86
|
+
}
|
|
87
|
+
<div class="relative h-64 w-full">
|
|
88
|
+
<svg #chartSvg class="h-full w-full" [attr.viewBox]="'0 0 ' + width + ' ' + height">
|
|
89
|
+
<!-- Grid lines -->
|
|
90
|
+
@for (i of gridLines; track i) {
|
|
91
|
+
<line
|
|
92
|
+
[attr.x1]="padding"
|
|
93
|
+
[attr.y1]="padding + (i * chartHeight) / 4"
|
|
94
|
+
[attr.x2]="width - padding"
|
|
95
|
+
[attr.y2]="padding + (i * chartHeight) / 4"
|
|
96
|
+
stroke="#e5e7eb"
|
|
97
|
+
stroke-width="1"
|
|
98
|
+
/>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
<!-- Line path -->
|
|
102
|
+
<path
|
|
103
|
+
[attr.d]="linePath"
|
|
104
|
+
fill="none"
|
|
105
|
+
[attr.stroke]="lineColor"
|
|
106
|
+
stroke-width="3"
|
|
107
|
+
stroke-linecap="round"
|
|
108
|
+
stroke-linejoin="round"
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<!-- Area fill -->
|
|
112
|
+
<path
|
|
113
|
+
[attr.d]="areaPath"
|
|
114
|
+
[attr.fill]="lineColor"
|
|
115
|
+
fill-opacity="0.1"
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<!-- Data points -->
|
|
119
|
+
@for (point of points; track point.x) {
|
|
120
|
+
<circle
|
|
121
|
+
[attr.cx]="point.x"
|
|
122
|
+
[attr.cy]="point.y"
|
|
123
|
+
r="4"
|
|
124
|
+
[attr.fill]="lineColor"
|
|
125
|
+
class="transition-all hover:r-6"
|
|
126
|
+
/>
|
|
127
|
+
}
|
|
128
|
+
</svg>
|
|
129
|
+
|
|
130
|
+
<!-- X-axis labels -->
|
|
131
|
+
<div class="mt-2 flex justify-between px-8 text-xs text-gray-500">
|
|
132
|
+
@for (item of data; track item.label) {
|
|
133
|
+
<span>{{ item.label }}</span>
|
|
134
|
+
}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
\`,
|
|
139
|
+
})
|
|
140
|
+
export class ChartLineComponent implements OnChanges, AfterViewInit {
|
|
141
|
+
@ViewChild('chartSvg') chartSvg!: ElementRef;
|
|
142
|
+
|
|
143
|
+
@Input() data: LineChartData[] = [];
|
|
144
|
+
@Input() title?: string;
|
|
145
|
+
@Input() lineColor = '#3b82f6';
|
|
146
|
+
|
|
147
|
+
width = 400;
|
|
148
|
+
height = 200;
|
|
149
|
+
padding = 20;
|
|
150
|
+
chartHeight = 160;
|
|
151
|
+
gridLines = [0, 1, 2, 3, 4];
|
|
152
|
+
|
|
153
|
+
linePath = '';
|
|
154
|
+
areaPath = '';
|
|
155
|
+
points: { x: number; y: number }[] = [];
|
|
156
|
+
|
|
157
|
+
ngAfterViewInit(): void {
|
|
158
|
+
this.calculatePaths();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
ngOnChanges(): void {
|
|
162
|
+
this.calculatePaths();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private calculatePaths(): void {
|
|
166
|
+
if (this.data.length === 0) return;
|
|
167
|
+
|
|
168
|
+
const maxValue = Math.max(...this.data.map((d) => d.value), 1);
|
|
169
|
+
const stepX = (this.width - this.padding * 2) / (this.data.length - 1 || 1);
|
|
170
|
+
|
|
171
|
+
this.points = this.data.map((item, index) => ({
|
|
172
|
+
x: this.padding + index * stepX,
|
|
173
|
+
y: this.padding + this.chartHeight - (item.value / maxValue) * this.chartHeight,
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
if (this.points.length > 0) {
|
|
177
|
+
this.linePath = this.points
|
|
178
|
+
.map((p, i) => (i === 0 ? \`M \${p.x} \${p.y}\` : \`L \${p.x} \${p.y}\`))
|
|
179
|
+
.join(' ');
|
|
180
|
+
|
|
181
|
+
this.areaPath =
|
|
182
|
+
this.linePath +
|
|
183
|
+
\` L \${this.points[this.points.length - 1].x} \${this.padding + this.chartHeight}\` +
|
|
184
|
+
\` L \${this.points[0].x} \${this.padding + this.chartHeight} Z\`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}`;
|
|
188
|
+
|
|
189
|
+
await fs.writeFile(
|
|
190
|
+
path.join(
|
|
191
|
+
config.fullPath,
|
|
192
|
+
"src/app/shared/components/chart-line/chart-line.component.ts"
|
|
193
|
+
),
|
|
194
|
+
lineChartComponent
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Donut Chart Component (SVG-based)
|
|
198
|
+
const donutChartComponent = `import { Component, Input, OnChanges } from '@angular/core';
|
|
199
|
+
import { DecimalPipe } from '@angular/common';
|
|
200
|
+
|
|
201
|
+
export interface DonutChartData {
|
|
202
|
+
label: string;
|
|
203
|
+
value: number;
|
|
204
|
+
color: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@Component({
|
|
208
|
+
selector: 'app-chart-donut',
|
|
209
|
+
standalone: true,
|
|
210
|
+
imports: [DecimalPipe],
|
|
211
|
+
template: \`
|
|
212
|
+
<div class="flex flex-col items-center">
|
|
213
|
+
@if (title) {
|
|
214
|
+
<h3 class="mb-4 text-lg font-semibold text-gray-900">{{ title }}</h3>
|
|
215
|
+
}
|
|
216
|
+
<div class="relative">
|
|
217
|
+
<svg [attr.width]="size" [attr.height]="size" [attr.viewBox]="'0 0 ' + size + ' ' + size">
|
|
218
|
+
@for (segment of segments; track segment.label) {
|
|
219
|
+
<circle
|
|
220
|
+
[attr.cx]="center"
|
|
221
|
+
[attr.cy]="center"
|
|
222
|
+
[attr.r]="radius"
|
|
223
|
+
fill="none"
|
|
224
|
+
[attr.stroke]="segment.color"
|
|
225
|
+
[attr.stroke-width]="strokeWidth"
|
|
226
|
+
[attr.stroke-dasharray]="segment.dashArray"
|
|
227
|
+
[attr.stroke-dashoffset]="segment.dashOffset"
|
|
228
|
+
stroke-linecap="round"
|
|
229
|
+
class="transition-all duration-500"
|
|
230
|
+
[attr.transform]="'rotate(-90 ' + center + ' ' + center + ')'"
|
|
231
|
+
/>
|
|
232
|
+
}
|
|
233
|
+
</svg>
|
|
234
|
+
<!-- Center text -->
|
|
235
|
+
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
|
236
|
+
<span class="text-3xl font-bold text-gray-900">{{ total | number }}</span>
|
|
237
|
+
<span class="text-sm text-gray-500">Total</span>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<!-- Legend -->
|
|
242
|
+
<div class="mt-6 grid grid-cols-2 gap-4">
|
|
243
|
+
@for (item of data; track item.label) {
|
|
244
|
+
<div class="flex items-center gap-2">
|
|
245
|
+
<div class="h-3 w-3 rounded-full" [style.backgroundColor]="item.color"></div>
|
|
246
|
+
<span class="text-sm text-gray-600">{{ item.label }}</span>
|
|
247
|
+
<span class="text-sm font-medium text-gray-900">{{ item.value | number }}</span>
|
|
248
|
+
</div>
|
|
249
|
+
}
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
\`,
|
|
253
|
+
})
|
|
254
|
+
export class ChartDonutComponent implements OnChanges {
|
|
255
|
+
@Input() data: DonutChartData[] = [];
|
|
256
|
+
@Input() title?: string;
|
|
257
|
+
@Input() size = 200;
|
|
258
|
+
@Input() strokeWidth = 24;
|
|
259
|
+
|
|
260
|
+
center = 100;
|
|
261
|
+
radius = 80;
|
|
262
|
+
circumference = 2 * Math.PI * 80;
|
|
263
|
+
total = 0;
|
|
264
|
+
segments: {
|
|
265
|
+
label: string;
|
|
266
|
+
color: string;
|
|
267
|
+
dashArray: string;
|
|
268
|
+
dashOffset: number;
|
|
269
|
+
}[] = [];
|
|
270
|
+
|
|
271
|
+
ngOnChanges(): void {
|
|
272
|
+
this.center = this.size / 2;
|
|
273
|
+
this.radius = (this.size - this.strokeWidth) / 2;
|
|
274
|
+
this.circumference = 2 * Math.PI * this.radius;
|
|
275
|
+
this.total = this.data.reduce((sum, item) => sum + item.value, 0);
|
|
276
|
+
this.calculateSegments();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private calculateSegments(): void {
|
|
280
|
+
let offset = 0;
|
|
281
|
+
this.segments = this.data.map((item) => {
|
|
282
|
+
const percentage = this.total > 0 ? item.value / this.total : 0;
|
|
283
|
+
const dashArray = \`\${percentage * this.circumference} \${this.circumference}\`;
|
|
284
|
+
const segment = {
|
|
285
|
+
label: item.label,
|
|
286
|
+
color: item.color,
|
|
287
|
+
dashArray,
|
|
288
|
+
dashOffset: -offset,
|
|
289
|
+
};
|
|
290
|
+
offset += percentage * this.circumference;
|
|
291
|
+
return segment;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}`;
|
|
295
|
+
|
|
296
|
+
await fs.writeFile(
|
|
297
|
+
path.join(
|
|
298
|
+
config.fullPath,
|
|
299
|
+
"src/app/shared/components/chart-donut/chart-donut.component.ts"
|
|
300
|
+
),
|
|
301
|
+
donutChartComponent
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
module.exports = { createCharts };
|