tailjng 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +27 -5
  2. package/cli/execute/init-app.js +5 -2
  3. package/cli/execute/sync-app.js +14 -2
  4. package/cli/settings/colors-config-utils.js +69 -11
  5. package/cli/settings/icons-config-utils.js +62 -0
  6. package/cli/settings/path-utils.js +32 -2
  7. package/cli/settings/project-utils.js +7 -1
  8. package/cli/templates/app.generator.js +8 -5
  9. package/fesm2022/tailjng.mjs +247 -80
  10. package/fesm2022/tailjng.mjs.map +1 -1
  11. package/lib/services/static/theme.service.d.ts +39 -1
  12. package/lib/utils/theme/theme-variables.util.d.ts +31 -0
  13. package/package.json +1 -1
  14. package/public-api.d.ts +2 -1
  15. package/registry/components.json +41 -18
  16. package/src/colors.safelist.css +2 -2
  17. package/src/lib/components/.config/README.md +11 -0
  18. package/src/lib/components/.config/colors/README.md +40 -0
  19. package/src/lib/components/{colors-config → .config/colors}/colors.config.ts +5 -5
  20. package/src/lib/components/{colors-config → .config/colors}/colors.safelist.css +2 -2
  21. package/src/lib/components/.config/icons/README.md +26 -0
  22. package/src/lib/components/.config/icons/icons.lucide.ts +134 -0
  23. package/src/lib/components/.config/input/README.md +24 -0
  24. package/src/lib/components/.config/input/input.classes.ts +119 -0
  25. package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +177 -6
  26. package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +24 -37
  27. package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +66 -56
  28. package/src/lib/components/alert/alert-dialog/dialog-alert.types.ts +19 -0
  29. package/src/lib/components/alert/alert-toast/toast-alert.component.css +494 -14
  30. package/src/lib/components/alert/alert-toast/toast-alert.component.html +106 -102
  31. package/src/lib/components/alert/alert-toast/toast-alert.component.ts +485 -128
  32. package/src/lib/components/alert/alert-toast/toast-alert.types.ts +25 -0
  33. package/src/lib/components/badge/badge.component.html +34 -21
  34. package/src/lib/components/badge/badge.component.ts +140 -31
  35. package/src/lib/components/button/button.component.html +16 -10
  36. package/src/lib/components/button/button.component.ts +162 -22
  37. package/src/lib/components/card/card-complete/complete-card.component.html +2 -2
  38. package/src/lib/components/card/card-complete/complete-card.component.ts +26 -16
  39. package/src/lib/components/card/card-crud-complete/complete-crud-card.component.html +2 -2
  40. package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +26 -16
  41. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.css +30 -0
  42. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.html +58 -46
  43. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.ts +135 -64
  44. package/src/lib/components/checkbox/checkbox-input/input-checkbox.types.ts +3 -0
  45. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.css +62 -0
  46. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +39 -25
  47. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.ts +74 -15
  48. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.types.ts +1 -0
  49. package/src/lib/components/coach-mark/coach-mark.component.html +6 -24
  50. package/src/lib/components/coach-mark/coach-mark.component.scss +1 -7
  51. package/src/lib/components/coach-mark/coach-mark.component.ts +51 -18
  52. package/src/lib/components/coach-mark/coach-mark.directive.ts +133 -78
  53. package/src/lib/components/coach-mark/coach-mark.types.ts +12 -0
  54. package/src/lib/components/dialog/dialog.component.css +1 -1
  55. package/src/lib/components/dialog/dialog.component.html +56 -65
  56. package/src/lib/components/dialog/dialog.component.ts +136 -110
  57. package/src/lib/components/dialog/dialog.types.ts +19 -0
  58. package/src/lib/components/filter/filter-complete/complete-filter.component.html +17 -20
  59. package/src/lib/components/filter/filter-complete/complete-filter.component.scss +25 -0
  60. package/src/lib/components/filter/filter-complete/complete-filter.component.ts +58 -34
  61. package/src/lib/components/filter/filter-complete/complete-filter.types.ts +7 -0
  62. package/src/lib/components/filter/filter-complete/complete-filter.util.ts +16 -0
  63. package/src/lib/components/form/form-container/container-form.component.css +4 -0
  64. package/src/lib/components/form/form-container/container-form.component.html +2 -2
  65. package/src/lib/components/form/form-container/container-form.component.ts +72 -16
  66. package/src/lib/components/form/form-container/container-form.types.ts +42 -0
  67. package/src/lib/components/form/form-container/form-col-span.directive.ts +25 -0
  68. package/src/lib/components/form/form-sidebar/sidebar-form.component.css +45 -0
  69. package/src/lib/components/form/form-sidebar/sidebar-form.component.html +128 -124
  70. package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +114 -34
  71. package/src/lib/components/form/form-sidebar/sidebar-form.types.ts +3 -0
  72. package/src/lib/components/{toggle-radio/toggle-radio.component.css → form/form-validation/validation-form.component.css} +0 -1
  73. package/src/lib/components/form/form-validation/validation-form.component.html +10 -6
  74. package/src/lib/components/form/form-validation/validation-form.component.ts +99 -12
  75. package/src/lib/components/form/form-validation/validation-form.types.ts +33 -0
  76. package/src/lib/components/icon/icon.component.html +8 -5
  77. package/src/lib/components/icon/icon.component.ts +111 -9
  78. package/src/lib/components/input/input/input.component.css +0 -14
  79. package/src/lib/components/input/input/input.component.html +19 -16
  80. package/src/lib/components/input/input/input.component.ts +130 -53
  81. package/src/lib/components/input/input/input.types.ts +8 -0
  82. package/src/lib/components/input/input-file/file-input.component.html +65 -56
  83. package/src/lib/components/input/input-file/file-input.component.ts +276 -173
  84. package/src/lib/components/input/input-file/file-input.types.ts +2 -0
  85. package/src/lib/components/input/input-range/range-input.component.css +67 -0
  86. package/src/lib/components/input/input-range/range-input.component.html +50 -58
  87. package/src/lib/components/input/input-range/range-input.component.ts +148 -60
  88. package/src/lib/components/input/input-range/range-input.types.ts +7 -0
  89. package/src/lib/components/input/input-textarea/textarea-input.component.html +16 -7
  90. package/src/lib/components/input/input-textarea/textarea-input.component.ts +140 -50
  91. package/src/lib/components/input/input-textarea/textarea-input.types.ts +2 -0
  92. package/src/lib/components/label/label.component.html +17 -16
  93. package/src/lib/components/label/label.component.ts +94 -16
  94. package/src/lib/components/label/label.types.ts +2 -0
  95. package/src/lib/components/menu/menu-options-table/menu-options-defaults.ts +34 -0
  96. package/src/lib/components/menu/menu-options-table/options-table-menu.component.html +34 -20
  97. package/src/lib/components/menu/menu-options-table/options-table-menu.component.ts +211 -58
  98. package/src/lib/components/menu/menu-options-table/options-table-menu.types.ts +38 -0
  99. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +49 -52
  100. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +112 -24
  101. package/src/lib/components/menu/options-coach-menu/options-coach-menu.types.ts +9 -0
  102. package/src/lib/components/mode-toggle/mode-toggle.component.html +11 -16
  103. package/src/lib/components/mode-toggle/mode-toggle.component.ts +69 -33
  104. package/src/lib/components/paginator/paginator-complete/complete-paginator.component.html +4 -4
  105. package/src/lib/components/paginator/paginator-complete/complete-paginator.component.ts +31 -7
  106. package/src/lib/components/paginator/paginator-complete/complete-paginator.types.ts +12 -0
  107. package/src/lib/components/paginator/paginator-complete/complete-paginator.util.ts +36 -0
  108. package/src/lib/components/progress-bar/progress-bar.component.html +41 -40
  109. package/src/lib/components/progress-bar/progress-bar.component.ts +95 -11
  110. package/src/lib/components/progress-bar/progress-bar.types.ts +2 -0
  111. package/src/lib/components/select/select-dropdown/dropdown-select.component.html +56 -46
  112. package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +450 -509
  113. package/src/lib/components/select/select-dropdown/dropdown-select.types.ts +43 -0
  114. package/src/lib/components/select/select-dropdown/dropdown-select.util.ts +179 -0
  115. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +131 -42
  116. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +491 -475
  117. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.types.ts +22 -0
  118. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.util.ts +20 -0
  119. package/src/lib/components/select/select-multi-table/multi-table-select.component.css +4 -0
  120. package/src/lib/components/select/select-multi-table/multi-table-select.component.html +76 -60
  121. package/src/lib/components/select/select-multi-table/multi-table-select.component.ts +250 -313
  122. package/src/lib/components/select/select-multi-table/multi-table-select.types.ts +10 -0
  123. package/src/lib/components/select/select-multi-table/multi-table-select.util.ts +5 -0
  124. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.css +155 -0
  125. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.html +72 -53
  126. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.ts +84 -27
  127. package/src/lib/components/sidebar/sidebar-static/static-sidebar.types.ts +2 -0
  128. package/src/lib/components/table/table-complete/complete-table.component.html +21 -23
  129. package/src/lib/components/table/table-complete/complete-table.component.ts +190 -338
  130. package/src/lib/components/table/table-complete/complete-table.types.ts +28 -0
  131. package/src/lib/components/table/table-complete/complete-table.util.ts +236 -0
  132. package/src/lib/components/table/table-complete/index.ts +2 -0
  133. package/src/lib/components/table/table-crud-complete/complete-crud-table.animations.ts +34 -0
  134. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +87 -142
  135. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.scss +0 -63
  136. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +544 -831
  137. package/src/lib/components/table/table-crud-complete/complete-crud-table.types.ts +57 -0
  138. package/src/lib/components/table/table-crud-complete/complete-crud-table.util.ts +723 -0
  139. package/src/lib/components/table/table-crud-complete/index.ts +3 -0
  140. package/src/lib/components/theme-generator/theme-generator.component.css +21 -0
  141. package/src/lib/components/theme-generator/theme-generator.component.html +141 -116
  142. package/src/lib/components/theme-generator/theme-generator.component.ts +44 -24
  143. package/src/lib/components/toggle-radio/shared/toggle-options.types.ts +8 -0
  144. package/src/lib/components/toggle-radio/shared/toggle-options.util.ts +62 -0
  145. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.css +22 -0
  146. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.html +65 -0
  147. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.ts +192 -0
  148. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.types.ts +1 -0
  149. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.css +34 -0
  150. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.html +47 -0
  151. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.ts +187 -0
  152. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.types.ts +1 -0
  153. package/src/lib/components/tooltip/tooltip.directive.ts +12 -9
  154. package/src/lib/components/tooltip/tooltip.service.ts +331 -133
  155. package/src/lib/components/tooltip/tooltip.types.ts +9 -0
  156. package/src/lib/components/viewer/viewer-image/image-viewer.component.css +14 -4
  157. package/src/lib/components/viewer/viewer-image/image-viewer.component.html +61 -95
  158. package/src/lib/components/viewer/viewer-image/image-viewer.component.ts +182 -177
  159. package/src/lib/components/viewer/viewer-image/image-viewer.types.ts +3 -0
  160. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.css +25 -0
  161. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.html +95 -24
  162. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.ts +168 -15
  163. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.types.ts +1 -0
  164. package/src/styles.css +2 -2
  165. package/lib/services/static/icons.service.d.ts +0 -65
  166. package/src/lib/components/colors-config/README.md +0 -38
  167. package/src/lib/components/form/form-sidebar/sidebar-form.component.scss +0 -0
  168. package/src/lib/components/form/form-validation/validation-form.component.scss +0 -0
  169. package/src/lib/components/menu/menu-options-table/options-table-menu.component.scss +0 -0
  170. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +0 -12
  171. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.scss +0 -0
  172. package/src/lib/components/toggle-radio/toggle-radio.component.html +0 -51
  173. package/src/lib/components/toggle-radio/toggle-radio.component.ts +0 -222
  174. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.scss +0 -0
  175. package/tailjng-0.1.6.tgz +0 -0
