tailjng 0.0.61 → 0.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.
- package/README.md +186 -32
- package/cli/component-manager.js +71 -20
- package/cli/execute/init-app.js +251 -0
- package/cli/file-operations.js +45 -29
- package/cli/index.js +66 -15
- package/cli/settings/components-list.js +4 -147
- package/cli/settings/header-generator.js +9 -10
- package/cli/settings/lib-utils.js +89 -0
- package/cli/settings/overwrite-policy.js +18 -0
- package/cli/settings/path-utils.js +14 -29
- package/cli/settings/project-utils.js +220 -0
- package/cli/settings/prompt-utils.js +66 -5
- package/cli/templates/app.generator.js +382 -0
- package/fesm2022/tailjng.mjs +232 -66
- package/fesm2022/tailjng.mjs.map +1 -1
- package/lib/services/static/colors.service.d.ts +17 -0
- package/lib/services/transformer/transform.service.d.ts +3 -3
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
- package/registry/components.json +164 -0
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +17 -0
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +83 -51
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +85 -53
- package/src/lib/components/alert/alert-toast/toast-alert.component.css +38 -4
- package/src/lib/components/alert/alert-toast/toast-alert.component.html +72 -40
- package/src/lib/components/alert/alert-toast/toast-alert.component.ts +84 -19
- package/src/lib/components/badge/badge.component.ts +1 -2
- package/src/lib/components/button/button.component.css +14 -0
- package/src/lib/components/button/button.component.html +17 -17
- package/src/lib/components/button/button.component.ts +139 -48
- package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +5 -1
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +1 -1
- package/src/lib/components/filter/filter-complete/complete-filter.component.html +1 -1
- package/src/lib/components/form/form-sidebar/sidebar-form.component.html +130 -97
- package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +3 -2
- package/src/lib/components/input/input/input.component.css +35 -0
- package/src/lib/components/input/input/input.component.html +37 -27
- package/src/lib/components/input/input/input.component.ts +1 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +56 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +12 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +41 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.component.css +4 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.component.html +1 -1
- package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +3 -3
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +4 -0
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +1 -1
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +30 -20
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +506 -172
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.scss +92 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +141 -7
- package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.builder.ts +116 -0
- package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.helper.ts +43 -0
- package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.types.ts +39 -0
- package/src/lib/components/table/table-crud-complete/index.ts +3 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.css +4 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.html +4 -4
- package/src/lib/components/toggle-radio/toggle-radio.component.ts +15 -6
- package/src/lib/components/tooltip/tooltip.service.ts +0 -30
- package/tailjng-0.1.0.tgz +0 -0
- package/src/lib/components/color/colors.service.ts +0 -187
|
@@ -2,86 +2,118 @@
|
|
|
2
2
|
@for (toast of toasts(); track toast.id) {
|
|
3
3
|
<div
|
|
4
4
|
@toastTransition
|
|
5
|
-
class="j-
|
|
6
|
-
[ngClass]="
|
|
5
|
+
class="j-toast group relative ml-auto w-full overflow-hidden rounded-xl border border-border/40 dark:border-dark-border/40 backdrop-blur-xl shadow-lg shadow-black/5 dark:shadow-black/40 pointer-events-auto"
|
|
6
|
+
[ngClass]="getToastSurfaceClass(toast.config.type)"
|
|
7
|
+
role="alert"
|
|
8
|
+
aria-live="polite"
|
|
7
9
|
>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
>
|
|
15
|
-
<lucide-icon
|
|
16
|
-
[name]="iconsService.icons.close"
|
|
17
|
-
[size]="16"
|
|
18
|
-
></lucide-icon>
|
|
19
|
-
</button>
|
|
20
|
-
}
|
|
10
|
+
<div
|
|
11
|
+
class="absolute left-0 top-0 w-1 z-[1]"
|
|
12
|
+
[ngClass]="getAccentBarClass(toast.config.type)"
|
|
13
|
+
[class.bottom-0]="!hasAutoClose(toast)"
|
|
14
|
+
[class.bottom-1]="hasAutoClose(toast)"
|
|
15
|
+
></div>
|
|
21
16
|
|
|
22
|
-
<!--
|
|
17
|
+
<!-- Icono decorativo grande -->
|
|
23
18
|
<div
|
|
19
|
+
class="j-toast-watermark j-toast-watermark--lg absolute -bottom-8 -left-5 z-0 pointer-events-none select-none"
|
|
24
20
|
[ngClass]="{ 'animate-spin': toast.config.type === 'loading' }"
|
|
25
|
-
class="absolute -bottom-5 opacity-15 dark:opacity-25 -left-5 text-black dark:text-white z-2 pointer-events-none"
|
|
26
21
|
>
|
|
27
22
|
<lucide-icon
|
|
28
23
|
[name]="getIcon(toast.config.type)"
|
|
29
|
-
[size]="
|
|
30
|
-
[ngClass]="
|
|
24
|
+
[size]="104"
|
|
25
|
+
[ngClass]="getWatermarkIconClass(toast.config.type)"
|
|
31
26
|
></lucide-icon>
|
|
32
27
|
</div>
|
|
33
28
|
|
|
34
|
-
<!--
|
|
29
|
+
<!-- Icono decorativo pequeño -->
|
|
35
30
|
<div
|
|
31
|
+
class="j-toast-watermark j-toast-watermark--sm absolute top-2 z-0 pointer-events-none select-none"
|
|
32
|
+
[class.right-9]="toast.btnClose"
|
|
33
|
+
[class.right-3]="!toast.btnClose"
|
|
36
34
|
[ngClass]="{ 'animate-spin': toast.config.type === 'loading' }"
|
|
37
|
-
class="absolute top-5 opacity-15 dark:opacity-25 right-5 text-black dark:text-white z-2 pointer-events-none"
|
|
38
35
|
>
|
|
39
36
|
<lucide-icon
|
|
40
37
|
[name]="getIcon(toast.config.type)"
|
|
41
|
-
[size]="
|
|
42
|
-
[ngClass]="
|
|
38
|
+
[size]="32"
|
|
39
|
+
[ngClass]="getWatermarkIconClass(toast.config.type, true)"
|
|
43
40
|
></lucide-icon>
|
|
44
41
|
</div>
|
|
45
42
|
|
|
46
|
-
<!--
|
|
47
|
-
<div
|
|
48
|
-
|
|
43
|
+
<!-- Texto -->
|
|
44
|
+
<div
|
|
45
|
+
class="relative z-10 pl-5 pr-4 pt-3.5"
|
|
46
|
+
[class.pr-10]="toast.btnClose"
|
|
47
|
+
[class.pb-2]="hasActions(toast)"
|
|
48
|
+
[class.pb-3.5]="!hasActions(toast) && !hasAutoClose(toast)"
|
|
49
|
+
[class.pb-4]="!hasActions(toast) && hasAutoClose(toast)"
|
|
50
|
+
>
|
|
51
|
+
<h3 class="m-0 text-sm font-semibold leading-tight text-foreground dark:text-white">
|
|
49
52
|
{{ toast.config.title }}
|
|
50
53
|
</h3>
|
|
51
|
-
<p
|
|
52
|
-
class="text-sm text-black/80 dark:text-dark-muted-foreground"
|
|
53
|
-
[innerHTML]="toast.config.description"
|
|
54
|
-
></p>
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
@if (toast.config.description) {
|
|
56
|
+
<p
|
|
57
|
+
class="m-0 mt-1 text-xs leading-relaxed text-muted-foreground dark:text-dark-muted-foreground [&_b]:font-semibold [&_b]:text-foreground dark:[&_b]:text-white"
|
|
58
|
+
[innerHTML]="toast.config.description"
|
|
59
|
+
></p>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@if (toast.btnClose) {
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
class="absolute top-2.5 right-2.5 z-20 flex h-7 w-7 items-center justify-center rounded-lg transition-all duration-200 hover:bg-black/5 dark:hover:bg-white/10 cursor-pointer"
|
|
66
|
+
[ngClass]="getIconClass(toast.config.type)"
|
|
67
|
+
[attr.aria-label]="'Cerrar notificación'"
|
|
68
|
+
(click)="closeToast(toast.id)"
|
|
69
|
+
>
|
|
70
|
+
<lucide-icon [name]="iconsService.icons.close" [size]="15"></lucide-icon>
|
|
71
|
+
</button>
|
|
72
|
+
}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<!-- Acciones: alineadas a la card, no al bloque de texto -->
|
|
76
|
+
@if (hasActions(toast)) {
|
|
77
|
+
<div
|
|
78
|
+
class="relative z-10 flex flex-wrap justify-end gap-1.5 pl-5 pr-4"
|
|
79
|
+
[class.pb-3.5]="!hasAutoClose(toast)"
|
|
80
|
+
[class.pb-4]="hasAutoClose(toast)"
|
|
81
|
+
>
|
|
82
|
+
@if (toast.config.type !== 'success' && toast.onCancelCallback) {
|
|
60
83
|
<JButton
|
|
84
|
+
size="xs"
|
|
61
85
|
(clicked)="handleAction(toast.id, 'cancel')"
|
|
62
86
|
[disabled]="toast.isCancelLoading || toast.isActionLoading"
|
|
63
87
|
[isLoading]="toast.isCancelLoading"
|
|
64
|
-
classes="text-[10px] min-w-auto"
|
|
65
88
|
[ngClasses]="getButtonSecondaryClass(toast.config.type)"
|
|
66
89
|
>
|
|
67
|
-
{{ toast.config.titleBtnCancel ||
|
|
90
|
+
{{ toast.config.titleBtnCancel || 'Cancelar' }}
|
|
68
91
|
</JButton>
|
|
69
92
|
}
|
|
70
93
|
|
|
71
|
-
|
|
72
|
-
@if (toast.config.type !== "loading" && toast.onActionCallback) {
|
|
94
|
+
@if (toast.config.type !== 'loading' && toast.onActionCallback) {
|
|
73
95
|
<JButton
|
|
96
|
+
size="xs"
|
|
74
97
|
(clicked)="handleAction(toast.id, 'action')"
|
|
75
98
|
[disabled]="toast.isCancelLoading || toast.isActionLoading"
|
|
76
99
|
[isLoading]="toast.isActionLoading"
|
|
77
|
-
classes="text-[10px] min-w-auto"
|
|
78
100
|
[ngClasses]="getButtonClass(toast.config.type)"
|
|
79
101
|
>
|
|
80
102
|
{{ toast.actionNameButton }}
|
|
81
103
|
</JButton>
|
|
82
104
|
}
|
|
83
105
|
</div>
|
|
84
|
-
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@if (hasAutoClose(toast)) {
|
|
109
|
+
<div class="j-toast-timeline absolute inset-x-0 bottom-0 z-20 h-1 bg-black/5 dark:bg-white/10">
|
|
110
|
+
<div
|
|
111
|
+
class="j-toast-progress h-full origin-left"
|
|
112
|
+
[ngClass]="getAccentBarClass(toast.config.type)"
|
|
113
|
+
[style.animation-duration.ms]="getAutoCloseDelay(toast)"
|
|
114
|
+
></div>
|
|
115
|
+
</div>
|
|
116
|
+
}
|
|
85
117
|
</div>
|
|
86
118
|
}
|
|
87
119
|
</div>
|
|
@@ -3,8 +3,7 @@ import { trigger, transition, style, animate } from "@angular/animations";
|
|
|
3
3
|
import { NgClass } from '@angular/common';
|
|
4
4
|
import { LucideAngularModule } from 'lucide-angular';
|
|
5
5
|
import { JButtonComponent } from '../../button/button.component';
|
|
6
|
-
import { JColorsService } from '
|
|
7
|
-
import { JIconsService, JAlertToastService } from 'tailjng';
|
|
6
|
+
import { JIconsService, JAlertToastService, JColorsService } from 'tailjng';
|
|
8
7
|
|
|
9
8
|
@Component({
|
|
10
9
|
selector: 'JAlertToast',
|
|
@@ -12,16 +11,16 @@ import { JIconsService, JAlertToastService } from 'tailjng';
|
|
|
12
11
|
templateUrl: './toast-alert.component.html',
|
|
13
12
|
styleUrl: './toast-alert.component.css',
|
|
14
13
|
animations: [
|
|
15
|
-
trigger(
|
|
16
|
-
transition(
|
|
17
|
-
style({ opacity: 0, transform:
|
|
18
|
-
animate(
|
|
14
|
+
trigger('toastTransition', [
|
|
15
|
+
transition(':enter', [
|
|
16
|
+
style({ opacity: 0, transform: 'translateX(1.25rem) scale(0.98)' }),
|
|
17
|
+
animate('320ms cubic-bezier(0.16, 1, 0.3, 1)', style({ opacity: 1, transform: 'translateX(0) scale(1)' })),
|
|
19
18
|
]),
|
|
20
|
-
transition(
|
|
21
|
-
animate(
|
|
22
|
-
])
|
|
23
|
-
])
|
|
24
|
-
]
|
|
19
|
+
transition(':leave', [
|
|
20
|
+
animate('180ms cubic-bezier(0.4, 0, 1, 1)', style({ opacity: 0, transform: 'translateX(0.75rem) scale(0.98)' })),
|
|
21
|
+
]),
|
|
22
|
+
]),
|
|
23
|
+
],
|
|
25
24
|
})
|
|
26
25
|
export class JAlertToastComponent {
|
|
27
26
|
|
|
@@ -71,15 +70,81 @@ export class JAlertToastComponent {
|
|
|
71
70
|
|
|
72
71
|
|
|
73
72
|
/**
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
* Fondo suave del toast según el tipo (tinte del diseño anterior + glass actual).
|
|
74
|
+
*/
|
|
75
|
+
getToastSurfaceClass(type: string): string {
|
|
76
|
+
if (this.monocromatic) {
|
|
77
|
+
return 'bg-white/95 dark:bg-foreground/95';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const surfaces: Record<string, string> = {
|
|
81
|
+
success: 'bg-green-50/95 dark:bg-[#15241f]/95',
|
|
82
|
+
error: 'bg-red-50/95 dark:bg-[#21181c]/95',
|
|
83
|
+
warning: 'bg-yellow-50/95 dark:bg-[#1f1c1a]/95',
|
|
84
|
+
info: 'bg-blue-50/95 dark:bg-[#1a1a24]/95',
|
|
85
|
+
question: 'bg-purple-50/95 dark:bg-[#241732]/95',
|
|
86
|
+
loading: 'bg-gray-50/95 dark:bg-[#15181e]/95',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return surfaces[type] ?? 'bg-white/95 dark:bg-foreground/95';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Opacidad del icono decorativo de fondo.
|
|
94
|
+
*/
|
|
95
|
+
getWatermarkIconClass(type: string, subtle = false): string {
|
|
96
|
+
if (subtle) {
|
|
97
|
+
return `${this.getIconClass(type)} opacity-[0.12] dark:opacity-[0.2]`;
|
|
98
|
+
}
|
|
99
|
+
return `${this.getIconClass(type)} opacity-[0.2] dark:opacity-[0.3]`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Barra lateral de acento según el tipo de toast.
|
|
77
104
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
105
|
+
getAccentBarClass(type: string): string {
|
|
106
|
+
if (this.monocromatic) return 'bg-primary';
|
|
107
|
+
|
|
108
|
+
const accents: Record<string, string> = {
|
|
109
|
+
success: 'bg-green-500',
|
|
110
|
+
error: 'bg-red-500',
|
|
111
|
+
warning: 'bg-yellow-500',
|
|
112
|
+
info: 'bg-blue-500',
|
|
113
|
+
question: 'bg-purple-500',
|
|
114
|
+
loading: 'bg-gray-500',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return accents[type] ?? 'bg-primary';
|
|
80
118
|
}
|
|
81
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Indica si el toast muestra botones de acción o cancelar.
|
|
122
|
+
*/
|
|
123
|
+
hasActions(toast: {
|
|
124
|
+
config: { type?: string };
|
|
125
|
+
onCancelCallback?: unknown;
|
|
126
|
+
onActionCallback?: unknown;
|
|
127
|
+
}): boolean {
|
|
128
|
+
return (
|
|
129
|
+
(toast.config.type !== 'success' && Boolean(toast.onCancelCallback)) ||
|
|
130
|
+
(toast.config.type !== 'loading' && Boolean(toast.onActionCallback))
|
|
131
|
+
);
|
|
132
|
+
}
|
|
82
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Indica si el toast se cierra automáticamente.
|
|
136
|
+
*/
|
|
137
|
+
hasAutoClose(toast: { config: { type?: string; autoClose?: boolean } }): boolean {
|
|
138
|
+
const { config } = toast;
|
|
139
|
+
return config.autoClose === true || (config.type === 'success' && config.autoClose !== false);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Duración del auto-cierre en milisegundos.
|
|
144
|
+
*/
|
|
145
|
+
getAutoCloseDelay(toast: { config: { autoCloseDelay?: number } }): number {
|
|
146
|
+
return toast.config.autoCloseDelay ?? 5000;
|
|
147
|
+
}
|
|
83
148
|
|
|
84
149
|
/**
|
|
85
150
|
* Get the class of the icon.
|
|
@@ -119,12 +184,12 @@ export class JAlertToastComponent {
|
|
|
119
184
|
* @returns The class of the toast.
|
|
120
185
|
*/
|
|
121
186
|
getPositionClass(): string {
|
|
122
|
-
const base = 'w-full fixed z-1000 flex flex-col gap-
|
|
187
|
+
const base = 'w-full fixed z-1000 flex flex-col gap-3 max-w-[22rem] pointer-events-none p-1';
|
|
123
188
|
switch (this.position) {
|
|
124
189
|
case 'top-left':
|
|
125
|
-
return `${base} top-
|
|
190
|
+
return `${base} top-18 left-3`;
|
|
126
191
|
case 'top-right':
|
|
127
|
-
return `${base} top-
|
|
192
|
+
return `${base} top-18 right-3`;
|
|
128
193
|
case 'bottom-left':
|
|
129
194
|
return `${base} bottom-4 left-4`;
|
|
130
195
|
case 'bottom-right':
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
2
2
|
import { JTooltipDirective } from '../tooltip/tooltip.directive';
|
|
3
|
-
import { JIconsService } from 'tailjng';
|
|
3
|
+
import { JIconsService, JColorsService } from 'tailjng';
|
|
4
4
|
import { LucideAngularModule } from 'lucide-angular';
|
|
5
5
|
import { NgClass } from '@angular/common';
|
|
6
|
-
import { JColorsService } from '../color/colors.service';
|
|
7
6
|
|
|
8
7
|
@Component({
|
|
9
8
|
selector: 'JBadge',
|
|
@@ -7,30 +7,30 @@
|
|
|
7
7
|
[class]="classes"
|
|
8
8
|
(click)="handleClick($event)"
|
|
9
9
|
>
|
|
10
|
-
<
|
|
11
|
-
|
|
10
|
+
<span
|
|
11
|
+
class="inline-flex items-center justify-center"
|
|
12
|
+
[ngClass]="innerContentClasses"
|
|
13
|
+
>
|
|
12
14
|
@if (isLoading) {
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
<lucide-icon
|
|
16
|
+
[name]="iconsService.icons.loading"
|
|
17
|
+
[size]="resolvedIconSize"
|
|
18
|
+
class="animate-spin shrink-0"
|
|
19
|
+
/>
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
@if (!isLoading && icon) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<lucide-icon [name]="iconChange" [size]="iconSize" />
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
</div>
|
|
23
|
+
@if (!isChangeIcon) {
|
|
24
|
+
<lucide-icon [name]="icon" [size]="resolvedIconSize" class="shrink-0" />
|
|
25
|
+
} @else {
|
|
26
|
+
<lucide-icon [name]="iconChange" [size]="resolvedIconSize" class="shrink-0" />
|
|
27
|
+
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
@if (text && (!isLoading || isLoadingText)) {
|
|
31
|
-
<span
|
|
31
|
+
<span>{{ text }}</span>
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
<ng-content></ng-content>
|
|
35
|
-
</
|
|
36
|
-
</button>
|
|
35
|
+
</span>
|
|
36
|
+
</button>
|
|
@@ -1,96 +1,187 @@
|
|
|
1
|
-
import { NgClass } from
|
|
2
|
-
import { Component, Input, Output, EventEmitter } from
|
|
3
|
-
import { LucideAngularModule } from
|
|
4
|
-
import { JIconsService } from
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
import { NgClass } from '@angular/common';
|
|
2
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
3
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
4
|
+
import { JIconsService, JColorsService } from 'tailjng';
|
|
5
|
+
import { JTooltipDirective } from '../tooltip/tooltip.directive';
|
|
6
|
+
|
|
7
|
+
export type JButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'icon';
|
|
7
8
|
|
|
8
9
|
@Component({
|
|
9
10
|
selector: 'JButton',
|
|
10
11
|
imports: [NgClass, LucideAngularModule, JTooltipDirective],
|
|
11
12
|
templateUrl: './button.component.html',
|
|
12
|
-
styleUrl: './button.component.css'
|
|
13
|
+
styleUrl: './button.component.css',
|
|
13
14
|
})
|
|
14
15
|
export class JButtonComponent {
|
|
15
16
|
|
|
16
|
-
@Input() type:
|
|
17
|
-
@Input() tooltipPosition:
|
|
17
|
+
@Input() type: 'button' | 'submit' | 'reset' = 'button';
|
|
18
|
+
@Input() tooltipPosition: 'top' | 'right' | 'bottom' | 'left' = 'top';
|
|
18
19
|
|
|
19
20
|
@Input() text!: string | number;
|
|
20
|
-
@Input() tooltip: string =
|
|
21
|
+
@Input() tooltip: string = '';
|
|
21
22
|
|
|
22
23
|
@Input() icon!: any;
|
|
23
|
-
@Input() iconSize: number =
|
|
24
|
+
@Input() iconSize: number = 0;
|
|
24
25
|
@Input() iconChange!: any;
|
|
25
26
|
@Input() isChangeIcon: boolean = false;
|
|
26
27
|
|
|
28
|
+
/** xs/sm: alertas y cards · md: formularios · lg: acciones destacadas · icon: solo icono */
|
|
29
|
+
@Input() size: JButtonSize = 'md';
|
|
30
|
+
|
|
27
31
|
@Output() clicked = new EventEmitter<Event>();
|
|
28
32
|
|
|
29
33
|
@Input() disabled = false;
|
|
30
34
|
@Input() isLoading = false;
|
|
31
35
|
@Input() isLoadingText = true;
|
|
32
36
|
|
|
33
|
-
@Input() classes: string =
|
|
37
|
+
@Input() classes: string = '';
|
|
34
38
|
@Input() ngClasses: { [key: string]: boolean } = {};
|
|
35
39
|
|
|
40
|
+
constructor(
|
|
41
|
+
public readonly iconsService: JIconsService,
|
|
42
|
+
private readonly colorsService: JColorsService,
|
|
43
|
+
) { }
|
|
36
44
|
|
|
37
|
-
// Define classes based on button type (switch)
|
|
38
45
|
get variantClasses(): string {
|
|
39
|
-
return this.colorsService.variants[this.getActiveVariant()]
|
|
46
|
+
return this.colorsService.variants[this.getActiveVariant()]
|
|
47
|
+
?? 'text-black dark:text-white shadow-md border border-border dark:border-dark-border';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get resolvedIconSize(): number {
|
|
51
|
+
if (this.iconSize > 0) return this.iconSize;
|
|
52
|
+
|
|
53
|
+
switch (this.size) {
|
|
54
|
+
case 'xs': return 14;
|
|
55
|
+
case 'sm': return 14;
|
|
56
|
+
case 'icon': return 16;
|
|
57
|
+
case 'lg': return 18;
|
|
58
|
+
default: return 15;
|
|
59
|
+
}
|
|
40
60
|
}
|
|
41
61
|
|
|
42
|
-
// Combine base classes with variants
|
|
43
62
|
get computedClasses() {
|
|
63
|
+
const result: Record<string, boolean> = {
|
|
64
|
+
'inline-flex items-center justify-center font-semibold border border-border dark:border-dark-border transition duration-300 select-none': true,
|
|
65
|
+
[this.dimensionClasses]: !this.hasCustomDimensions(),
|
|
66
|
+
[this.paddingClasses]: !this.hasCustomPadding(),
|
|
67
|
+
[this.radiusClasses]: !this.hasCustomRadius(),
|
|
68
|
+
[this.variantClasses]: true,
|
|
69
|
+
'cursor-pointer': !this.disabled && !this.isLoading,
|
|
70
|
+
'cursor-default opacity-50 pointer-events-none': this.disabled || this.isLoading,
|
|
71
|
+
...this.ngClasses,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (!this.hasCustomTextSize() && this.textSizeClasses) {
|
|
75
|
+
result[this.textSizeClasses] = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get innerContentClasses(): Record<string, boolean> {
|
|
44
82
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
83
|
+
'gap-1': this.size === 'xs' || this.size === 'icon',
|
|
84
|
+
'gap-1.5': this.size === 'sm',
|
|
85
|
+
'gap-2': this.size === 'md' || this.size === 'lg',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get dimensionClasses(): string {
|
|
90
|
+
switch (this.size) {
|
|
91
|
+
case 'xs':
|
|
92
|
+
return 'h-7 min-w-0';
|
|
93
|
+
case 'sm':
|
|
94
|
+
return 'h-8 min-w-0';
|
|
95
|
+
case 'icon':
|
|
96
|
+
return 'h-8 w-8 min-w-0 shrink-0';
|
|
97
|
+
case 'lg':
|
|
98
|
+
return 'min-h-10 min-w-[7.5rem]';
|
|
99
|
+
case 'md':
|
|
100
|
+
default:
|
|
101
|
+
return this.icon && !this.text
|
|
102
|
+
? 'min-h-9 min-w-9'
|
|
103
|
+
: 'min-h-9 min-w-[6.25rem]';
|
|
50
104
|
}
|
|
51
105
|
}
|
|
52
106
|
|
|
107
|
+
get paddingClasses(): string {
|
|
108
|
+
switch (this.size) {
|
|
109
|
+
case 'xs':
|
|
110
|
+
return 'px-2.5 py-0';
|
|
111
|
+
case 'sm':
|
|
112
|
+
return 'px-3 py-0';
|
|
113
|
+
case 'icon':
|
|
114
|
+
return 'p-0';
|
|
115
|
+
case 'lg':
|
|
116
|
+
return 'px-4 py-2.5';
|
|
117
|
+
case 'md':
|
|
118
|
+
default:
|
|
119
|
+
return 'px-3 py-2 max-[400px]:px-2 max-[400px]:py-1.5';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
53
122
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
123
|
+
get textSizeClasses(): string {
|
|
124
|
+
switch (this.size) {
|
|
125
|
+
case 'xs':
|
|
126
|
+
return 'text-[11px] leading-tight font-medium';
|
|
127
|
+
case 'sm':
|
|
128
|
+
return 'text-xs leading-tight font-medium';
|
|
129
|
+
case 'lg':
|
|
130
|
+
return 'text-base leading-snug';
|
|
131
|
+
case 'icon':
|
|
132
|
+
return '';
|
|
133
|
+
case 'md':
|
|
134
|
+
default:
|
|
135
|
+
return 'text-sm leading-snug max-[400px]:text-xs';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
58
138
|
|
|
139
|
+
get radiusClasses(): string {
|
|
140
|
+
return this.size === 'icon' || this.size === 'xs' || this.size === 'sm'
|
|
141
|
+
? 'rounded-lg'
|
|
142
|
+
: 'rounded-md';
|
|
143
|
+
}
|
|
59
144
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
private hasClass(className: string): boolean {
|
|
67
|
-
const classArray = this.classes.split(" ")
|
|
68
|
-
return classArray.includes(className) || this.ngClasses[className]
|
|
145
|
+
private getClassTokens(): string[] {
|
|
146
|
+
const fromClasses = this.classes.split(/\s+/).filter(Boolean);
|
|
147
|
+
const fromNgClasses = Object.entries(this.ngClasses)
|
|
148
|
+
.filter(([, active]) => active)
|
|
149
|
+
.map(([className]) => className);
|
|
150
|
+
return [...fromClasses, ...fromNgClasses];
|
|
69
151
|
}
|
|
70
152
|
|
|
153
|
+
private matchesTokenPattern(pattern: RegExp): boolean {
|
|
154
|
+
return this.getClassTokens().some((token) => pattern.test(token));
|
|
155
|
+
}
|
|
71
156
|
|
|
157
|
+
hasCustomDimensions(): boolean {
|
|
158
|
+
return this.matchesTokenPattern(/^!?((min|max)-)?[wh](-|\[|$)|^!?size-/);
|
|
159
|
+
}
|
|
72
160
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* It checks if the class exists in the `variants` object of `JColorsService
|
|
76
|
-
* @returns The active variant based on the provided classes.
|
|
77
|
-
*/
|
|
78
|
-
private getActiveVariant(): string {
|
|
79
|
-
const variant = Object.keys(this.colorsService.variants).find((variant) => this.hasClass(variant))
|
|
80
|
-
return variant ?? "default"
|
|
161
|
+
hasCustomPadding(): boolean {
|
|
162
|
+
return this.matchesTokenPattern(/^!?p[xytblr]?(-|\[|$)|^!?p$/);
|
|
81
163
|
}
|
|
82
164
|
|
|
165
|
+
hasCustomTextSize(): boolean {
|
|
166
|
+
return this.matchesTokenPattern(/^!?text(-|\[|$)/);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
hasCustomRadius(): boolean {
|
|
170
|
+
return this.matchesTokenPattern(/^!?rounded(-|\[|$)/);
|
|
171
|
+
}
|
|
83
172
|
|
|
173
|
+
private hasClass(className: string): boolean {
|
|
174
|
+
return this.getClassTokens().includes(className);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private getActiveVariant(): string {
|
|
178
|
+
const variants = Object.keys(this.colorsService.variants).sort((a, b) => b.length - a.length);
|
|
179
|
+
return variants.find((variant) => this.hasClass(variant)) ?? 'default';
|
|
180
|
+
}
|
|
84
181
|
|
|
85
|
-
/**
|
|
86
|
-
* Handle click event on the button.
|
|
87
|
-
* Emits the clicked event if the button is not disabled and not loading.
|
|
88
|
-
* @param event The click event
|
|
89
|
-
*/
|
|
90
182
|
handleClick(event: Event) {
|
|
91
183
|
if (!this.disabled && !this.isLoading) {
|
|
92
|
-
this.clicked.emit(event)
|
|
184
|
+
this.clicked.emit(event);
|
|
93
185
|
}
|
|
94
186
|
}
|
|
95
|
-
|
|
96
187
|
}
|
|
@@ -48,6 +48,8 @@ export class JCompleteCrudCardComponent implements OnInit {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
@Input() endpoint!: string;
|
|
51
|
+
// Clave alternativa de response.data cuando difiere del mainEndpoint (ej. reminders en reminder/mine)
|
|
52
|
+
@Input() responseKey?: string;
|
|
51
53
|
mainEndpoint!: string;
|
|
52
54
|
@Input() columns: TableColumn<any>[] = [];
|
|
53
55
|
@Input() defaultFilters: { [key: string]: any } = {};
|
|
@@ -177,7 +179,9 @@ export class JCompleteCrudCardComponent implements OnInit {
|
|
|
177
179
|
// setTimeout(() => {
|
|
178
180
|
this.genericService.findAll<any>({ endpoint: this.endpoint, params }).subscribe({
|
|
179
181
|
next: (response) => {
|
|
180
|
-
|
|
182
|
+
// Usa responseKey si la API devuelve una colección con nombre distinto al endpoint
|
|
183
|
+
const dataKey = this.responseKey ?? this.mainEndpoint;
|
|
184
|
+
this.data = response.data[dataKey] ?? [];
|
|
181
185
|
|
|
182
186
|
if (response.meta?.page) {
|
|
183
187
|
this.totalItems = response.meta.page.totalRecords;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
>
|
|
11
11
|
<!-- Slider background -->
|
|
12
12
|
<div class="absolute inset-0 rounded-full transition-all duration-300"
|
|
13
|
-
[ngClass]="isChecked ? 'bg-primary' : 'bg-gray-300 dark:bg-gray-500'">
|
|
13
|
+
[ngClass]="isChecked ? 'bg-dark-primary dark:bg-primary' : 'bg-gray-300 dark:bg-gray-500'">
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
16
|
<!-- Circle -->
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
[tooltipPosition]="button.tooltipPosition ?? 'top'"
|
|
96
96
|
(clicked)="filterBottomClick(button)"
|
|
97
97
|
[classes]="button.classes ?? ''"
|
|
98
|
-
[ngClasses]="{ 'min-w-auto p-1! pl-2! pr-2!': true }"
|
|
98
|
+
[ngClasses]="{ 'min-w-auto p-1! pl-2! pr-2! w-[40px]! h-[40px]!': true }"
|
|
99
99
|
/>
|
|
100
100
|
|
|
101
101
|
@if (button.type === "upload") {
|