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.
Files changed (176) hide show
  1. package/README.md +12 -4
  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 +43 -8
  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 +2 -2
  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 +38 -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 +244 -2
  26. package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +25 -38
  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 +630 -12
  30. package/src/lib/components/alert/alert-toast/toast-alert.component.html +103 -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 +97 -0
  42. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.html +54 -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 +112 -0
  46. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +28 -25
  47. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.ts +67 -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 +4 -22
  50. package/src/lib/components/coach-mark/coach-mark.component.scss +1 -1
  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 +103 -1
  55. package/src/lib/components/dialog/dialog.component.html +46 -66
  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 +16 -19
  59. package/src/lib/components/filter/filter-complete/complete-filter.component.scss +35 -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 +276 -0
  69. package/src/lib/components/form/form-sidebar/sidebar-form.component.html +117 -125
  70. package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +109 -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.html +19 -16
  79. package/src/lib/components/input/input/input.component.ts +130 -53
  80. package/src/lib/components/input/input/input.types.ts +8 -0
  81. package/src/lib/components/input/input-file/file-input.component.html +65 -56
  82. package/src/lib/components/input/input-file/file-input.component.ts +276 -173
  83. package/src/lib/components/input/input-file/file-input.types.ts +2 -0
  84. package/src/lib/components/input/input-range/range-input.component.css +67 -0
  85. package/src/lib/components/input/input-range/range-input.component.html +50 -58
  86. package/src/lib/components/input/input-range/range-input.component.ts +148 -60
  87. package/src/lib/components/input/input-range/range-input.types.ts +7 -0
  88. package/src/lib/components/input/input-textarea/textarea-input.component.html +16 -7
  89. package/src/lib/components/input/input-textarea/textarea-input.component.ts +140 -50
  90. package/src/lib/components/input/input-textarea/textarea-input.types.ts +2 -0
  91. package/src/lib/components/label/label.component.html +17 -16
  92. package/src/lib/components/label/label.component.ts +70 -16
  93. package/src/lib/components/label/label.types.ts +2 -0
  94. package/src/lib/components/menu/menu-options-table/menu-options-defaults.ts +34 -0
  95. package/src/lib/components/menu/menu-options-table/options-table-menu.component.html +34 -20
  96. package/src/lib/components/menu/menu-options-table/options-table-menu.component.ts +211 -58
  97. package/src/lib/components/menu/menu-options-table/options-table-menu.types.ts +38 -0
  98. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +49 -52
  99. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +112 -24
  100. package/src/lib/components/menu/options-coach-menu/options-coach-menu.types.ts +9 -0
  101. package/src/lib/components/mode-toggle/mode-toggle.component.html +11 -16
  102. package/src/lib/components/mode-toggle/mode-toggle.component.ts +69 -33
  103. package/src/lib/components/paginator/paginator-complete/complete-paginator.component.html +4 -4
  104. package/src/lib/components/paginator/paginator-complete/complete-paginator.component.ts +31 -7
  105. package/src/lib/components/paginator/paginator-complete/complete-paginator.types.ts +12 -0
  106. package/src/lib/components/paginator/paginator-complete/complete-paginator.util.ts +36 -0
  107. package/src/lib/components/progress-bar/progress-bar.component.css +11 -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.css +6 -0
  112. package/src/lib/components/select/select-dropdown/dropdown-select.component.html +54 -44
  113. package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +450 -509
  114. package/src/lib/components/select/select-dropdown/dropdown-select.types.ts +43 -0
  115. package/src/lib/components/select/select-dropdown/dropdown-select.util.ts +179 -0
  116. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +6 -0
  117. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +131 -42
  118. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +491 -475
  119. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.types.ts +22 -0
  120. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.util.ts +20 -0
  121. package/src/lib/components/select/select-multi-table/multi-table-select.component.css +10 -0
  122. package/src/lib/components/select/select-multi-table/multi-table-select.component.html +76 -60
  123. package/src/lib/components/select/select-multi-table/multi-table-select.component.ts +250 -313
  124. package/src/lib/components/select/select-multi-table/multi-table-select.types.ts +10 -0
  125. package/src/lib/components/select/select-multi-table/multi-table-select.util.ts +5 -0
  126. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.css +212 -0
  127. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.html +62 -53
  128. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.ts +84 -27
  129. package/src/lib/components/sidebar/sidebar-static/static-sidebar.types.ts +2 -0
  130. package/src/lib/components/table/table-complete/complete-table.component.html +15 -17
  131. package/src/lib/components/table/table-complete/complete-table.component.ts +190 -338
  132. package/src/lib/components/table/table-complete/complete-table.types.ts +28 -0
  133. package/src/lib/components/table/table-complete/complete-table.util.ts +236 -0
  134. package/src/lib/components/table/table-complete/index.ts +2 -0
  135. package/src/lib/components/table/table-crud-complete/complete-crud-table.animations.ts +34 -0
  136. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +73 -128
  137. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +542 -829
  138. package/src/lib/components/table/table-crud-complete/complete-crud-table.types.ts +57 -0
  139. package/src/lib/components/table/table-crud-complete/complete-crud-table.util.ts +723 -0
  140. package/src/lib/components/table/table-crud-complete/index.ts +3 -0
  141. package/src/lib/components/theme-generator/theme-generator.component.css +21 -0
  142. package/src/lib/components/theme-generator/theme-generator.component.html +146 -116
  143. package/src/lib/components/theme-generator/theme-generator.component.ts +44 -24
  144. package/src/lib/components/toggle-radio/shared/toggle-options.types.ts +8 -0
  145. package/src/lib/components/toggle-radio/shared/toggle-options.util.ts +44 -0
  146. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.css +135 -0
  147. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.html +52 -0
  148. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.ts +198 -0
  149. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.types.ts +1 -0
  150. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.css +108 -0
  151. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.html +37 -0
  152. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.ts +193 -0
  153. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.types.ts +1 -0
  154. package/src/lib/components/tooltip/tooltip.directive.ts +12 -9
  155. package/src/lib/components/tooltip/tooltip.service.ts +331 -133
  156. package/src/lib/components/tooltip/tooltip.types.ts +9 -0
  157. package/src/lib/components/viewer/viewer-image/image-viewer.component.css +90 -4
  158. package/src/lib/components/viewer/viewer-image/image-viewer.component.html +52 -103
  159. package/src/lib/components/viewer/viewer-image/image-viewer.component.ts +182 -177
  160. package/src/lib/components/viewer/viewer-image/image-viewer.types.ts +3 -0
  161. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.css +177 -0
  162. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.html +74 -24
  163. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.ts +168 -15
  164. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.types.ts +1 -0
  165. package/src/styles.css +2 -2
  166. package/lib/services/static/icons.service.d.ts +0 -65
  167. package/src/lib/components/colors-config/README.md +0 -38
  168. package/src/lib/components/form/form-sidebar/sidebar-form.component.scss +0 -0
  169. package/src/lib/components/form/form-validation/validation-form.component.scss +0 -0
  170. package/src/lib/components/menu/menu-options-table/options-table-menu.component.scss +0 -0
  171. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +0 -12
  172. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.scss +0 -0
  173. package/src/lib/components/toggle-radio/toggle-radio.component.html +0 -51
  174. package/src/lib/components/toggle-radio/toggle-radio.component.ts +0 -222
  175. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.scss +0 -0
  176. package/tailjng-0.1.6.tgz +0 -0
