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.
Files changed (47) hide show
  1. package/CHANGELOG.md +81 -344
  2. package/README.md +93 -157
  3. package/lib/cli/index.js +29 -3
  4. package/lib/cli/interactive.js +26 -1
  5. package/lib/managers/ProjectManager.js +0 -4
  6. package/lib/templates/base/components.js +243 -0
  7. package/lib/templates/base/index.js +207 -0
  8. package/lib/templates/base/infrastructure.js +314 -0
  9. package/lib/templates/base/linting.js +359 -0
  10. package/lib/templates/base/pwa.js +103 -0
  11. package/lib/templates/base/services.js +362 -0
  12. package/lib/templates/blog/app.js +250 -0
  13. package/lib/templates/blog/components.js +360 -0
  14. package/lib/templates/blog/i18n.js +77 -0
  15. package/lib/templates/blog/index.js +126 -0
  16. package/lib/templates/blog/pages.js +554 -0
  17. package/lib/templates/blog/services.js +390 -0
  18. package/lib/templates/dashboard/app.js +320 -0
  19. package/lib/templates/dashboard/charts.js +305 -0
  20. package/lib/templates/dashboard/components.js +410 -0
  21. package/lib/templates/dashboard/i18n.js +340 -0
  22. package/lib/templates/dashboard/index.js +141 -0
  23. package/lib/templates/dashboard/layout.js +310 -0
  24. package/lib/templates/dashboard/pages.js +681 -0
  25. package/lib/templates/ecommerce/app.js +315 -0
  26. package/lib/templates/ecommerce/components.js +496 -0
  27. package/lib/templates/ecommerce/i18n.js +389 -0
  28. package/lib/templates/ecommerce/index.js +152 -0
  29. package/lib/templates/ecommerce/layout.js +270 -0
  30. package/lib/templates/ecommerce/pages.js +969 -0
  31. package/lib/templates/ecommerce/services.js +300 -0
  32. package/lib/templates/index.js +12 -0
  33. package/lib/templates/landing/index.js +1117 -0
  34. package/lib/templates/portfolio/index.js +1160 -0
  35. package/lib/templates/saas/index.js +1371 -0
  36. package/lib/templates/starter/app.js +364 -0
  37. package/lib/templates/starter/i18n.js +856 -0
  38. package/lib/templates/starter/index.js +53 -4055
  39. package/lib/templates/starter/layout.js +852 -0
  40. package/lib/templates/starter/pages.js +1241 -0
  41. package/package.json +1 -1
  42. package/lib/templates/starter/features.js +0 -867
  43. package/lib/utils/ai-config.js +0 -641
  44. /package/lib/templates/{starter → base}/advanced-features.js +0 -0
  45. /package/lib/templates/{starter → base}/seo-assets.js +0 -0
  46. /package/lib/templates/{starter → base}/seo-features.js +0 -0
  47. /package/lib/templates/{starter → base}/ui-features.js +0 -0