@@ -0,0 +1,192 @@
1
+ import { NgClass } from '@angular/common';
2
+ import {
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ OnChanges,
7
+ OnInit,
8
+ Optional,
9
+ Output,
10
+ SimpleChanges,
11
+ forwardRef,
12
+ } from '@angular/core';
13
+ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
14
+ import { JGenericCrudService } from 'tailjng';
15
+ import { Icons } from '../../.config/icons/icons.lucide';
16
+ import { JIconComponent } from '../../icon/icon.component';
17
+ import { normalizeToggleOptions, buildToggleLoadParams } from '../shared/toggle-options.util';
18
+ import { ToggleOption, ToggleRadioLayout, ToggleSortOrder } from '../shared/toggle-options.types';
19
+
20
+ export type { ToggleOption, ToggleRadioLayout, ToggleSortOrder } from './toggle-radio.types';
21
+
22
+ let radioGroupCounter = 0;
23
+
24
+ /**
25
+ * Classic radio group with circular indicators and labels.
26
+ *
27
+ * Install: `npx tailjng add toggle-radio`
28
+ */
29
+ @Component({
30
+ selector: 'JToggleRadio',
31
+ imports: [NgClass, JIconComponent],
32
+ templateUrl: './toggle-radio.component.html',
33
+ styleUrl: './toggle-radio.component.css',
34
+ providers: [
35
+ {
36
+ provide: NG_VALUE_ACCESSOR,
37
+ useExisting: forwardRef(() => JToggleRadioComponent),
38
+ multi: true,
39
+ },
40
+ ],
41
+ })
42
+ export class JToggleRadioComponent implements OnInit, OnChanges, ControlValueAccessor {
43
+ readonly Icons = Icons;
44
+
45
+ @Input() name = `j-toggle-radio-${++radioGroupCounter}`;
46
+ @Input() endpoint = '';
47
+ @Input() options: unknown[] = [];
48
+ @Input() optionLabel = 'label';
49
+ @Input() optionValue = 'value';
50
+ @Input() sort: ToggleSortOrder = 'ASC';
51
+ @Input() defaultFilters: Record<string, unknown> = {};
52
+ @Input() loadOnInit = false;
53
+ @Input() selectFirstOnLoad = false;
54
+ @Input() isDisabled = false;
55
+ @Input() showClear = false;
56
+ @Input() layout: ToggleRadioLayout = 'vertical';
57
+ @Input() classes = '';
58
+ @Input() ngClasses: Record<string, boolean> = {};
59
+
60
+ @Output() selectionChange = new EventEmitter<any>();
61
+
62
+ internalOptions: ToggleOption[] = [];
63
+ selectedValue: unknown = null;
64
+ isLoading = false;
65
+ isComponentDisabled = false;
66
+
67
+ private onChange: (value: unknown) => void = () => {};
68
+ private onTouched: () => void = () => {};
69
+
70
+ constructor(@Optional() private readonly genericService: JGenericCrudService | null) {}
71
+
72
+ /**
73
+ * Whether user interaction is blocked.
74
+ */
75
+ get isInteractionDisabled(): boolean {
76
+ return this.isDisabled || this.isComponentDisabled;
77
+ }
78
+
79
+ /**
80
+ * Class map for the radio group container.
81
+ */
82
+ get groupClasses(): Record<string, boolean> {
83
+ return {
84
+ 'j-radio-group': true,
85
+ 'j-radio-group--horizontal': this.layout === 'horizontal',
86
+ 'j-radio-group--disabled': this.isInteractionDisabled,
87
+ ...this.ngClasses,
88
+ [this.classes]: !!this.classes,
89
+ };
90
+ }
91
+
92
+ ngOnInit(): void {
93
+ if (this.endpoint && this.loadOnInit) {
94
+ this.loadOptionsFromApi();
95
+ return;
96
+ }
97
+
98
+ this.processOptions();
99
+ }
100
+
101
+ ngOnChanges(changes: SimpleChanges): void {
102
+ if (changes['options'] && !this.endpoint) {
103
+ this.processOptions();
104
+ }
105
+ }
106
+
107
+ setDisabledState(isDisabled: boolean): void {
108
+ this.isComponentDisabled = isDisabled;
109
+ }
110
+
111
+ loadOptionsFromApi(): void {
112
+ if (!this.genericService || !this.endpoint) {
113
+ return;
114
+ }
115
+
116
+ this.isLoading = true;
117
+
118
+ const params = buildToggleLoadParams(this.sort, this.defaultFilters);
119
+
120
+ this.genericService.findAll<unknown>({ endpoint: this.endpoint, params }).subscribe({
121
+ next: (res) => {
122
+ const data = (res.data as Record<string, unknown[]>)[this.endpoint] ?? [];
123
+ this.options = data;
124
+ this.processOptions();
125
+ this.isLoading = false;
126
+
127
+ if (this.selectFirstOnLoad && this.internalOptions.length > 0) {
128
+ this.select(this.internalOptions[0].value);
129
+ }
130
+ },
131
+ error: () => {
132
+ this.isLoading = false;
133
+ },
134
+ });
135
+ }
136
+
137
+ processOptions(): void {
138
+ this.internalOptions = normalizeToggleOptions(this.options, this.optionLabel, this.optionValue);
139
+ }
140
+
141
+ select(value: unknown): void {
142
+ if (this.isInteractionDisabled) {
143
+ return;
144
+ }
145
+
146
+ this.selectedValue = value;
147
+ this.onChange(this.selectedValue);
148
+ this.onTouched();
149
+ this.selectionChange.emit(this.selectedValue);
150
+ }
151
+
152
+ clear(): void {
153
+ this.selectedValue = null;
154
+ this.onChange(null);
155
+ this.onTouched();
156
+ this.selectionChange.emit(null);
157
+ }
158
+
159
+ writeValue(value: unknown): void {
160
+ this.selectedValue = value;
161
+ }
162
+
163
+ registerOnChange(fn: (value: unknown) => void): void {
164
+ this.onChange = fn;
165
+ }
166
+
167
+ registerOnTouched(fn: () => void): void {
168
+ this.onTouched = fn;
169
+ }
170
+
171
+ reloadOptions(): void {
172
+ if (!this.genericService) {
173
+ return;
174
+ }
175
+
176
+ this.loadOptionsFromApi();
177
+ }
178
+
179
+ /**
180
+ * Whether an option is currently selected.
181
+ */
182
+ isSelected(value: unknown): boolean {
183
+ return this.selectedValue === value;
184
+ }
185
+
186
+ /**
187
+ * Stable track id for radio options.
188
+ */
189
+ optionId(value: unknown, index: number): string {
190
+ return `${this.name}-opt-${index}-${String(value)}`;
191
+ }
192
+ }
@@ -0,0 +1 @@
1
+ export type { ToggleOption, ToggleRadioLayout, ToggleSortOrder } from '../shared/toggle-options.types';
@@ -0,0 +1,34 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ }
5
+
6
+ .j-segment-track--disabled {
7
+ opacity: 0.5;
8
+ }
9
+
10
+ .j-segment-item:hover:not(.j-segment-item--selected):not(.j-segment-item--disabled) {
11
+ background-color: color-mix(in srgb, var(--color-muted) 45%, var(--color-background));
12
+ }
13
+
14
+ .j-segment-item--selected {
15
+ background-color: var(--color-primary);
16
+ color: var(--color-primary-foreground);
17
+ }
18
+
19
+ :host-context(.dark) .j-segment-item--selected,
20
+ :host-context(html.dark) .j-segment-item--selected {
21
+ background-color: var(--color-dark-primary);
22
+ color: var(--color-dark-primary-foreground);
23
+ }
24
+
25
+ .j-segment-item--disabled {
26
+ cursor: not-allowed;
27
+ pointer-events: none;
28
+ }
29
+
30
+ .j-segment-item:focus-visible {
31
+ outline: 2px solid var(--color-ring);
32
+ outline-offset: -2px;
33
+ z-index: 1;
34
+ }
@@ -0,0 +1,47 @@
1
+ @if (internalOptions.length > 0) {
2
+ <div
3
+ [ngClass]="trackClasses"
4
+ class="flex min-h-10 overflow-hidden border border-border dark:border-dark-border rounded-lg text-sm select-none"
5
+ >
6
+ @for (opt of internalOptions; track $index) {
7
+ <button
8
+ type="button"
9
+ [disabled]="isInteractionDisabled"
10
+ [ngClass]="segmentClasses(opt.value)"
11
+ class="flex-1 basis-0 py-2 px-4 border-0 border-r border-border dark:border-dark-border bg-background dark:bg-dark-background text-foreground dark:text-dark-foreground font-medium cursor-pointer transition-[background-color,color] duration-200 ease-in-out last:border-r-0"
12
+ (click)="select(opt.value)"
13
+ >
14
+ {{ opt.label }}
15
+ </button>
16
+ }
17
+ </div>
18
+ } @else if (isLoading) {
19
+ <div
20
+ class="flex items-center justify-center gap-2 min-h-10 border border-border dark:border-dark-border rounded-lg text-foreground dark:text-dark-foreground text-sm"
21
+ >
22
+ <JIcon
23
+ [icon]="Icons.Loader2"
24
+ [size]="16"
25
+ iconClass="animate-spin text-muted-foreground"
26
+ [ariaHidden]="true"
27
+ />
28
+ <span>Cargando...</span>
29
+ </div>
30
+ } @else {
31
+ <div
32
+ class="flex items-center justify-center gap-2 min-h-10 border border-border dark:border-dark-border rounded-lg text-foreground dark:text-dark-foreground text-sm"
33
+ >
34
+ No hay opciones
35
+ </div>
36
+ }
37
+
38
+ @if (showClear && selectedValue !== null) {
39
+ <button
40
+ type="button"
41
+ class="mt-2 p-0 border-0 bg-transparent text-destructive text-xs underline cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
42
+ (click)="clear()"
43
+ [disabled]="isInteractionDisabled"
44
+ >
45
+ Limpiar selección
46
+ </button>
47
+ }
@@ -0,0 +1,187 @@
1
+ import { NgClass } from '@angular/common';
2
+ import {
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ OnChanges,
7
+ OnInit,
8
+ Optional,
9
+ Output,
10
+ SimpleChanges,
11
+ forwardRef,
12
+ } from '@angular/core';
13
+ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
14
+ import { JGenericCrudService } from 'tailjng';
15
+ import { Icons } from '../../.config/icons/icons.lucide';
16
+ import { JIconComponent } from '../../icon/icon.component';
17
+ import { normalizeToggleOptions, buildToggleLoadParams } from '../shared/toggle-options.util';
18
+ import { ToggleOption, ToggleSortOrder } from '../shared/toggle-options.types';
19
+
20
+ export type { ToggleOption, ToggleSortOrder } from './segment-toggle.types';
21
+
22
+ /**
23
+ * Segmented single-select control (pill/tab style).
24
+ *
25
+ * Install: `npx tailjng add toggle-segment`
26
+ */
27
+ @Component({
28
+ selector: 'JToggleSegment',
29
+ imports: [NgClass, JIconComponent],
30
+ templateUrl: './segment-toggle.component.html',
31
+ styleUrl: './segment-toggle.component.css',
32
+ providers: [
33
+ {
34
+ provide: NG_VALUE_ACCESSOR,
35
+ useExisting: forwardRef(() => JToggleSegmentComponent),
36
+ multi: true,
37
+ },
38
+ ],
39
+ })
40
+ export class JToggleSegmentComponent implements OnInit, OnChanges, ControlValueAccessor {
41
+ readonly Icons = Icons;
42
+
43
+ @Input() endpoint = '';
44
+ @Input() options: unknown[] = [];
45
+ @Input() optionLabel = 'label';
46
+ @Input() optionValue = 'value';
47
+ @Input() sort: ToggleSortOrder = 'ASC';
48
+ @Input() defaultFilters: Record<string, unknown> = {};
49
+ @Input() loadOnInit = false;
50
+ @Input() selectFirstOnLoad = false;
51
+ @Input() isDisabled = false;
52
+ @Input() showClear = false;
53
+ @Input() classes = '';
54
+ @Input() classesElement = '';
55
+ @Input() ngClasses: Record<string, boolean> = {};
56
+
57
+ @Output() selectionChange = new EventEmitter<any>();
58
+
59
+ internalOptions: ToggleOption[] = [];
60
+ selectedValue: unknown = null;
61
+ isLoading = false;
62
+ isComponentDisabled = false;
63
+
64
+ private onChange: (value: unknown) => void = () => {};
65
+ private onTouched: () => void = () => {};
66
+
67
+ constructor(@Optional() private readonly genericService: JGenericCrudService | null) {}
68
+
69
+ /**
70
+ * Whether user interaction is blocked.
71
+ */
72
+ get isInteractionDisabled(): boolean {
73
+ return this.isDisabled || this.isComponentDisabled;
74
+ }
75
+
76
+ /**
77
+ * Class map for the segmented track shell.
78
+ */
79
+ get trackClasses(): Record<string, boolean> {
80
+ return {
81
+ 'j-segment-track': true,
82
+ 'j-segment-track--disabled': this.isInteractionDisabled,
83
+ ...this.ngClasses,
84
+ [this.classes]: !!this.classes,
85
+ };
86
+ }
87
+
88
+ ngOnInit(): void {
89
+ if (this.endpoint && this.loadOnInit) {
90
+ this.loadOptionsFromApi();
91
+ return;
92
+ }
93
+
94
+ this.processOptions();
95
+ }
96
+
97
+ ngOnChanges(changes: SimpleChanges): void {
98
+ if (changes['options'] && !this.endpoint) {
99
+ this.processOptions();
100
+ }
101
+ }
102
+
103
+ setDisabledState(isDisabled: boolean): void {
104
+ this.isComponentDisabled = isDisabled;
105
+ }
106
+
107
+ loadOptionsFromApi(): void {
108
+ if (!this.genericService || !this.endpoint) {
109
+ return;
110
+ }
111
+
112
+ this.isLoading = true;
113
+
114
+ const params = buildToggleLoadParams(this.sort, this.defaultFilters);
115
+
116
+ this.genericService.findAll<unknown>({ endpoint: this.endpoint, params }).subscribe({
117
+ next: (res) => {
118
+ const data = (res.data as Record<string, unknown[]>)[this.endpoint] ?? [];
119
+ this.options = data;
120
+ this.processOptions();
121
+ this.isLoading = false;
122
+
123
+ if (this.selectFirstOnLoad && this.internalOptions.length > 0) {
124
+ this.select(this.internalOptions[0].value);
125
+ }
126
+ },
127
+ error: () => {
128
+ this.isLoading = false;
129
+ },
130
+ });
131
+ }
132
+
133
+ processOptions(): void {
134
+ this.internalOptions = normalizeToggleOptions(this.options, this.optionLabel, this.optionValue);
135
+ }
136
+
137
+ select(value: unknown): void {
138
+ if (this.isInteractionDisabled) {
139
+ return;
140
+ }
141
+
142
+ this.selectedValue = value;
143
+ this.onChange(this.selectedValue);
144
+ this.onTouched();
145
+ this.selectionChange.emit(this.selectedValue);
146
+ }
147
+
148
+ clear(): void {
149
+ this.selectedValue = null;
150
+ this.onChange(null);
151
+ this.onTouched();
152
+ this.selectionChange.emit(null);
153
+ }
154
+
155
+ writeValue(value: unknown): void {
156
+ this.selectedValue = value;
157
+ }
158
+
159
+ registerOnChange(fn: (value: unknown) => void): void {
160
+ this.onChange = fn;
161
+ }
162
+
163
+ registerOnTouched(fn: () => void): void {
164
+ this.onTouched = fn;
165
+ }
166
+
167
+ reloadOptions(): void {
168
+ if (!this.genericService) {
169
+ return;
170
+ }
171
+
172
+ this.loadOptionsFromApi();
173
+ }
174
+
175
+ /**
176
+ * Class map for a segment button.
177
+ */
178
+ segmentClasses(value: unknown): Record<string, boolean> {
179
+ const selected = this.selectedValue === value;
180
+ return {
181
+ 'j-segment-item': true,
182
+ 'j-segment-item--selected': selected,
183
+ 'j-segment-item--disabled': this.isInteractionDisabled,
184
+ [this.classesElement]: !!this.classesElement,
185
+ };
186
+ }
187
+ }
@@ -0,0 +1 @@
1
+ export type { ToggleOption, ToggleSortOrder } from '../shared/toggle-options.types';
@@ -1,5 +1,6 @@
1
1
  import { Directive, ElementRef, Input, OnDestroy, HostListener, NgZone, TemplateRef, ViewContainerRef, } from "@angular/core"