@@ -0,0 +1,198 @@
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 } 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<unknown>();
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: Record<string, string> = {
119
+ sortOrder: this.sort,
120
+ };
121
+
122
+ Object.keys(this.defaultFilters).forEach((key) => {
123
+ params[`filter[${key}]`] = String(this.defaultFilters[key]);
124
+ });
125
+
126
+ this.genericService.findAll<unknown>({ endpoint: this.endpoint, params }).subscribe({
127
+ next: (res) => {
128
+ const data = (res.data as Record<string, unknown[]>)[this.endpoint] ?? [];
129
+ this.options = data;
130
+ this.processOptions();
131
+ this.isLoading = false;
132
+
133
+ if (this.selectFirstOnLoad && this.internalOptions.length > 0) {
134
+ this.select(this.internalOptions[0].value);
135
+ }
136
+ },
137
+ error: () => {
138
+ this.isLoading = false;
139
+ },
140
+ });
141
+ }
142
+
143
+ processOptions(): void {
144
+ this.internalOptions = normalizeToggleOptions(this.options, this.optionLabel, this.optionValue);
145
+ }
146
+
147
+ select(value: unknown): void {
148
+ if (this.isInteractionDisabled) {
149
+ return;
150
+ }
151
+
152
+ this.selectedValue = value;
153
+ this.onChange(this.selectedValue);
154
+ this.onTouched();
155
+ this.selectionChange.emit(this.selectedValue);
156
+ }
157
+
158
+ clear(): void {
159
+ this.selectedValue = null;
160
+ this.onChange(null);
161
+ this.onTouched();
162
+ this.selectionChange.emit(null);
163
+ }
164
+
165
+ writeValue(value: unknown): void {
166
+ this.selectedValue = value;
167
+ }
168
+
169
+ registerOnChange(fn: (value: unknown) => void): void {
170
+ this.onChange = fn;
171
+ }
172
+
173
+ registerOnTouched(fn: () => void): void {
174
+ this.onTouched = fn;
175
+ }
176
+
177
+ reloadOptions(): void {
178
+ if (!this.genericService) {
179
+ return;
180
+ }
181
+
182
+ this.loadOptionsFromApi();
183
+ }
184
+
185
+ /**
186
+ * Whether an option is currently selected.
187
+ */
188
+ isSelected(value: unknown): boolean {
189
+ return this.selectedValue === value;
190
+ }
191
+
192
+ /**
193
+ * Stable track id for radio options.
194
+ */
195
+ optionId(value: unknown, index: number): string {
196
+ return `${this.name}-opt-${index}-${String(value)}`;
197
+ }
198
+ }
@@ -0,0 +1 @@
1
+ export type { ToggleOption, ToggleRadioLayout, ToggleSortOrder } from '../shared/toggle-options.types';
@@ -0,0 +1,108 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ }
5
+
6
+ .j-segment-track {
7
+ display: flex;
8
+ min-height: 2.5rem;
9
+ overflow: hidden;
10
+ border: 1px solid var(--color-border);
11
+ border-radius: 0.5rem;
12
+ font-size: 0.875rem;
13
+ user-select: none;
14
+ }
15
+
16
+ :host-context(.dark) .j-segment-track,
17
+ :host-context(html.dark) .j-segment-track {
18
+ border-color: var(--color-dark-border);
19
+ }
20
+
21
+ .j-segment-track--disabled {
22
+ opacity: 0.5;
23
+ }
24
+
25
+ .j-segment-item {
26
+ flex: 1 1 0;
27
+ padding: 0.5rem 1rem;
28
+ border: 0;
29
+ border-right: 1px solid var(--color-border);
30
+ background-color: var(--color-background);
31
+ color: var(--color-foreground);
32
+ font-weight: 500;
33
+ cursor: pointer;
34
+ transition:
35
+ background-color 0.2s ease,
36
+ color 0.2s ease;
37
+ }
38
+
39
+ .j-segment-item:last-child {
40
+ border-right: 0;
41
+ }
42
+
43
+ .j-segment-item:hover:not(.j-segment-item--selected):not(.j-segment-item--disabled) {
44
+ background-color: color-mix(in srgb, var(--color-muted) 45%, var(--color-background));
45
+ }
46
+
47
+ .j-segment-item--selected {
48
+ background-color: var(--color-primary);
49
+ color: var(--color-primary-foreground);
50
+ }
51
+
52
+ :host-context(.dark) .j-segment-item,
53
+ :host-context(html.dark) .j-segment-item {
54
+ border-right-color: var(--color-dark-border);
55
+ background-color: var(--color-dark-background);
56
+ color: var(--color-dark-foreground);
57
+ }
58
+
59
+ :host-context(.dark) .j-segment-item--selected,
60
+ :host-context(html.dark) .j-segment-item--selected {
61
+ background-color: var(--color-dark-primary);
62
+ color: var(--color-dark-primary-foreground);
63
+ }
64
+
65
+ .j-segment-item--disabled {
66
+ cursor: not-allowed;
67
+ pointer-events: none;
68
+ }
69
+
70
+ .j-segment-item:focus-visible {
71
+ outline: 2px solid var(--color-ring);
72
+ outline-offset: -2px;
73
+ z-index: 1;
74
+ }
75
+
76
+ .j-segment-state {
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ gap: 0.5rem;
81
+ min-height: 2.5rem;
82
+ border: 1px solid var(--color-border);
83
+ border-radius: 0.5rem;
84
+ color: var(--color-foreground);
85
+ font-size: 0.875rem;
86
+ }
87
+
88
+ :host-context(.dark) .j-segment-state,
89
+ :host-context(html.dark) .j-segment-state {
90
+ border-color: var(--color-dark-border);
91
+ color: var(--color-dark-foreground);
92
+ }
93
+
94
+ .j-segment-clear {
95
+ margin-top: 0.5rem;
96
+ padding: 0;
97
+ border: 0;
98
+ background: transparent;
99
+ color: var(--color-destructive);
100
+ font-size: 0.75rem;
101
+ text-decoration: underline;
102
+ cursor: pointer;
103
+ }
104
+
105
+ .j-segment-clear:disabled {
106
+ opacity: 0.5;
107
+ cursor: not-allowed;
108
+ }
@@ -0,0 +1,37 @@
1
+ @if (internalOptions.length > 0) {
2
+ <div [ngClass]="trackClasses">
3
+ @for (opt of internalOptions; track $index) {
4
+ <button
5
+ type="button"
6
+ [disabled]="isInteractionDisabled"
7
+ [ngClass]="segmentClasses(opt.value)"
8
+ (click)="select(opt.value)"
9
+ >
10
+ {{ opt.label }}
11
+ </button>
12
+ }
13
+ </div>
14
+ } @else if (isLoading) {
15
+ <div class="j-segment-state">
16
+ <JIcon
17
+ [icon]="Icons.Loader2"
18
+ [size]="16"
19
+ iconClass="animate-spin text-muted-foreground"
20
+ [ariaHidden]="true"
21
+ />
22
+ <span>Cargando...</span>
23
+ </div>
24
+ } @else {
25
+ <div class="j-segment-state">No hay opciones</div>
26
+ }
27
+
28
+ @if (showClear && selectedValue !== null) {
29
+ <button
30
+ type="button"
31
+ class="j-segment-clear"
32
+ (click)="clear()"
33
+ [disabled]="isInteractionDisabled"
34
+ >
35
+ Limpiar selección
36
+ </button>
37
+ }
@@ -0,0 +1,193 @@
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 } 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<unknown>();
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: Record<string, string> = {
115
+ sortOrder: this.sort,
116
+ };
117
+
118
+ Object.keys(this.defaultFilters).forEach((key) => {
119
+ params[`filter[${key}]`] = String(this.defaultFilters[key]);
120
+ });
121
+
122
+ this.genericService.findAll<unknown>({ endpoint: this.endpoint, params }).subscribe({
123
+ next: (res) => {
124
+ const data = (res.data as Record<string, unknown[]>)[this.endpoint] ?? [];
125
+ this.options = data;
126
+ this.processOptions();
127
+ this.isLoading = false;
128
+
129
+ if (this.selectFirstOnLoad && this.internalOptions.length > 0) {
130
+ this.select(this.internalOptions[0].value);
131
+ }
132
+ },
133
+ error: () => {
134
+ this.isLoading = false;
135
+ },
136
+ });
137
+ }
138
+
139
+ processOptions(): void {
140
+ this.internalOptions = normalizeToggleOptions(this.options, this.optionLabel, this.optionValue);
141
+ }
142
+
143
+ select(value: unknown): void {
144
+ if (this.isInteractionDisabled) {
145
+ return;
146
+ }
147
+
148
+ this.selectedValue = value;
149
+ this.onChange(this.selectedValue);
150
+ this.onTouched();
151
+ this.selectionChange.emit(this.selectedValue);
152
+ }
153
+
154
+ clear(): void {
155
+ this.selectedValue = null;
156
+ this.onChange(null);
157
+ this.onTouched();
158
+ this.selectionChange.emit(null);
159
+ }
160
+
161
+ writeValue(value: unknown): void {
162
+ this.selectedValue = value;
163
+ }
164
+
165
+ registerOnChange(fn: (value: unknown) => void): void {
166
+ this.onChange = fn;
167
+ }
168
+
169
+ registerOnTouched(fn: () => void): void {
170
+ this.onTouched = fn;
171
+ }
172
+
173
+ reloadOptions(): void {
174
+ if (!this.genericService) {
175
+ return;
176
+ }
177
+
178
+ this.loadOptionsFromApi();
179
+ }
180
+
181
+ /**
182
+ * Class map for a segment button.
183
+ */
184
+ segmentClasses(value: unknown): Record<string, boolean> {
185
+ const selected = this.selectedValue === value;
186
+ return {
187
+ 'j-segment-item': true,
188
+ 'j-segment-item--selected': selected,
189
+ 'j-segment-item--disabled': this.isInteractionDisabled,
190
+ [this.classesElement]: !!this.classesElement,
191
+ };
192
+ }
193
+ }
@@ -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