@@ -0,0 +1,310 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Create dashboard layout components (Sidebar, Header, Layout)
6
+ */
7
+ async function createLayout(config) {
8
+ // Sidebar Component
9
+ const sidebarComponent = `import { Component, Input, Output, EventEmitter, inject } from '@angular/core';
10
+ import { RouterModule } from '@angular/router';
11
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
12
+ import { TranslateModule } from '@ngx-translate/core';
13
+ import {
14
+ heroHome,
15
+ heroChartBar,
16
+ heroUsers,
17
+ heroShoppingCart,
18
+ heroCog,
19
+ heroChevronLeft,
20
+ heroChevronRight,
21
+ heroArrowRightOnRectangle,
22
+ heroSquares2x2,
23
+ } from '@ng-icons/heroicons/outline';
24
+ import { TranslationService } from '@core/i18n/translation.service';
25
+
26
+ interface NavItem {
27
+ labelKey: string;
28
+ icon: string;
29
+ route: string;
30
+ badge?: number;
31
+ }
32
+
33
+ @Component({
34
+ selector: 'app-sidebar',
35
+ standalone: true,
36
+ imports: [RouterModule, NgIconComponent, TranslateModule],
37
+ viewProviders: [
38
+ provideIcons({
39
+ heroHome,
40
+ heroChartBar,
41
+ heroUsers,
42
+ heroShoppingCart,
43
+ heroCog,
44
+ heroChevronLeft,
45
+ heroChevronRight,
46
+ heroArrowRightOnRectangle,
47
+ heroSquares2x2,
48
+ }),
49
+ ],
50
+ template: \`
51
+ <aside
52
+ class="fixed inset-inline-start-0 top-0 z-40 flex h-screen flex-col bg-linear-to-b from-slate-900 to-slate-800 transition-all duration-300"
53
+ [class.w-64]="!collapsed"
54
+ [class.w-20]="collapsed">
55
+ <!-- Logo -->
56
+ <div class="flex h-16 items-center border-b border-slate-700/50 px-4" [class.justify-center]="collapsed">
57
+ <button
58
+ (click)="collapsed && toggleCollapse()"
59
+ class="flex items-center gap-3"
60
+ [class.cursor-pointer]="collapsed"
61
+ [class.cursor-default]="!collapsed">
62
+ <div class="flex h-10 w-10 items-center justify-center rounded-xl bg-linear-to-br from-indigo-500 to-purple-600 shadow-lg shadow-indigo-500/30">
63
+ <ng-icon name="heroSquares2x2" size="20" style="color: white;"></ng-icon>
64
+ </div>
65
+ </button>
66
+ @if (!collapsed) {
67
+ <button
68
+ (click)="toggleCollapse()"
69
+ class="ms-auto rounded-lg p-1.5 text-slate-400 hover:bg-slate-700/50 hover:text-white transition-colors">
70
+ <ng-icon name="heroChevronLeft" size="18"></ng-icon>
71
+ </button>
72
+ }
73
+ </div>
74
+
75
+ <!-- Navigation -->
76
+ <nav class="flex-1 space-y-1 overflow-y-auto p-3">
77
+ @for (item of navItems; track item.route) {
78
+ <a
79
+ [routerLink]="item.route"
80
+ routerLinkActive="bg-gradient-to-r from-indigo-500/20 to-purple-500/20 text-white border-indigo-400"
81
+ [routerLinkActiveOptions]="{ exact: item.route === '/dashboard' }"
82
+ class="group flex items-center gap-3 rounded-xl border-s-4 border-transparent px-3 py-3 text-slate-300 transition-all hover:bg-slate-700/50 hover:text-white"
83
+ [class.justify-center]="collapsed"
84
+ [class.px-3]="collapsed">
85
+ <ng-icon [name]="item.icon" size="20" class="transition-transform group-hover:scale-110"></ng-icon>
86
+ @if (!collapsed) {
87
+ <span class="font-medium">{{ item.labelKey | translate }}</span>
88
+ @if (item.badge) {
89
+ <span class="ms-auto rounded-full bg-indigo-500 px-2.5 py-0.5 text-xs font-semibold text-white shadow-sm">
90
+ {{ item.badge }}
91
+ </span>
92
+ }
93
+ }
94
+ </a>
95
+ }
96
+ </nav>
97
+
98
+ <!-- Expand button when collapsed -->
99
+ @if (collapsed) {
100
+ <div class="p-3">
101
+ <button
102
+ (click)="toggleCollapse()"
103
+ class="flex w-full items-center justify-center rounded-xl bg-slate-700/50 p-3 text-slate-300 transition-all hover:bg-indigo-500/20 hover:text-white">
104
+ <ng-icon name="heroChevronRight" size="20"></ng-icon>
105
+ </button>
106
+ </div>
107
+ }
108
+
109
+ <!-- Logout -->
110
+ <div class="border-t border-slate-700/50 p-3">
111
+ <button
112
+ class="flex w-full items-center gap-3 rounded-xl px-3 py-3 text-slate-300 transition-all hover:bg-red-500/20 hover:text-red-400"
113
+ [class.justify-center]="collapsed">
114
+ <ng-icon name="heroArrowRightOnRectangle" size="20"></ng-icon>
115
+ @if (!collapsed) {
116
+ <span class="font-medium">{{ 'dashboard.logout' | translate }}</span>
117
+ }
118
+ </button>
119
+ </div>
120
+ </aside>
121
+ \`,
122
+ })
123
+ export class SidebarComponent {
124
+ @Input() collapsed = false;
125
+ @Output() collapsedChange = new EventEmitter<boolean>();
126
+
127
+ private translationService = inject(TranslationService);
128
+
129
+ navItems: NavItem[] = [
130
+ { labelKey: 'dashboard.overview', icon: 'heroHome', route: '/dashboard' },
131
+ { labelKey: 'dashboard.analytics', icon: 'heroChartBar', route: '/dashboard/analytics' },
132
+ { labelKey: 'dashboard.users', icon: 'heroUsers', route: '/dashboard/users', badge: 12 },
133
+ { labelKey: 'dashboard.orders', icon: 'heroShoppingCart', route: '/dashboard/orders', badge: 5 },
134
+ { labelKey: 'dashboard.settings', icon: 'heroCog', route: '/dashboard/settings' },
135
+ ];
136
+
137
+ toggleCollapse(): void {
138
+ this.collapsed = !this.collapsed;
139
+ this.collapsedChange.emit(this.collapsed);
140
+ }
141
+ }`;
142
+
143
+ await fs.writeFile(
144
+ path.join(config.fullPath, 'src/app/layout/sidebar/sidebar.component.ts'),
145
+ sidebarComponent
146
+ );
147
+
148
+ // Dashboard Header Component
149
+ const dashboardHeaderComponent = `import { Component, Input, inject } from '@angular/core';
150
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
151
+ import { TranslateModule } from '@ngx-translate/core';
152
+ import {
153
+ heroBell,
154
+ heroMagnifyingGlass,
155
+ heroUser,
156
+ heroLanguage,
157
+ heroChevronDown,
158
+ } from '@ng-icons/heroicons/outline';
159
+ import { TranslationService, type SupportedLanguage, type LanguageOption } from '@core/i18n/translation.service';
160
+
161
+ @Component({
162
+ selector: 'app-dashboard-header',
163
+ standalone: true,
164
+ imports: [NgIconComponent, TranslateModule],
165
+ viewProviders: [provideIcons({ heroBell, heroMagnifyingGlass, heroUser, heroLanguage, heroChevronDown })],
166
+ template: \`
167
+ <header
168
+ class="sticky top-0 z-30 flex h-16 items-center justify-between border-b border-gray-200 bg-white px-6">
169
+ <!-- Search -->
170
+ <div class="relative max-w-md flex-1">
171
+ <ng-icon
172
+ name="heroMagnifyingGlass"
173
+ size="18"
174
+ class="absolute top-1/2 -translate-y-1/2 text-gray-400"
175
+ style="inset-inline-start: 10px;">
176
+ </ng-icon>
177
+ <input
178
+ type="text"
179
+ [placeholder]="'table.search' | translate"
180
+ class="w-full rounded-lg border border-gray-300 p-4 ps-10 text-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
181
+ />
182
+ </div>
183
+
184
+ <!-- Right side -->
185
+ <div class="flex items-center gap-4">
186
+ <!-- Language Switcher -->
187
+ <div class="relative">
188
+ <button
189
+ (click)="toggleLanguageDropdown()"
190
+ class="flex items-center gap-2 rounded-lg px-3 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-900 transition-colors">
191
+ <ng-icon name="heroLanguage" size="20"></ng-icon>
192
+ <span class="hidden sm:inline text-sm font-medium">{{ currentLanguage.nativeName }}</span>
193
+ <ng-icon name="heroChevronDown" size="14" [class.rotate-180]="languageDropdownOpen"></ng-icon>
194
+ </button>
195
+
196
+ @if (languageDropdownOpen) {
197
+ <div class="absolute end-0 top-full mt-1 w-40 rounded-lg border border-gray-200 bg-white py-1 shadow-lg z-50">
198
+ @for (lang of translationService.languages; track lang.code) {
199
+ <button
200
+ (click)="setLanguage(lang.code)"
201
+ class="flex w-full items-center gap-2 px-4 py-2 text-sm hover:bg-gray-50 transition-colors"
202
+ [class.bg-primary-50]="lang.code === currentLang"
203
+ [class.text-primary-600]="lang.code === currentLang">
204
+ <span>{{ lang.flag }}</span>
205
+ <span>{{ lang.nativeName }}</span>
206
+ </button>
207
+ }
208
+ </div>
209
+ }
210
+ </div>
211
+
212
+ <!-- Notifications -->
213
+ <button class="relative rounded-lg p-2 text-gray-500 hover:bg-gray-100 hover:text-gray-900">
214
+ <ng-icon name="heroBell" size="20"></ng-icon>
215
+ <span class="absolute top-1 end-1 h-2 w-2 rounded-full bg-danger-500"></span>
216
+ </button>
217
+
218
+ <!-- User menu -->
219
+ <div class="flex items-center gap-3">
220
+ <div class="h-9 w-9 overflow-hidden rounded-full bg-primary-100">
221
+ <div class="flex h-full w-full items-center justify-center">
222
+ <ng-icon name="heroUser" size="18" class="text-primary-600"></ng-icon>
223
+ </div>
224
+ </div>
225
+ <div class="hidden md:block">
226
+ <p class="text-sm font-medium text-gray-900">Admin User</p>
227
+ <p class="text-xs text-gray-500">admin&#64;example.com</p>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ </header>
232
+ \`,
233
+ })
234
+ export class DashboardHeaderComponent {
235
+ @Input() sidebarCollapsed = false;
236
+
237
+ translationService = inject(TranslationService);
238
+ languageDropdownOpen = false;
239
+ currentLang: SupportedLanguage = 'en';
240
+ currentLanguage: LanguageOption = this.translationService.languages[0];
241
+
242
+ constructor() {
243
+ this.currentLang = this.translationService.getCurrentLanguage();
244
+ this.updateCurrentLanguage();
245
+ }
246
+
247
+ toggleLanguageDropdown(): void {
248
+ this.languageDropdownOpen = !this.languageDropdownOpen;
249
+ }
250
+
251
+ setLanguage(lang: SupportedLanguage): void {
252
+ this.translationService.setLanguage(lang);
253
+ this.currentLang = lang;
254
+ this.updateCurrentLanguage();
255
+ this.languageDropdownOpen = false;
256
+ }
257
+
258
+ private updateCurrentLanguage(): void {
259
+ const langOption = this.translationService.getLanguageOption(this.currentLang);
260
+ if (langOption) {
261
+ this.currentLanguage = langOption;
262
+ }
263
+ }
264
+ }`;
265
+
266
+ await fs.writeFile(
267
+ path.join(config.fullPath, 'src/app/layout/dashboard-header/dashboard-header.component.ts'),
268
+ dashboardHeaderComponent
269
+ );
270
+
271
+ // Dashboard Layout Component
272
+ const dashboardLayoutComponent = `import { Component, inject } from '@angular/core';
273
+ import { RouterOutlet } from '@angular/router';
274
+ import { SidebarComponent } from '../sidebar/sidebar.component';
275
+ import { DashboardHeaderComponent } from '../dashboard-header/dashboard-header.component';
276
+ import { TranslationService } from '@core/i18n/translation.service';
277
+
278
+ @Component({
279
+ selector: 'app-dashboard-layout',
280
+ standalone: true,
281
+ imports: [RouterOutlet, SidebarComponent, DashboardHeaderComponent],
282
+ template: \`
283
+ <div class="min-h-screen bg-linear-to-br from-slate-50 via-white to-indigo-50/30">
284
+ <app-sidebar [(collapsed)]="sidebarCollapsed"></app-sidebar>
285
+
286
+ <div
287
+ class="transition-all duration-300"
288
+ [class.ms-64]="!sidebarCollapsed"
289
+ [class.ms-20]="sidebarCollapsed">
290
+ <app-dashboard-header [sidebarCollapsed]="sidebarCollapsed"></app-dashboard-header>
291
+
292
+ <main class="p-6">
293
+ <router-outlet />
294
+ </main>
295
+ </div>
296
+ </div>
297
+ \`,
298
+ })
299
+ export class DashboardLayoutComponent {
300
+ private translationService = inject(TranslationService);
301
+ sidebarCollapsed = false;
302
+ }`;
303
+
304
+ await fs.writeFile(
305
+ path.join(config.fullPath, 'src/app/layout/dashboard/dashboard-layout.component.ts'),
306
+ dashboardLayoutComponent
307
+ );
308
+ }
309
+
310
+ module.exports = { createLayout };