2
2
  import { JTooltipService } from "./tooltip.service"
3
+ import { TooltipPosition } from "./tooltip.types"
3
4
 
4
5
  @Directive({
5
6
  selector: '[jTooltip]'
@@ -8,8 +9,12 @@ export class JTooltipDirective implements OnDestroy {
8
9
 
9
10
  @Input("jTooltip") content!: string | TemplateRef<any>;
10
11
 
11
- @Input() jTooltipPosition: "top" | "right" | "bottom" | "left" = "top";
12
+ // Preferred position relative to the host (may flip when space is limited).
13
+ @Input() jTooltipPosition: TooltipPosition = "top";
14
+ // Show the arrow pointing at the anchor element.
12
15
  @Input() jTooltipShowArrow = true;
16
+ // Automatically flip position when the tooltip does not fit in the viewport.
17
+ @Input() jTooltipAutoFlip = true;
13
18
 
14
19
  @Input() jTooltipOffsetX = 0;
15
20
  @Input() jTooltipOffsetY = 0;
@@ -31,7 +36,7 @@ export class JTooltipDirective implements OnDestroy {
31
36
  private readonly zone: NgZone,
32
37
  private readonly viewContainerRef: ViewContainerRef,
33
38
  ) {
34
- // Detected tactile devices
39
+ // Detect touch devices
35
40
  this.isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0
36
41
  }
37
42
 
@@ -238,6 +243,7 @@ export class JTooltipDirective implements OnDestroy {
238
243
  this.jTooltipShowArrow,
239
244
  this.jTooltipOffsetX,
240
245
  this.jTooltipOffsetY,
246
+ this.jTooltipAutoFlip,
241
247
  )
242
248
  })
243
249
  }
