create-ng-tailwind 1.0.0 → 2.0.2

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.
@@ -0,0 +1,437 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+
4
+ // ==================== MODAL SERVICE & COMPONENT ====================
5
+
6
+ async function createModalService(config) {
7
+ const modalService = `import { Injectable, signal } from '@angular/core';
8
+
9
+ export interface ModalData {
10
+ message?: string;
11
+ type?: 'confirm' | 'alert' | 'custom';
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ export interface ModalConfig {
16
+ title?: string;
17
+ data?: ModalData;
18
+ size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
19
+ closeOnBackdrop?: boolean;
20
+ closeOnEscape?: boolean;
21
+ }
22
+
23
+ @Injectable({
24
+ providedIn: 'root'
25
+ })
26
+ export class ModalService {
27
+ // Reactive signals for modal state
28
+ isOpen = signal(false);
29
+ modalConfig = signal<ModalConfig | null>(null);
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ private resolve: ((value: any) => void) | null = null;
32
+
33
+ /**
34
+ * Open a modal with configuration
35
+ */
36
+ open<T = boolean>(config: ModalConfig): Promise<T> {
37
+ this.modalConfig.set(config);
38
+ this.isOpen.set(true);
39
+
40
+ return new Promise<T>((resolve) => {
41
+ this.resolve = resolve as (value: unknown) => void;
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Close the modal with optional result
47
+ */
48
+ close<T = boolean>(result?: T): void {
49
+ this.isOpen.set(false);
50
+ this.modalConfig.set(null);
51
+
52
+ if (this.resolve) {
53
+ this.resolve(result);
54
+ this.resolve = null;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Show a confirmation dialog
60
+ */
61
+ async confirm(message: string, title = 'Confirm'): Promise<boolean> {
62
+ const result = await this.open({
63
+ title,
64
+ data: { message, type: 'confirm' },
65
+ size: 'sm',
66
+ closeOnBackdrop: false
67
+ });
68
+
69
+ return result === true;
70
+ }
71
+
72
+ /**
73
+ * Show an alert dialog
74
+ */
75
+ async alert(message: string, title = 'Alert'): Promise<void> {
76
+ await this.open({
77
+ title,
78
+ data: { message, type: 'alert' },
79
+ size: 'sm',
80
+ closeOnBackdrop: true
81
+ });
82
+ }
83
+ }`;
84
+
85
+ await fs.writeFile(
86
+ path.join(config.fullPath, "src/app/core/services/modal.service.ts"),
87
+ modalService,
88
+ );
89
+ }
90
+
91
+ async function createModalComponent(config) {
92
+ const modalComponent = `import { Component, inject, HostListener } from '@angular/core';
93
+ import { CommonModule } from '@angular/common';
94
+ import { ModalService } from '@core/services/modal.service';
95
+ import { ButtonComponent } from '@shared/components/button/button.component';
96
+
97
+ @Component({
98
+ selector: 'app-modal',
99
+ imports: [CommonModule, ButtonComponent],
100
+ template: \`
101
+ @if (modalService.isOpen()) {
102
+ <!-- Backdrop -->
103
+ <div
104
+ class="fixed inset-0 [backdrop-filter:blur(2px)] bg-[#4a494b4d] z-40 transition-opacity"
105
+ (click)="onBackdropClick()"
106
+ role="presentation"
107
+ tabindex="-1"
108
+ (keydown)="onBackdropKeydown($event)">
109
+ </div>
110
+
111
+ <!-- Modal Container -->
112
+ <div class="fixed inset-0 z-50 overflow-y-auto">
113
+ <div class="flex min-h-full items-center justify-center p-4">
114
+ <!-- Modal Content -->
115
+ <div
116
+ [class]="getModalClasses()"
117
+ class="relative bg-white rounded-lg shadow-xl transform transition-all animate-fade-in">
118
+
119
+ <!-- Header -->
120
+ @if (modalService.modalConfig()?.title) {
121
+ <div class="border-b border-gray-200 px-6 py-4">
122
+ <div class="flex items-center justify-between">
123
+ <h3 class="text-xl font-semibold text-gray-900">
124
+ {{ modalService.modalConfig()?.title }}
125
+ </h3>
126
+ <button
127
+ (click)="modalService.close()"
128
+ class="text-gray-400 hover:text-gray-600 transition-colors">
129
+ <svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
130
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
131
+ </svg>
132
+ </button>
133
+ </div>
134
+ </div>
135
+ }
136
+
137
+ <!-- Body -->
138
+ <div class="px-6 py-4">
139
+ @if (getModalType() === 'confirm' || getModalType() === 'alert') {
140
+ <p class="text-gray-700">{{ getModalMessage() }}</p>
141
+ } @else {
142
+ <!-- Custom component content would go here -->
143
+ <div>
144
+ <p class="text-gray-600">Modal content goes here</p>
145
+ </div>
146
+ }
147
+ </div>
148
+
149
+ <!-- Footer -->
150
+ @if (getModalType() === 'confirm') {
151
+ <div class="border-t border-gray-200 px-6 py-4 flex justify-end space-x-3">
152
+ <app-button
153
+ variant="secondary"
154
+ (click)="modalService.close(false)">
155
+ Cancel
156
+ </app-button>
157
+ <app-button
158
+ (click)="modalService.close(true)">
159
+ Confirm
160
+ </app-button>
161
+ </div>
162
+ } @else if (getModalType() === 'alert') {
163
+ <div class="border-t border-gray-200 px-6 py-4 flex justify-end">
164
+ <app-button
165
+ (click)="modalService.close()">
166
+ OK
167
+ </app-button>
168
+ </div>
169
+ }
170
+ </div>
171
+ </div>
172
+ </div>
173
+ }
174
+ \`,
175
+ styles: [\`
176
+ @keyframes fade-in {
177
+ from {
178
+ opacity: 0;
179
+ transform: scale(0.95);
180
+ }
181
+ to {
182
+ opacity: 1;
183
+ transform: scale(1);
184
+ }
185
+ }
186
+
187
+ .animate-fade-in {
188
+ animation: fade-in 0.2s ease-out;
189
+ }
190
+ \`]
191
+ })
192
+ export class ModalComponent {
193
+ modalService = inject(ModalService);
194
+
195
+ @HostListener('document:keydown.escape')
196
+ onEscapeKey(): void {
197
+ const config = this.modalService.modalConfig();
198
+ if (config?.closeOnEscape !== false) {
199
+ this.modalService.close();
200
+ }
201
+ }
202
+
203
+ onBackdropClick(): void {
204
+ const config = this.modalService.modalConfig();
205
+ if (config?.closeOnBackdrop !== false) {
206
+ this.modalService.close();
207
+ }
208
+ }
209
+
210
+ onBackdropKeydown(event: KeyboardEvent): void {
211
+ if (event.key === 'Enter' || event.key === ' ') {
212
+ event.preventDefault();
213
+ this.onBackdropClick();
214
+ }
215
+ }
216
+
217
+ getModalClasses(): string {
218
+ const size = this.modalService.modalConfig()?.size || 'md';
219
+ const sizeClasses = {
220
+ sm: 'max-w-sm',
221
+ md: 'max-w-md',
222
+ lg: 'max-w-lg',
223
+ xl: 'max-w-xl',
224
+ full: 'max-w-full mx-4'
225
+ };
226
+
227
+ return \`w-full \${sizeClasses[size]}\`;
228
+ }
229
+
230
+ getModalType(): string | undefined {
231
+ return this.modalService.modalConfig()?.data?.type;
232
+ }
233
+
234
+ getModalMessage(): string {
235
+ return this.modalService.modalConfig()?.data?.message || '';
236
+ }
237
+ }`;
238
+
239
+ await fs.writeFile(
240
+ path.join(
241
+ config.fullPath,
242
+ "src/app/shared/components/modal/modal.component.ts",
243
+ ),
244
+ modalComponent,
245
+ );
246
+ }
247
+
248
+ // ==================== ADDITIONAL PIPES ====================
249
+
250
+ async function createTruncatePipe(config) {
251
+ const truncatePipe = `import { Pipe, PipeTransform } from '@angular/core';
252
+
253
+ @Pipe({
254
+ name: 'truncate',
255
+ pure: true
256
+ })
257
+ export class TruncatePipe implements PipeTransform {
258
+ transform(value: string, limit = 50, completeWords = false, ellipsis = '...'): string {
259
+ if (!value) return '';
260
+
261
+ if (value.length <= limit) {
262
+ return value;
263
+ }
264
+
265
+ if (completeWords) {
266
+ limit = value.substring(0, limit).lastIndexOf(' ');
267
+ }
268
+
269
+ return value.substring(0, limit) + ellipsis;
270
+ }
271
+ }`;
272
+
273
+ await fs.writeFile(
274
+ path.join(config.fullPath, "src/app/shared/pipes/truncate.pipe.ts"),
275
+ truncatePipe,
276
+ );
277
+ }
278
+
279
+ // ==================== ADDITIONAL DIRECTIVES ====================
280
+
281
+ async function createClickOutsideDirective(config) {
282
+ const clickOutsideDirective = `import { Directive, ElementRef, EventEmitter, HostListener, Output, inject } from '@angular/core';
283
+
284
+ @Directive({
285
+ selector: '[appClickOutside]',
286
+ standalone: true
287
+ })
288
+ export class ClickOutsideDirective {
289
+ @Output() appClickOutside = new EventEmitter<void>();
290
+
291
+ private elementRef = inject(ElementRef);
292
+
293
+ @HostListener('document:click', ['$event'])
294
+ onClick(event: MouseEvent): void {
295
+ const target = event.target as HTMLElement;
296
+
297
+ if (!target) {
298
+ return;
299
+ }
300
+
301
+ const clickedInside = this.elementRef.nativeElement.contains(target);
302
+
303
+ if (!clickedInside) {
304
+ this.appClickOutside.emit();
305
+ }
306
+ }
307
+ }`;
308
+
309
+ await fs.writeFile(
310
+ path.join(
311
+ config.fullPath,
312
+ "src/app/shared/directives/click-outside.directive.ts",
313
+ ),
314
+ clickOutsideDirective,
315
+ );
316
+ }
317
+
318
+ async function createTooltipDirective(config) {
319
+ const tooltipDirective = `import { Directive, ElementRef, HostListener, Input, Renderer2, inject, OnDestroy } from '@angular/core';
320
+
321
+ @Directive({
322
+ selector: '[appTooltip]',
323
+ standalone: true
324
+ })
325
+ export class TooltipDirective implements OnDestroy {
326
+ @Input() appTooltip = '';
327
+ @Input() tooltipPosition: 'top' | 'bottom' | 'left' | 'right' = 'top';
328
+
329
+ private elementRef = inject(ElementRef);
330
+ private renderer = inject(Renderer2);
331
+ private tooltipElement: HTMLElement | null = null;
332
+
333
+ @HostListener('mouseenter')
334
+ onMouseEnter(): void {
335
+ if (!this.appTooltip) return;
336
+
337
+ this.createTooltip();
338
+ }
339
+
340
+ @HostListener('mouseleave')
341
+ onMouseLeave(): void {
342
+ this.removeTooltip();
343
+ }
344
+
345
+ ngOnDestroy(): void {
346
+ this.removeTooltip();
347
+ }
348
+
349
+ private createTooltip(): void {
350
+ // Create tooltip element
351
+ this.tooltipElement = this.renderer.createElement('div');
352
+ this.renderer.addClass(this.tooltipElement, 'tooltip');
353
+ this.renderer.addClass(this.tooltipElement, 'absolute');
354
+ this.renderer.addClass(this.tooltipElement, 'z-50');
355
+ this.renderer.addClass(this.tooltipElement, 'px-3');
356
+ this.renderer.addClass(this.tooltipElement, 'py-2');
357
+ this.renderer.addClass(this.tooltipElement, 'text-sm');
358
+ this.renderer.addClass(this.tooltipElement, 'text-white');
359
+ this.renderer.addClass(this.tooltipElement, 'bg-gray-900');
360
+ this.renderer.addClass(this.tooltipElement, 'rounded-lg');
361
+ this.renderer.addClass(this.tooltipElement, 'shadow-lg');
362
+ this.renderer.addClass(this.tooltipElement, 'whitespace-nowrap');
363
+ this.renderer.addClass(this.tooltipElement, 'pointer-events-none');
364
+
365
+ // Set tooltip text
366
+ const text = this.renderer.createText(this.appTooltip);
367
+ this.renderer.appendChild(this.tooltipElement, text);
368
+
369
+ // Append to body
370
+ this.renderer.appendChild(document.body, this.tooltipElement);
371
+
372
+ // Position tooltip
373
+ this.positionTooltip();
374
+ }
375
+
376
+ private positionTooltip(): void {
377
+ if (!this.tooltipElement) return;
378
+
379
+ const hostPos = this.elementRef.nativeElement.getBoundingClientRect();
380
+ const tooltipPos = this.tooltipElement.getBoundingClientRect();
381
+
382
+ let top = 0;
383
+ let left = 0;
384
+
385
+ switch (this.tooltipPosition) {
386
+ case 'top':
387
+ top = hostPos.top - tooltipPos.height - 8;
388
+ left = hostPos.left + (hostPos.width - tooltipPos.width) / 2;
389
+ break;
390
+ case 'bottom':
391
+ top = hostPos.bottom + 8;
392
+ left = hostPos.left + (hostPos.width - tooltipPos.width) / 2;
393
+ break;
394
+ case 'left':
395
+ top = hostPos.top + (hostPos.height - tooltipPos.height) / 2;
396
+ left = hostPos.left - tooltipPos.width - 8;
397
+ break;
398
+ case 'right':
399
+ top = hostPos.top + (hostPos.height - tooltipPos.height) / 2;
400
+ left = hostPos.right + 8;
401
+ break;
402
+ }
403
+
404
+ this.renderer.setStyle(this.tooltipElement, 'top', \`\${top}px\`);
405
+ this.renderer.setStyle(this.tooltipElement, 'left', \`\${left}px\`);
406
+ }
407
+
408
+ private removeTooltip(): void {
409
+ if (this.tooltipElement) {
410
+ this.renderer.removeChild(document.body, this.tooltipElement);
411
+ this.tooltipElement = null;
412
+ }
413
+ }
414
+ }`;
415
+
416
+ await fs.writeFile(
417
+ path.join(
418
+ config.fullPath,
419
+ "src/app/shared/directives/tooltip.directive.ts",
420
+ ),
421
+ tooltipDirective,
422
+ );
423
+ }
424
+
425
+ // Export all functions
426
+ module.exports = {
427
+ // Modal System
428
+ createModalService,
429
+ createModalComponent,
430
+
431
+ // Pipes
432
+ createTruncatePipe,
433
+
434
+ // Directives
435
+ createClickOutsideDirective,
436
+ createTooltipDirective,
437
+ };