tailjng 0.0.56 → 0.0.57

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.
@@ -14,6 +14,10 @@ function getComponentList() {
14
14
  path: "src/lib/components/tooltip",
15
15
  dependencies: [],
16
16
  },
17
+ 'coach-mark': {
18
+ path: "src/lib/components/coach-mark",
19
+ dependencies: [],
20
+ },
17
21
  'badge': {
18
22
  path: "src/lib/components/badge",
19
23
  dependencies: ["tooltip"],
@@ -27,7 +27,7 @@ Authors:
27
27
  License:
28
28
  This project is licensed under the BSD 3-Clause - see the LICENSE file for more details.
29
29
 
30
- Version: 0.0.56
30
+ Version: 0.0.57
31
31
  Creation Date: 2025-01-04
32
32
  ===============================================`
33
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailjng",
3
- "version": "0.0.56",
3
+ "version": "0.0.57",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^19.2.0",
6
6
  "@angular/core": "^19.2.0",
@@ -0,0 +1,36 @@
1
+ @if (spotlight) {
2
+ <div
3
+ class="j-spotlight fixed pointer-events-none border-2 border-white/90 transition-all duration-150 ease-out"
4
+ [ngStyle]="{
5
+ top: targetTop + 'px',
6
+ left: targetLeft + 'px',
7
+ width: targetWidth + 'px',
8
+ height: targetHeight + 'px',
9
+ borderRadius: targetRadius,
10
+ zIndex: zIndex - 1,
11
+ }"
12
+ ></div>
13
+ }
14
+
15
+ <div
16
+ class="j-coach-card fixed rounded-2xl border border-slate-400/25 bg-white p-3.5 text-slate-900 shadow-2xl"
17
+ [ngStyle]="{
18
+ top: cardTop + 'px',
19
+ left: cardLeft + 'px',
20
+ width: width,
21
+ maxWidth: maxWidth,
22
+ zIndex: zIndex,
23
+ }"
24
+ >
25
+ @if (contentTemplate) {
26
+ <ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
27
+ } @else {
28
+ <div class="mb-1 text-sm font-bold">
29
+ {{ title }}
30
+ </div>
31
+
32
+ <div class="text-[13px] leading-relaxed opacity-80">
33
+ {{ description }}
34
+ </div>
35
+ }
36
+ </div>
@@ -0,0 +1,20 @@
1
+ .j-spotlight {
2
+ box-shadow: 0 0 0 9999px rgba(15, 23, 42, 0.68);
3
+ }
4
+
5
+ .j-coach-card {
6
+ animation: coachIn 0.14s ease-out;
7
+ box-shadow: 0 20px 50px rgba(15, 23, 42, 0.28);
8
+ }
9
+
10
+ @keyframes coachIn {
11
+ from {
12
+ opacity: 0;
13
+ transform: translateY(5px) scale(0.98);
14
+ }
15
+
16
+ to {
17
+ opacity: 1;
18
+ transform: translateY(0) scale(1);
19
+ }
20
+ }
@@ -0,0 +1,45 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { Component, Input, TemplateRef } from '@angular/core';
3
+
4
+ type CoachPosition =
5
+ | 'top'
6
+ | 'top-left'
7
+ | 'top-right'
8
+ | 'bottom'
9
+ | 'bottom-left'
10
+ | 'bottom-right'
11
+ | 'left'
12
+ | 'left-top'
13
+ | 'left-bottom'
14
+ | 'right'
15
+ | 'right-top'
16
+ | 'right-bottom';
17
+
18
+ @Component({
19
+ selector: 'JCoachMarkOverlay',
20
+ imports: [CommonModule],
21
+ templateUrl: './coach-mark.component.html',
22
+ styleUrl: './coach-mark.component.scss'
23
+ })
24
+ export class JCoachMarkComponent {
25
+ @Input() title = '';
26
+ @Input() description = '';
27
+ @Input() contentTemplate?: TemplateRef<unknown>;
28
+
29
+ @Input() spotlight = true;
30
+
31
+ @Input() cardTop = 0;
32
+ @Input() cardLeft = 0;
33
+
34
+ @Input() targetTop = 0;
35
+ @Input() targetLeft = 0;
36
+ @Input() targetWidth = 0;
37
+ @Input() targetHeight = 0;
38
+ @Input() targetRadius = '12px';
39
+
40
+ @Input() position: CoachPosition = 'bottom';
41
+
42
+ @Input() width = '300px';
43
+ @Input() maxWidth = '340px';
44
+ @Input() zIndex = 99999;
45
+ }
@@ -0,0 +1,256 @@
1
+ import {
2
+ ApplicationRef,
3
+ ComponentRef,
4
+ Directive,
5
+ ElementRef,
6
+ EnvironmentInjector,
7
+ HostListener,
8
+ Input,
9
+ OnDestroy,
10
+ TemplateRef,
11
+ createComponent
12
+ } from '@angular/core';
13
+
14
+ import { JCoachMarkComponent } from './coach-mark.component';
15
+
16
+ type CoachTrigger = 'hover' | 'click';
17
+ type CoachPosition =
18
+ | 'top'
19
+ | 'top-left'
20
+ | 'top-right'
21
+ | 'bottom'
22
+ | 'bottom-left'
23
+ | 'bottom-right'
24
+ | 'left'
25
+ | 'left-top'
26
+ | 'left-bottom'
27
+ | 'right'
28
+ | 'right-top'
29
+ | 'right-bottom';
30
+
31
+ @Directive({
32
+ selector: '[jCoachMark]',
33
+ standalone: true
34
+ })
35
+ export class JCoachMarkDirective implements OnDestroy {
36
+ @Input() coachTitle = '';
37
+ @Input() coachDescription = '';
38
+ @Input() coachContent?: TemplateRef<unknown>;
39
+
40
+ @Input() coachTrigger: CoachTrigger = 'click';
41
+ @Input() coachPosition: CoachPosition = 'bottom';
42
+
43
+ @Input() coachSpotlight = true;
44
+ @Input() coachDisabled = false;
45
+
46
+ @Input() coachWidth = '300px';
47
+ @Input() coachMaxWidth = '340px';
48
+ @Input() coachOffset = 12;
49
+ @Input() coachRadius = '12px';
50
+
51
+ private overlayRef?: ComponentRef<JCoachMarkComponent>;
52
+ private isOpen = false;
53
+
54
+ private scrollHandler = () => this.updatePosition();
55
+ private resizeHandler = () => this.updatePosition();
56
+
57
+ constructor(
58
+ private elementRef: ElementRef<HTMLElement>,
59
+ private appRef: ApplicationRef,
60
+ private injector: EnvironmentInjector
61
+ ) { }
62
+
63
+ @HostListener('mouseenter')
64
+ onMouseEnter() {
65
+ if (this.coachTrigger === 'hover') this.open();
66
+ }
67
+
68
+ @HostListener('mouseleave')
69
+ onMouseLeave() {
70
+ if (this.coachTrigger === 'hover') this.close();
71
+ }
72
+
73
+ @HostListener('click', ['$event'])
74
+ onClick(event: MouseEvent) {
75
+ if (this.coachTrigger !== 'click') return;
76
+
77
+ event.stopPropagation();
78
+
79
+ this.isOpen ? this.close() : this.open();
80
+
81
+ setTimeout(() => {
82
+ document.addEventListener('click', this.documentClickHandler);
83
+ });
84
+ }
85
+
86
+ private documentClickHandler = (event: MouseEvent) => {
87
+ const host = this.elementRef.nativeElement;
88
+
89
+ if (!host.contains(event.target as Node)) {
90
+ this.close();
91
+ }
92
+ };
93
+
94
+ private open() {
95
+ if (this.coachDisabled) return;
96
+ if (this.overlayRef) return;
97
+
98
+ this.overlayRef = createComponent(JCoachMarkComponent, {
99
+ environmentInjector: this.injector
100
+ });
101
+
102
+ this.overlayRef.instance.title = this.coachTitle;
103
+ this.overlayRef.instance.description = this.coachDescription;
104
+ this.overlayRef.instance.contentTemplate = this.coachContent;
105
+
106
+ this.overlayRef.instance.spotlight = this.coachSpotlight;
107
+ this.overlayRef.instance.position = this.coachPosition;
108
+
109
+ this.overlayRef.instance.width = this.coachWidth;
110
+ this.overlayRef.instance.maxWidth = this.coachMaxWidth;
111
+ this.overlayRef.instance.targetRadius = this.coachRadius;
112
+
113
+ this.appRef.attachView(this.overlayRef.hostView);
114
+
115
+ document.body.appendChild(
116
+ this.overlayRef.location.nativeElement
117
+ );
118
+
119
+ this.isOpen = true;
120
+
121
+ requestAnimationFrame(() => this.updatePosition());
122
+
123
+ window.addEventListener('scroll', this.scrollHandler, true);
124
+ window.addEventListener('resize', this.resizeHandler);
125
+ }
126
+
127
+ private updatePosition() {
128
+ if (!this.overlayRef) return;
129
+
130
+ const host = this.elementRef.nativeElement;
131
+ const rect = host.getBoundingClientRect();
132
+
133
+ const cardElement = this.overlayRef.location.nativeElement
134
+ .querySelector('.j-coach-card') as HTMLElement;
135
+
136
+ if (!cardElement) return;
137
+
138
+ const cardRect = cardElement.getBoundingClientRect();
139
+
140
+ let cardTop = 0;
141
+ let cardLeft = 0;
142
+
143
+ const position = this.coachPosition;
144
+
145
+ const alignCenterX = rect.left + rect.width / 2 - cardRect.width / 2;
146
+ const alignLeft = rect.left;
147
+ const alignRight = rect.right - cardRect.width;
148
+
149
+ const alignCenterY = rect.top + rect.height / 2 - cardRect.height / 2;
150
+ const alignTop = rect.top;
151
+ const alignBottom = rect.bottom - cardRect.height;
152
+
153
+ switch (position) {
154
+ case 'top':
155
+ cardTop = rect.top - cardRect.height - this.coachOffset;
156
+ cardLeft = alignCenterX;
157
+ break;
158
+
159
+ case 'top-left':
160
+ cardTop = rect.top - cardRect.height - this.coachOffset;
161
+ cardLeft = alignLeft;
162
+ break;
163
+
164
+ case 'top-right':
165
+ cardTop = rect.top - cardRect.height - this.coachOffset;
166
+ cardLeft = alignRight;
167
+ break;
168
+
169
+ case 'bottom':
170
+ cardTop = rect.bottom + this.coachOffset;
171
+ cardLeft = alignCenterX;
172
+ break;
173
+
174
+ case 'bottom-left':
175
+ cardTop = rect.bottom + this.coachOffset;
176
+ cardLeft = alignLeft;
177
+ break;
178
+
179
+ case 'bottom-right':
180
+ cardTop = rect.bottom + this.coachOffset;
181
+ cardLeft = alignRight;
182
+ break;
183
+
184
+ case 'left':
185
+ cardTop = alignCenterY;
186
+ cardLeft = rect.left - cardRect.width - this.coachOffset;
187
+ break;
188
+
189
+ case 'left-top':
190
+ cardTop = alignTop;
191
+ cardLeft = rect.left - cardRect.width - this.coachOffset;
192
+ break;
193
+
194
+ case 'left-bottom':
195
+ cardTop = alignBottom;
196
+ cardLeft = rect.left - cardRect.width - this.coachOffset;
197
+ break;
198
+
199
+ case 'right':
200
+ cardTop = alignCenterY;
201
+ cardLeft = rect.right + this.coachOffset;
202
+ break;
203
+
204
+ case 'right-top':
205
+ cardTop = alignTop;
206
+ cardLeft = rect.right + this.coachOffset;
207
+ break;
208
+
209
+ case 'right-bottom':
210
+ cardTop = alignBottom;
211
+ cardLeft = rect.right + this.coachOffset;
212
+ break;
213
+ }
214
+
215
+ const margin = 10;
216
+
217
+ cardLeft = Math.max(margin, cardLeft);
218
+ cardTop = Math.max(margin, cardTop);
219
+
220
+ if (cardLeft + cardRect.width > window.innerWidth - margin) {
221
+ cardLeft = window.innerWidth - cardRect.width - margin;
222
+ }
223
+
224
+ if (cardTop + cardRect.height > window.innerHeight - margin) {
225
+ cardTop = window.innerHeight - cardRect.height - margin;
226
+ }
227
+
228
+ this.overlayRef.instance.targetTop = rect.top;
229
+ this.overlayRef.instance.targetLeft = rect.left;
230
+ this.overlayRef.instance.targetWidth = rect.width;
231
+ this.overlayRef.instance.targetHeight = rect.height;
232
+
233
+ this.overlayRef.instance.cardTop = cardTop;
234
+ this.overlayRef.instance.cardLeft = cardLeft;
235
+
236
+ this.overlayRef.changeDetectorRef.detectChanges();
237
+ }
238
+
239
+ private close() {
240
+ this.isOpen = false;
241
+
242
+ document.removeEventListener('click', this.documentClickHandler);
243
+ window.removeEventListener('scroll', this.scrollHandler, true);
244
+ window.removeEventListener('resize', this.resizeHandler);
245
+
246
+ if (!this.overlayRef) return;
247
+
248
+ this.appRef.detachView(this.overlayRef.hostView);
249
+ this.overlayRef.destroy();
250
+ this.overlayRef = undefined;
251
+ }
252
+
253
+ ngOnDestroy() {
254
+ this.close();
255
+ }
256
+ }
@@ -9,7 +9,7 @@
9
9
  type="text"
10
10
  [(ngModel)]="searchQuery"
11
11
  [placeholder]="searchPlaceholder"
12
- class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 pr-16 focus:outline-none focus:ring-2 focus:ring-primary"
12
+ class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
13
13
  />
14
14
 
15
15
  <div
@@ -1,5 +1,5 @@
1
1
  @if (hasErrors) {
2
- <div class="flex flex-col text-red-600 dark:text-red-300 text-sm">
2
+ <div class="flex flex-col text-red-600 dark:text-red-300 text-[12px]">
3
3
  @for (message of errors; track $index) {
4
4
  <span [class]="classes">{{ message }}</span>
5
5
  }
@@ -11,7 +11,7 @@
11
11
  (blur)="onTouched()"
12
12
  [ngClass]="combinedNgClass"
13
13
  [class]="classes"
14
- class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
14
+ class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
15
15
  />
16
16
 
17
17
  @if (value && clearButton) {
@@ -15,13 +15,13 @@
15
15
 
16
16
  <label
17
17
  [for]="id"
18
- class="input block text-sm dark:text-gray-400 w-full h-[40px] pr-1 bg-background dark:bg-dark-background border border-border dark:border-dark-border flex items-center justify-between text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200 cursor-pointer"
18
+ class="input block text-sm w-full h-[40px] pr-1 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground dark:placeholder:text-dark-muted-foreground rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-between transition duration-200 cursor-pointer"
19
19
  >
20
- <div class="truncate" [ngClass]="{ 'opacity-50' : !innerValue?.name }">
20
+ <div class="truncate" [ngClass]="{ 'opacity-80' : !innerValue?.name }">
21
21
  @if (innerValue?.name) {
22
22
  <span>{{ innerValue?.name }}</span>
23
23
  } @else {
24
- <span class="flex items-center gap-2">
24
+ <span class="flex items-center gap-2 text-muted-foreground dark:text-dark-muted-foreground">
25
25
  <lucide-icon [name]="iconsService.icons.upload" [size]="15" />
26
26
  Seleccionar archivo...
27
27
  </span>
@@ -1,21 +1,25 @@
1
1
  <div class="relative w-full flex">
2
- <textarea
3
- [id]="id"
4
- [name]="name ?? ''"
5
- [placeholder]="placeholder"
6
- [value]="value"
7
- (input)="onInput($event)"
8
- [required]="required"
9
- [disabled]="disabled"
10
- (blur)="onTouched()"
11
- [ngClass]="combinedNgClass"
12
- [class]="classes"
13
- class="input w-full h-[70px] min-h-[70px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
14
- ></textarea>
2
+ <textarea
3
+ [id]="id"
4
+ [name]="name ?? ''"
5
+ [placeholder]="placeholder"
6
+ [value]="value"
7
+ (input)="onInput($event)"
8
+ [required]="required"
9
+ [disabled]="disabled"
10
+ (blur)="onTouched()"
11
+ [ngClass]="combinedNgClass"
12
+ [class]="classes"
13
+ class="input w-full h-[70px] min-h-[70px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
14
+ ></textarea>
15
15
 
16
- @if (value && clearButton) {
17
- <button type="button" class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer" (click)="clearInput()">
18
- <lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
19
- </button>
20
- }
21
- </div>
16
+ @if (value && clearButton) {
17
+ <button
18
+ type="button"
19
+ class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
20
+ (click)="clearInput()"
21
+ >
22
+ <lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
23
+ </button>
24
+ }
25
+ </div>
@@ -10,7 +10,7 @@
10
10
  ngClass: true
11
11
  }"
12
12
  >
13
- <span class="truncate text-black dark:text-white" [ngClass]="{'opacity-50' : selectedValue === null}">{{selectedLabel}}</span>
13
+ <span class="truncate text-black dark:text-white" [ngClass]="{'opacity-70 text-muted-foreground dark:text-dark-muted-foreground' : selectedValue === null}">{{selectedLabel}}</span>
14
14
 
15
15
  <div class="flex items-center">
16
16
  @if (showClear && selectedValue !== null) {
@@ -1,3 +1,4 @@
1
+
1
2
  <div class="relative w-full h-full">
2
3
  <div class="w-auto" #selectButton>
3
4
  <button
@@ -7,7 +8,7 @@
7
8
  class="flex w-full h-[40px] items-center justify-between px-3 py-2 text-sm bg-background dark:bg-dark-background border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary select-none"
8
9
  [ngClass]="{ 'opacity-50 cursor-not-allowed pointer-events-none': disabled || isLoading }"
9
10
  >
10
- <span class="truncate text-black dark:text-white" [ngClass]="{ 'opacity-50': selectedValues.length === 0 }">
11
+ <span class="truncate text-black dark:text-white" [ngClass]="{ 'opacity-70 text-muted-foreground dark:text-dark-muted-foreground': selectedValues.length === 0 }">
11
12
  {{ displayLabel }}
12
13
  </span>
13
14