@@ -245,8 +251,7 @@ export class JTooltipDirective implements OnDestroy {
245
251
 
246
252
 
247
253
  /**
248
- * Start mouse tracking to update tooltip position.
249
- * This method sets an interval to update the tooltip position based on mouse movements.
254
+ * Hides the tooltip and stops mouse tracking.
250
255
  */
251
256
  private hide() {
252
257
  this.isTooltipVisible = false
@@ -259,9 +264,7 @@ export class JTooltipDirective implements OnDestroy {
259
264
 
260
265
 
261
266
  /**
262
- * Stop mouse tracking by clearing the interval and removing global event listeners.
263
- * This method ensures that no multiple intervals are running and cleans up the event listeners.
264
- * @return
267
+ * Starts mouse tracking to hide the tooltip when the pointer leaves the host.
265
268
  */
266
269
  private startMouseTracking() {
267
270
  this.stopMouseTracking();
@@ -270,11 +273,11 @@ export class JTooltipDirective implements OnDestroy {
270
273
  this.mouseTrackingInterval = setInterval(() => {
271
274
  if (!this.isTooltipVisible) return
272
275
 
273
- // Obtener la posición actual del mouse
276
+ // Get the current mouse position
274
277
  const mouseEvent = this.getLastMousePosition()
275
278
  if (!mouseEvent) return
276
279
 
277
- // Verificar si el mouse sigue sobre el elemento
280
+ // Check whether the mouse is still over the element
278
281
  const elementUnderMouse = document.elementFromPoint(mouseEvent.clientX, mouseEvent.clientY)
279
282
  const isMouseOverElement =
280
283
  this.el.nativeElement.contains(elementUnderMouse) || this.el.nativeElement === elementUnderMouse