tailjng 0.1.6 → 0.1.7
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 +12 -4
- package/cli/execute/init-app.js +5 -2
- package/cli/execute/sync-app.js +14 -2
- package/cli/settings/colors-config-utils.js +43 -8
- package/cli/settings/icons-config-utils.js +62 -0
- package/cli/settings/path-utils.js +32 -2
- package/cli/settings/project-utils.js +7 -1
- package/cli/templates/app.generator.js +2 -2
- package/fesm2022/tailjng.mjs +247 -80
- package/fesm2022/tailjng.mjs.map +1 -1
- package/lib/services/static/theme.service.d.ts +39 -1
- package/lib/utils/theme/theme-variables.util.d.ts +31 -0
- package/package.json +1 -1
- package/public-api.d.ts +2 -1
- package/registry/components.json +41 -18
- package/src/colors.safelist.css +2 -2
- package/src/lib/components/.config/README.md +11 -0
- package/src/lib/components/.config/colors/README.md +38 -0
- package/src/lib/components/{colors-config → .config/colors}/colors.config.ts +5 -5
- package/src/lib/components/{colors-config → .config/colors}/colors.safelist.css +2 -2
- package/src/lib/components/.config/icons/README.md +26 -0
- package/src/lib/components/.config/icons/icons.lucide.ts +134 -0
- package/src/lib/components/.config/input/README.md +24 -0
- package/src/lib/components/.config/input/input.classes.ts +119 -0
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +244 -2
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +25 -38
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +66 -56
- package/src/lib/components/alert/alert-dialog/dialog-alert.types.ts +19 -0
- package/src/lib/components/alert/alert-toast/toast-alert.component.css +630 -12
- package/src/lib/components/alert/alert-toast/toast-alert.component.html +103 -102
- package/src/lib/components/alert/alert-toast/toast-alert.component.ts +485 -128
- package/src/lib/components/alert/alert-toast/toast-alert.types.ts +25 -0
- package/src/lib/components/badge/badge.component.html +34 -21
- package/src/lib/components/badge/badge.component.ts +140 -31
- package/src/lib/components/button/button.component.html +16 -10
- package/src/lib/components/button/button.component.ts +162 -22
- package/src/lib/components/card/card-complete/complete-card.component.html +2 -2
- package/src/lib/components/card/card-complete/complete-card.component.ts +26 -16
- package/src/lib/components/card/card-crud-complete/complete-crud-card.component.html +2 -2
- package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +26 -16
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.css +97 -0
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.html +54 -46
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.ts +135 -64
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.types.ts +3 -0
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.css +112 -0
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +28 -25
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.ts +67 -15
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.types.ts +1 -0
- package/src/lib/components/coach-mark/coach-mark.component.html +4 -22
- package/src/lib/components/coach-mark/coach-mark.component.scss +1 -1
- package/src/lib/components/coach-mark/coach-mark.component.ts +51 -18
- package/src/lib/components/coach-mark/coach-mark.directive.ts +133 -78
- package/src/lib/components/coach-mark/coach-mark.types.ts +12 -0
- package/src/lib/components/dialog/dialog.component.css +103 -1
- package/src/lib/components/dialog/dialog.component.html +46 -66
- package/src/lib/components/dialog/dialog.component.ts +136 -110
- package/src/lib/components/dialog/dialog.types.ts +19 -0
- package/src/lib/components/filter/filter-complete/complete-filter.component.html +16 -19
- package/src/lib/components/filter/filter-complete/complete-filter.component.scss +35 -0
- package/src/lib/components/filter/filter-complete/complete-filter.component.ts +58 -34
- package/src/lib/components/filter/filter-complete/complete-filter.types.ts +7 -0
- package/src/lib/components/filter/filter-complete/complete-filter.util.ts +16 -0
- package/src/lib/components/form/form-container/container-form.component.css +4 -0
- package/src/lib/components/form/form-container/container-form.component.html +2 -2
- package/src/lib/components/form/form-container/container-form.component.ts +72 -16
- package/src/lib/components/form/form-container/container-form.types.ts +42 -0
- package/src/lib/components/form/form-container/form-col-span.directive.ts +25 -0
- package/src/lib/components/form/form-sidebar/sidebar-form.component.css +276 -0
- package/src/lib/components/form/form-sidebar/sidebar-form.component.html +117 -125
- package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +109 -34
- package/src/lib/components/form/form-sidebar/sidebar-form.types.ts +3 -0
- package/src/lib/components/{toggle-radio/toggle-radio.component.css → form/form-validation/validation-form.component.css} +0 -1
- package/src/lib/components/form/form-validation/validation-form.component.html +10 -6
- package/src/lib/components/form/form-validation/validation-form.component.ts +99 -12
- package/src/lib/components/form/form-validation/validation-form.types.ts +33 -0
- package/src/lib/components/icon/icon.component.html +8 -5
- package/src/lib/components/icon/icon.component.ts +111 -9
- package/src/lib/components/input/input/input.component.html +19 -16
- package/src/lib/components/input/input/input.component.ts +130 -53
- package/src/lib/components/input/input/input.types.ts +8 -0
- package/src/lib/components/input/input-file/file-input.component.html +65 -56
- package/src/lib/components/input/input-file/file-input.component.ts +276 -173
- package/src/lib/components/input/input-file/file-input.types.ts +2 -0
- package/src/lib/components/input/input-range/range-input.component.css +67 -0
- package/src/lib/components/input/input-range/range-input.component.html +50 -58
- package/src/lib/components/input/input-range/range-input.component.ts +148 -60
- package/src/lib/components/input/input-range/range-input.types.ts +7 -0
- package/src/lib/components/input/input-textarea/textarea-input.component.html +16 -7
- package/src/lib/components/input/input-textarea/textarea-input.component.ts +140 -50
- package/src/lib/components/input/input-textarea/textarea-input.types.ts +2 -0
- package/src/lib/components/label/label.component.html +17 -16
- package/src/lib/components/label/label.component.ts +70 -16
- package/src/lib/components/label/label.types.ts +2 -0
- package/src/lib/components/menu/menu-options-table/menu-options-defaults.ts +34 -0
- package/src/lib/components/menu/menu-options-table/options-table-menu.component.html +34 -20
- package/src/lib/components/menu/menu-options-table/options-table-menu.component.ts +211 -58
- package/src/lib/components/menu/menu-options-table/options-table-menu.types.ts +38 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +49 -52
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +112 -24
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.types.ts +9 -0
- package/src/lib/components/mode-toggle/mode-toggle.component.html +11 -16
- package/src/lib/components/mode-toggle/mode-toggle.component.ts +69 -33
- package/src/lib/components/paginator/paginator-complete/complete-paginator.component.html +4 -4
- package/src/lib/components/paginator/paginator-complete/complete-paginator.component.ts +31 -7
- package/src/lib/components/paginator/paginator-complete/complete-paginator.types.ts +12 -0
- package/src/lib/components/paginator/paginator-complete/complete-paginator.util.ts +36 -0
- package/src/lib/components/progress-bar/progress-bar.component.css +11 -0
- package/src/lib/components/progress-bar/progress-bar.component.html +41 -40
- package/src/lib/components/progress-bar/progress-bar.component.ts +95 -11
- package/src/lib/components/progress-bar/progress-bar.types.ts +2 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.component.css +6 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.component.html +54 -44
- package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +450 -509
- package/src/lib/components/select/select-dropdown/dropdown-select.types.ts +43 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.util.ts +179 -0
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +6 -0
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +131 -42
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +491 -475
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.types.ts +22 -0
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.util.ts +20 -0
- package/src/lib/components/select/select-multi-table/multi-table-select.component.css +10 -0
- package/src/lib/components/select/select-multi-table/multi-table-select.component.html +76 -60
- package/src/lib/components/select/select-multi-table/multi-table-select.component.ts +250 -313
- package/src/lib/components/select/select-multi-table/multi-table-select.types.ts +10 -0
- package/src/lib/components/select/select-multi-table/multi-table-select.util.ts +5 -0
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.css +212 -0
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.html +62 -53
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.ts +84 -27
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.types.ts +2 -0
- package/src/lib/components/table/table-complete/complete-table.component.html +15 -17
- package/src/lib/components/table/table-complete/complete-table.component.ts +190 -338
- package/src/lib/components/table/table-complete/complete-table.types.ts +28 -0
- package/src/lib/components/table/table-complete/complete-table.util.ts +236 -0
- package/src/lib/components/table/table-complete/index.ts +2 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.animations.ts +34 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +73 -128
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +542 -829
- package/src/lib/components/table/table-crud-complete/complete-crud-table.types.ts +57 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.util.ts +723 -0
- package/src/lib/components/table/table-crud-complete/index.ts +3 -0
- package/src/lib/components/theme-generator/theme-generator.component.css +21 -0
- package/src/lib/components/theme-generator/theme-generator.component.html +146 -116
- package/src/lib/components/theme-generator/theme-generator.component.ts +44 -24
- package/src/lib/components/toggle-radio/shared/toggle-options.types.ts +8 -0
- package/src/lib/components/toggle-radio/shared/toggle-options.util.ts +44 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.css +135 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.html +52 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.ts +198 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.types.ts +1 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.css +108 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.html +37 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.ts +193 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.types.ts +1 -0
- package/src/lib/components/tooltip/tooltip.directive.ts +12 -9
- package/src/lib/components/tooltip/tooltip.service.ts +331 -133
- package/src/lib/components/tooltip/tooltip.types.ts +9 -0
- package/src/lib/components/viewer/viewer-image/image-viewer.component.css +90 -4
- package/src/lib/components/viewer/viewer-image/image-viewer.component.html +52 -103
- package/src/lib/components/viewer/viewer-image/image-viewer.component.ts +182 -177
- package/src/lib/components/viewer/viewer-image/image-viewer.types.ts +3 -0
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.css +177 -0
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.html +74 -24
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.ts +168 -15
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.types.ts +1 -0
- package/src/styles.css +2 -2
- package/lib/services/static/icons.service.d.ts +0 -65
- package/src/lib/components/colors-config/README.md +0 -38
- package/src/lib/components/form/form-sidebar/sidebar-form.component.scss +0 -0
- package/src/lib/components/form/form-validation/validation-form.component.scss +0 -0
- package/src/lib/components/menu/menu-options-table/options-table-menu.component.scss +0 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +0 -12
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.scss +0 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.html +0 -51
- package/src/lib/components/toggle-radio/toggle-radio.component.ts +0 -222
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.scss +0 -0
- package/tailjng-0.1.6.tgz +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LucideIconData } from 'lucide-angular';
|
|
2
|
+
import { Icons } from '../../.config/icons/icons.lucide';
|
|
3
|
+
|
|
4
|
+
export type AlertToastType =
|
|
5
|
+
| 'success'
|
|
6
|
+
| 'error'
|
|
7
|
+
| 'warning'
|
|
8
|
+
| 'info'
|
|
9
|
+
| 'question'
|
|
10
|
+
| 'loading';
|
|
11
|
+
|
|
12
|
+
export type AlertToastPosition =
|
|
13
|
+
| 'top-right'
|
|
14
|
+
| 'top-left'
|
|
15
|
+
| 'bottom-right'
|
|
16
|
+
| 'bottom-left';
|
|
17
|
+
|
|
18
|
+
export const ALERT_TOAST_TYPE_ICONS: Record<AlertToastType, LucideIconData> = {
|
|
19
|
+
success: Icons.CircleCheck,
|
|
20
|
+
error: Icons.CircleX,
|
|
21
|
+
warning: Icons.TriangleAlert,
|
|
22
|
+
info: Icons.Info,
|
|
23
|
+
question: Icons.CircleHelp,
|
|
24
|
+
loading: Icons.Loader2,
|
|
25
|
+
};
|
|
@@ -1,38 +1,51 @@
|
|
|
1
1
|
<div
|
|
2
2
|
[style]="{
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
opacity: disabled ? 0.2 : 1,
|
|
4
|
+
cursor: hasClickHandler && !disabled && !isLoading ? 'pointer' : 'default'
|
|
5
5
|
}"
|
|
6
6
|
[jTooltip]="tooltip"
|
|
7
7
|
[jTooltipPosition]="tooltipPosition"
|
|
8
|
+
[attr.role]="hasClickHandler ? 'button' : null"
|
|
9
|
+
[attr.tabindex]="hasClickHandler && !disabled && !isLoading ? 0 : null"
|
|
10
|
+
[attr.aria-label]="resolvedAriaLabel"
|
|
11
|
+
[attr.aria-busy]="isLoading ? true : null"
|
|
12
|
+
[attr.aria-disabled]="disabled ? true : null"
|
|
8
13
|
(click)="handleClick($event)"
|
|
9
|
-
|
|
14
|
+
>
|
|
10
15
|
<div
|
|
11
|
-
class="flex justify-center items-center border-2 rounded-full"
|
|
16
|
+
class="flex justify-center items-center border-2 rounded-full select-none"
|
|
12
17
|
[style]="{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
width: size + 'px',
|
|
19
|
+
height: size + 'px',
|
|
20
|
+
fontSize: resolvedTextSize + 'px',
|
|
21
|
+
padding: '0',
|
|
22
|
+
position: 'relative'
|
|
18
23
|
}"
|
|
19
24
|
[ngClass]="computedClasses"
|
|
20
25
|
[class]="classes"
|
|
21
26
|
>
|
|
22
|
-
<span
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
<span
|
|
28
|
+
class="font-bold inline-flex items-center justify-center"
|
|
29
|
+
[style]="{
|
|
30
|
+
position: 'absolute',
|
|
31
|
+
left: '50%',
|
|
32
|
+
top: '50%',
|
|
33
|
+
transform: 'translate(-50%, -50%)',
|
|
34
|
+
transformOrigin: 'center',
|
|
35
|
+
marginLeft: textOffsetX + 'px',
|
|
36
|
+
marginTop: textOffsetY + 'px'
|
|
37
|
+
}"
|
|
38
|
+
>
|
|
32
39
|
@if (isLoading) {
|
|
33
|
-
<
|
|
40
|
+
<JIcon
|
|
41
|
+
[icon]="Icons.Loader2"
|
|
42
|
+
[size]="resolvedLoaderSize"
|
|
43
|
+
iconClass="animate-spin shrink-0"
|
|
44
|
+
[inheritParentColor]="true"
|
|
45
|
+
[ariaHidden]="true"
|
|
46
|
+
/>
|
|
34
47
|
} @else {
|
|
35
|
-
{{value}}
|
|
48
|
+
{{ value }}
|
|
36
49
|
}
|
|
37
50
|
</span>
|
|
38
51
|
</div>
|
|
@@ -1,64 +1,173 @@
|
|
|
1
|
+
import { NgClass } from '@angular/common';
|
|
1
2
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
3
|
+
import { JColorsService } from 'tailjng';
|
|
4
|
+
import { Icons } from '../.config/icons/icons.lucide';
|
|
5
|
+
import { JIconComponent } from '../icon/icon.component';
|
|
2
6
|
import { JTooltipDirective } from '../tooltip/tooltip.directive';
|
|
3
|
-
import {
|
|
4
|
-
import { LucideAngularModule } from 'lucide-angular';
|
|
5
|
-
import { NgClass } from '@angular/common';
|
|
7
|
+
import { TooltipPosition } from '../tooltip/tooltip.types';
|
|
6
8
|
|
|
9
|
+
export type { TooltipPosition as JBadgeTooltipPosition } from '../tooltip/tooltip.types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Circular badge with color variants, optional tooltip, and click support.
|
|
13
|
+
*
|
|
14
|
+
* Install: `npx tailjng add badge`
|
|
15
|
+
*/
|
|
7
16
|
@Component({
|
|
8
17
|
selector: 'JBadge',
|
|
9
|
-
imports: [JTooltipDirective,
|
|
18
|
+
imports: [JTooltipDirective, JIconComponent, NgClass],
|
|
10
19
|
templateUrl: './badge.component.html',
|
|
11
|
-
styleUrl: './badge.component.css'
|
|
20
|
+
styleUrl: './badge.component.css',
|
|
12
21
|
})
|
|
13
22
|
export class JBadgeComponent {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@Input()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@Input()
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@Input()
|
|
23
|
+
// Project Icons registry — template usage: Icons.Loader2
|
|
24
|
+
readonly Icons = Icons;
|
|
25
|
+
|
|
26
|
+
// Label shown inside the badge (text or short counter)
|
|
27
|
+
@Input() value = 'J';
|
|
28
|
+
|
|
29
|
+
// Badge diameter in px
|
|
30
|
+
@Input() size = 20;
|
|
31
|
+
|
|
32
|
+
// Label font size in px; 0 = derived from `size`
|
|
33
|
+
@Input() textSize = 0;
|
|
34
|
+
|
|
35
|
+
// Tooltip position (jTooltip)
|
|
36
|
+
@Input() tooltipPosition: TooltipPosition = 'top';
|
|
37
|
+
|
|
38
|
+
// Tooltip text on hover / long-press
|
|
39
|
+
@Input() tooltip = '';
|
|
40
|
+
|
|
41
|
+
// Fine-tune label position inside the circle (px)
|
|
42
|
+
@Input() textOffsetX = 0;
|
|
43
|
+
@Input() textOffsetY = 0;
|
|
44
|
+
|
|
45
|
+
@Input() disabled = false;
|
|
46
|
+
@Input() isLoading = false;
|
|
47
|
+
|
|
48
|
+
// Color variant (primary, success_soft, …) + extra Tailwind classes
|
|
49
|
+
@Input() classes = '';
|
|
50
|
+
|
|
51
|
+
// Extra conditional classes (same role as [ngClass] on the inner circle)
|
|
52
|
+
@Input() ngClasses: Record<string, boolean> = {};
|
|
53
|
+
|
|
54
|
+
// Optional explicit accessible name when clickable
|
|
55
|
+
@Input() ariaLabel?: string;
|
|
25
56
|
|
|
26
57
|
@Output() clicked = new EventEmitter<Event>();
|
|
27
58
|
|
|
59
|
+
constructor(private readonly colorsService: JColorsService) {}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether a `(clicked)` listener is attached.
|
|
63
|
+
* @returns `true` when the badge is interactive.
|
|
64
|
+
*/
|
|
28
65
|
get hasClickHandler(): boolean {
|
|
29
66
|
return this.clicked.observers.length > 0;
|
|
30
67
|
}
|
|
31
68
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Tailwind classes for the active color variant without shadow utilities.
|
|
71
|
+
* @returns Variant string from `JColorsService`.
|
|
72
|
+
*/
|
|
37
73
|
get variantClasses(): string {
|
|
38
|
-
|
|
74
|
+
const base =
|
|
75
|
+
this.colorsService.variants[this.getActiveVariant()] ??
|
|
76
|
+
this.colorsService.variants['default'] ??
|
|
77
|
+
'text-black dark:text-white border border-dark-border dark:border-border';
|
|
78
|
+
|
|
79
|
+
return base.replace(/\bshadow-\S+/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
39
80
|
}
|
|
40
81
|
|
|
41
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Class map for the inner circle `[ngClass]`.
|
|
84
|
+
* @returns Object ready for `ngClass`.
|
|
85
|
+
*/
|
|
86
|
+
get computedClasses(): Record<string, boolean> {
|
|
42
87
|
return {
|
|
43
|
-
"flex flex-col gap-1": true,
|
|
44
88
|
...this.ngClasses,
|
|
45
89
|
[this.variantClasses]: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Final label font size in px.
|
|
95
|
+
* @returns `textSize` when greater than 0; otherwise derived from `size`.
|
|
96
|
+
*/
|
|
97
|
+
get resolvedTextSize(): number {
|
|
98
|
+
if (this.textSize > 0) {
|
|
99
|
+
return this.textSize;
|
|
46
100
|
}
|
|
101
|
+
return Math.max(10, Math.round(this.size * 0.55));
|
|
47
102
|
}
|
|
48
103
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Spinner size while loading.
|
|
106
|
+
* @returns Same as `resolvedTextSize`.
|
|
107
|
+
*/
|
|
108
|
+
get resolvedLoaderSize(): number {
|
|
109
|
+
return this.resolvedTextSize;
|
|
52
110
|
}
|
|
53
111
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Resolves `aria-label` for interactive badges.
|
|
114
|
+
* Priority: `ariaLabel` → `tooltip` → `value` (when clickable).
|
|
115
|
+
* @returns Accessible text or `null` when not applicable.
|
|
116
|
+
*/
|
|
117
|
+
get resolvedAriaLabel(): string | null {
|
|
118
|
+
if (this.ariaLabel) {
|
|
119
|
+
return this.ariaLabel;
|
|
120
|
+
}
|
|
121
|
+
if (this.tooltip) {
|
|
122
|
+
return this.tooltip;
|
|
123
|
+
}
|
|
124
|
+
if (this.hasClickHandler && this.value) {
|
|
125
|
+
return this.value;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
57
128
|
}
|
|
58
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Handles badge click.
|
|
132
|
+
* @param event Native click event.
|
|
133
|
+
* @returns Emits `clicked` when not `disabled`, not loading, and a listener exists.
|
|
134
|
+
*/
|
|
59
135
|
handleClick(event: Event) {
|
|
60
|
-
if (!this.isLoading && this.hasClickHandler) {
|
|
136
|
+
if (!this.disabled && !this.isLoading && this.hasClickHandler) {
|
|
61
137
|
this.clicked.emit(event);
|
|
62
138
|
}
|
|
63
139
|
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Combines tokens from `classes` and active keys from `ngClasses`.
|
|
143
|
+
* @returns Flat list of class names.
|
|
144
|
+
*/
|
|
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];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Whether an exact token is present in `classes` or `ngClasses`.
|
|
155
|
+
* @param className Class name to look for (e.g. `primary_soft`).
|
|
156
|
+
* @returns `true` when the token exists.
|
|
157
|
+
*/
|
|
158
|
+
private hasClass(className: string): boolean {
|
|
159
|
+
return this.getClassTokens().includes(className);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolves the color variant used by the badge.
|
|
164
|
+
* Finds the longest matching variant name in `classes`.
|
|
165
|
+
* @returns Variant key (e.g. `primary`, `success_soft`) or `default`.
|
|
166
|
+
*/
|
|
167
|
+
private getActiveVariant(): string {
|
|
168
|
+
const variants = Object.keys(this.colorsService.variants).sort(
|
|
169
|
+
(a, b) => b.length - a.length,
|
|
170
|
+
);
|
|
171
|
+
return variants.find((variant) => this.hasClass(variant)) ?? 'default';
|
|
172
|
+
}
|
|
64
173
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[jTooltip]="tooltip"
|
|
4
4
|
[jTooltipPosition]="tooltipPosition"
|
|
5
5
|
[disabled]="disabled || isLoading"
|
|
6
|
+
[attr.aria-label]="resolvedAriaLabel"
|
|
7
|
+
[attr.aria-busy]="isLoading ? true : null"
|
|
6
8
|
[ngClass]="computedClasses"
|
|
7
9
|
[class]="classes"
|
|
8
10
|
(click)="handleClick($event)"
|
|
@@ -12,22 +14,26 @@
|
|
|
12
14
|
[ngClass]="innerContentClasses"
|
|
13
15
|
>
|
|
14
16
|
@if (isLoading) {
|
|
15
|
-
<
|
|
16
|
-
[
|
|
17
|
+
<JIcon
|
|
18
|
+
[icon]="Icons.Loader2"
|
|
17
19
|
[size]="resolvedIconSize"
|
|
18
|
-
|
|
20
|
+
iconClass="animate-spin shrink-0"
|
|
21
|
+
[inheritParentColor]="true"
|
|
22
|
+
[ariaHidden]="true"
|
|
19
23
|
/>
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
@if (!isLoading &&
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
@if (!isLoading && displayIcon) {
|
|
27
|
+
<JIcon
|
|
28
|
+
[icon]="displayIcon"
|
|
29
|
+
[size]="resolvedIconSize"
|
|
30
|
+
iconClass="shrink-0"
|
|
31
|
+
[inheritParentColor]="true"
|
|
32
|
+
[ariaHidden]="true"
|
|
33
|
+
/>
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
@if (text && (!isLoading || isLoadingText)) {
|
|
36
|
+
@if (text !== '' && text !== null && text !== undefined && (!isLoading || isLoadingText)) {
|
|
31
37
|
<span>{{ text }}</span>
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -1,64 +1,144 @@
|
|
|
1
1
|
import { NgClass } from '@angular/common';
|
|
2
2
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { LucideIconData } from 'lucide-angular';
|
|
4
|
+
import { JColorsService } from 'tailjng';
|
|
5
|
+
import { Icons } from '../.config/icons/icons.lucide';
|
|
6
|
+
import { JIconComponent } from '../icon/icon.component';
|
|
5
7
|
import { JTooltipDirective } from '../tooltip/tooltip.directive';
|
|
6
8
|
|
|
7
9
|
export type JButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'icon';
|
|
10
|
+
export type JButtonIcon = LucideIconData;
|
|
8
11
|
|
|
9
12
|
@Component({
|
|
10
13
|
selector: 'JButton',
|
|
11
|
-
imports: [NgClass,
|
|
14
|
+
imports: [NgClass, JIconComponent, JTooltipDirective],
|
|
12
15
|
templateUrl: './button.component.html',
|
|
13
16
|
styleUrl: './button.component.css',
|
|
14
17
|
})
|
|
15
18
|
export class JButtonComponent {
|
|
19
|
+
// Project Icons registry — template usage: Icons.Loader2, Icons.Save, …
|
|
20
|
+
readonly Icons = Icons;
|
|
16
21
|
|
|
22
|
+
// button | submit | reset
|
|
17
23
|
@Input() type: 'button' | 'submit' | 'reset' = 'button';
|
|
24
|
+
|
|
25
|
+
// Tooltip position (jTooltip)
|
|
18
26
|
@Input() tooltipPosition: 'top' | 'right' | 'bottom' | 'left' = 'top';
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
@Input()
|
|
28
|
+
// Visible label; empty = icon-only
|
|
29
|
+
@Input() text: string | number = '';
|
|
30
|
+
|
|
31
|
+
// Tooltip text; on icon-only buttons also used as aria-label fallback
|
|
32
|
+
@Input() tooltip = '';
|
|
33
|
+
|
|
34
|
+
// Primary icon (Icons.* from .config/icons)
|
|
35
|
+
@Input() icon?: JButtonIcon;
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
@Input()
|
|
25
|
-
@Input() iconChange!: any;
|
|
26
|
-
@Input() isChangeIcon: boolean = false;
|
|
37
|
+
// Alternate icon when isChangeIcon is true (e.g. copy → check)
|
|
38
|
+
@Input() iconChange?: JButtonIcon;
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
// true = render iconChange instead of icon
|
|
41
|
+
@Input() isChangeIcon = false;
|
|
42
|
+
|
|
43
|
+
// Icon size in px; 0 = automatic based on size
|
|
44
|
+
@Input() iconSize = 0;
|
|
45
|
+
|
|
46
|
+
// xs/sm: alerts · md: forms · lg: actions · icon: square icon-only
|
|
29
47
|
@Input() size: JButtonSize = 'md';
|
|
30
48
|
|
|
31
49
|
@Output() clicked = new EventEmitter<Event>();
|
|
32
50
|
|
|
33
51
|
@Input() disabled = false;
|
|
34
52
|
@Input() isLoading = false;
|
|
53
|
+
|
|
54
|
+
// false = hide label while loading (spinner only)
|
|
35
55
|
@Input() isLoadingText = true;
|
|
36
56
|
|
|
37
|
-
|
|
38
|
-
@Input()
|
|
57
|
+
// Color variant (primary, success_soft, …) + extra Tailwind classes
|
|
58
|
+
@Input() classes = '';
|
|
59
|
+
|
|
60
|
+
// Extra conditional classes (same role as [ngClass] on the host)
|
|
61
|
+
@Input() ngClasses: Record<string, boolean> = {};
|
|
62
|
+
|
|
63
|
+
// Icon-only: explicit aria-label; falls back to tooltip when omitted
|
|
64
|
+
@Input() ariaLabel?: string;
|
|
65
|
+
|
|
66
|
+
constructor(private readonly colorsService: JColorsService) {}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Icon rendered in the template.
|
|
70
|
+
* Uses `iconChange` when `isChangeIcon` is true; otherwise `icon`.
|
|
71
|
+
* @returns Active Lucide reference or `undefined` when no icon is set.
|
|
72
|
+
*/
|
|
73
|
+
get displayIcon(): JButtonIcon | undefined {
|
|
74
|
+
if (this.isChangeIcon && this.iconChange) {
|
|
75
|
+
return this.iconChange;
|
|
76
|
+
}
|
|
77
|
+
return this.icon;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Whether the button shows no visible text.
|
|
82
|
+
* @returns `true` with `size="icon"` or when `icon` is set and `text` is empty.
|
|
83
|
+
*/
|
|
84
|
+
get isIconOnly(): boolean {
|
|
85
|
+
return this.size === 'icon' || (!!this.icon && this.text === '');
|
|
86
|
+
}
|
|
39
87
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Resolves the `<button>` element `aria-label`.
|
|
90
|
+
* Priority: `ariaLabel` → `tooltip` (icon-only) → `text` (icon-only).
|
|
91
|
+
* @returns Accessible text or `null` when not applicable.
|
|
92
|
+
*/
|
|
93
|
+
get resolvedAriaLabel(): string | null {
|
|
94
|
+
if (this.ariaLabel) {
|
|
95
|
+
return this.ariaLabel;
|
|
96
|
+
}
|
|
97
|
+
if (this.isIconOnly && this.tooltip) {
|
|
98
|
+
return this.tooltip;
|
|
99
|
+
}
|
|
100
|
+
if (this.isIconOnly && this.text !== '') {
|
|
101
|
+
return String(this.text);
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
44
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Tailwind classes for the active color variant (`JColorsService`).
|
|
108
|
+
* @returns Tailwind utility string for background, text, and border.
|
|
109
|
+
*/
|
|
45
110
|
get variantClasses(): string {
|
|
46
111
|
return this.colorsService.variants[this.getActiveVariant()]
|
|
47
112
|
?? 'text-black dark:text-white shadow-md border border-border dark:border-dark-border';
|
|
48
113
|
}
|
|
49
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Final icon size in pixels.
|
|
117
|
+
* @returns `iconSize` when greater than 0; otherwise the default for `size`.
|
|
118
|
+
*/
|
|
50
119
|
get resolvedIconSize(): number {
|
|
51
|
-
if (this.iconSize > 0)
|
|
120
|
+
if (this.iconSize > 0) {
|
|
121
|
+
return this.iconSize;
|
|
122
|
+
}
|
|
52
123
|
|
|
53
124
|
switch (this.size) {
|
|
54
|
-
case 'xs':
|
|
55
|
-
case 'sm':
|
|
56
|
-
|
|
57
|
-
case '
|
|
58
|
-
|
|
125
|
+
case 'xs':
|
|
126
|
+
case 'sm':
|
|
127
|
+
return 14;
|
|
128
|
+
case 'icon':
|
|
129
|
+
return 16;
|
|
130
|
+
case 'lg':
|
|
131
|
+
return 18;
|
|
132
|
+
default:
|
|
133
|
+
return 15;
|
|
59
134
|
}
|
|
60
135
|
}
|
|
61
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Host class map for `[ngClass]`.
|
|
139
|
+
* Skips dimensions, padding, radius, and text when already set in `classes` / `ngClasses`.
|
|
140
|
+
* @returns Class → boolean object ready for `ngClass`.
|
|
141
|
+
*/
|
|
62
142
|
get computedClasses() {
|
|
63
143
|
const result: Record<string, boolean> = {
|
|
64
144
|
'inline-flex items-center justify-center font-semibold border border-border dark:border-dark-border transition duration-300 select-none': true,
|
|
@@ -78,6 +158,10 @@ export class JButtonComponent {
|
|
|
78
158
|
return result;
|
|
79
159
|
}
|
|
80
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Gap between icon and label inside the button.
|
|
163
|
+
* @returns `gap-*` class map based on `size`.
|
|
164
|
+
*/
|
|
81
165
|
get innerContentClasses(): Record<string, boolean> {
|
|
82
166
|
return {
|
|
83
167
|
'gap-1': this.size === 'xs' || this.size === 'icon',
|
|
@@ -86,6 +170,10 @@ export class JButtonComponent {
|
|
|
86
170
|
};
|
|
87
171
|
}
|
|
88
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Button height and minimum width classes.
|
|
175
|
+
* @returns Tailwind utilities such as `h-*`, `min-h-*`, `min-w-*`, etc.
|
|
176
|
+
*/
|
|
89
177
|
get dimensionClasses(): string {
|
|
90
178
|
switch (this.size) {
|
|
91
179
|
case 'xs':
|
|
@@ -104,6 +192,10 @@ export class JButtonComponent {
|
|
|
104
192
|
}
|
|
105
193
|
}
|
|
106
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Button padding classes.
|
|
197
|
+
* @returns Tailwind `px-*`, `py-*`, or `p-0` for `size="icon"`.
|
|
198
|
+
*/
|
|
107
199
|
get paddingClasses(): string {
|
|
108
200
|
switch (this.size) {
|
|
109
201
|
case 'xs':
|
|
@@ -120,6 +212,10 @@ export class JButtonComponent {
|
|
|
120
212
|
}
|
|
121
213
|
}
|
|
122
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Label font-size classes.
|
|
217
|
+
* @returns `text-*` / `leading-*` utilities; empty string for `size="icon"`.
|
|
218
|
+
*/
|
|
123
219
|
get textSizeClasses(): string {
|
|
124
220
|
switch (this.size) {
|
|
125
221
|
case 'xs':
|
|
@@ -136,12 +232,20 @@ export class JButtonComponent {
|
|
|
136
232
|
}
|
|
137
233
|
}
|
|
138
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Border-radius classes.
|
|
237
|
+
* @returns `rounded-lg` for xs/sm/icon; `rounded-md` for md/lg.
|
|
238
|
+
*/
|
|
139
239
|
get radiusClasses(): string {
|
|
140
240
|
return this.size === 'icon' || this.size === 'xs' || this.size === 'sm'
|
|
141
241
|
? 'rounded-lg'
|
|
142
242
|
: 'rounded-md';
|
|
143
243
|
}
|
|
144
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Merges tokens from `classes` and active keys from `ngClasses`.
|
|
247
|
+
* @returns Flat list of Tailwind class names.
|
|
248
|
+
*/
|
|
145
249
|
private getClassTokens(): string[] {
|
|
146
250
|
const fromClasses = this.classes.split(/\s+/).filter(Boolean);
|
|
147
251
|
const fromNgClasses = Object.entries(this.ngClasses)
|
|
@@ -150,35 +254,71 @@ export class JButtonComponent {
|
|
|
150
254
|
return [...fromClasses, ...fromNgClasses];
|
|
151
255
|
}
|
|
152
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Checks whether any token matches a Tailwind pattern.
|
|
259
|
+
* @param pattern Regular expression over class names.
|
|
260
|
+
* @returns `true` when at least one token matches.
|
|
261
|
+
*/
|
|
153
262
|
private matchesTokenPattern(pattern: RegExp): boolean {
|
|
154
263
|
return this.getClassTokens().some((token) => pattern.test(token));
|
|
155
264
|
}
|
|
156
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Whether the consumer already defined dimensions in `classes` / `ngClasses`.
|
|
268
|
+
* @returns `true` when tokens such as `h-*`, `w-*`, `min-h-*`, `size-*` exist.
|
|
269
|
+
*/
|
|
157
270
|
hasCustomDimensions(): boolean {
|
|
158
271
|
return this.matchesTokenPattern(/^!?((min|max)-)?[wh](-|\[|$)|^!?size-/);
|
|
159
272
|
}
|
|
160
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Whether the consumer already defined padding in `classes` / `ngClasses`.
|
|
276
|
+
* @returns `true` when tokens such as `p-*`, `px-*`, `py-*` exist.
|
|
277
|
+
*/
|
|
161
278
|
hasCustomPadding(): boolean {
|
|
162
279
|
return this.matchesTokenPattern(/^!?p[xytblr]?(-|\[|$)|^!?p$/);
|
|
163
280
|
}
|
|
164
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Whether the consumer already defined text size in `classes` / `ngClasses`.
|
|
284
|
+
* @returns `true` when `text-*` tokens exist.
|
|
285
|
+
*/
|
|
165
286
|
hasCustomTextSize(): boolean {
|
|
166
287
|
return this.matchesTokenPattern(/^!?text(-|\[|$)/);
|
|
167
288
|
}
|
|
168
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Whether the consumer already defined border-radius in `classes` / `ngClasses`.
|
|
292
|
+
* @returns `true` when `rounded-*` tokens exist.
|
|
293
|
+
*/
|
|
169
294
|
hasCustomRadius(): boolean {
|
|
170
295
|
return this.matchesTokenPattern(/^!?rounded(-|\[|$)/);
|
|
171
296
|
}
|
|
172
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Whether an exact token is present in `classes` or `ngClasses`.
|
|
300
|
+
* @param className Class name to look for (e.g. `primary_soft`).
|
|
301
|
+
* @returns `true` when the token exists.
|
|
302
|
+
*/
|
|
173
303
|
private hasClass(className: string): boolean {
|
|
174
304
|
return this.getClassTokens().includes(className);
|
|
175
305
|
}
|
|
176
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Resolves the color variant used by the button.
|
|
309
|
+
* Finds the longest matching variant name in `classes`.
|
|
310
|
+
* @returns Variant key (e.g. `primary`, `success_soft`) or `default`.
|
|
311
|
+
*/
|
|
177
312
|
private getActiveVariant(): string {
|
|
178
313
|
const variants = Object.keys(this.colorsService.variants).sort((a, b) => b.length - a.length);
|
|
179
314
|
return variants.find((variant) => this.hasClass(variant)) ?? 'default';
|
|
180
315
|
}
|
|
181
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Handles the button click.
|
|
319
|
+
* @param event Native click event.
|
|
320
|
+
* @returns Emits `clicked` only when not `disabled` or `isLoading`.
|
|
321
|
+
*/
|
|
182
322
|
handleClick(event: Event) {
|
|
183
323
|
if (!this.disabled && !this.isLoading) {
|
|
184
324
|
this.clicked.emit(event);
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
@if (isLoading('initialLoad') && displayData.length === 0) {
|
|
47
47
|
<div class="w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px] border border-border dark:border-dark-border">
|
|
48
48
|
<div class="flex flex-col gap-3 items-center justify-center py-4">
|
|
49
|
-
<
|
|
49
|
+
<JIcon [icon]="Icons.Loader2" size="30" iconClass="text-primary animate-spin" [ariaHidden]="true" />
|
|
50
50
|
<p>Cargando datos...</p>
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
|
|
84
84
|
} @else if (!isLoading('pagination')) {
|
|
85
85
|
<div class="w-full flex flex-col gap-3 justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px] border border-border dark:border-dark-border hover:border-primary/50 hover:dark:border-primary">
|
|
86
|
-
<
|
|
86
|
+
<JIcon [icon]="Icons.Info" size="35" iconClass="text-primary" [ariaHidden]="true" />
|
|
87
87
|
<p class="text-black dark:text-white">No hay datos disponibles</p>
|
|
88
88
|
</div>
|
|
89
89
|
}
|