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
@@ -1,30 +1,69 @@
1
1
  import { Component, EventEmitter, Input, Output, OnDestroy, OnInit, ViewChild, ElementRef } from '@angular/core';
2
2
  import { NgClass } from '@angular/common';
3
3
  import { FormsModule } from '@angular/forms';
4
- import { LucideAngularModule } from 'lucide-angular';
5
4
  import { Subject, Subscription, debounceTime, filter, forkJoin } from 'rxjs';
6
- import { TableColumn, FilterButton, FilterSelect, JUploadFilterService, JAlertDialogService, JAlertToastService, JGenericCrudService, JIconsService, LoadingState, JExcelService, JExcelFilterService } from 'tailjng';
5
+ import { TableColumn, FilterButton, FilterSelect, JUploadFilterService, JAlertDialogService, JAlertToastService, JGenericCrudService, LoadingState, JExcelService, JExcelFilterService } from 'tailjng';
7
6
  import { JDropdownSelectComponent } from '../../select/select-dropdown/dropdown-select.component';
8
7
  import { JMultiTableSelectComponent } from '../../select/select-multi-table/multi-table-select.component';
9
8
  import { JSwitchCheckboxComponent } from '../../checkbox/checkbox-switch/switch-checkbox.component';
10
9
  import { JDialogComponent } from '../../dialog/dialog.component';
11
10
  import { JButtonComponent } from '../../button/button.component';
12
-
11
+ import type { CompleteFilterAdditionalButtonLoading } from './complete-filter.types';
12
+ import { hasActiveFilters } from './complete-filter.util';
13
+ import { Icons } from '../../.config/icons/icons.lucide';
14
+ import { JIconComponent } from '../../icon/icon.component';
15
+
16
+ export type {
17
+ CompleteFilterAdditionalButtonLoading,
18
+ CompleteFilterClearReason} from './complete-filter.types';
19
+ export { hasActiveFilters } from './complete-filter.util';
20
+
21
+ /**
22
+ * Filter bar with debounced search, items-per-page, column picker, filter dialog and action buttons.
23
+ *
24
+ * Install: `npx tailjng add filter-complete`
25
+ *
26
+ * ```html
27
+ * <JFilter
28
+ * [endpoint]="endpoint"
29
+ * [mainEndpoint]="mainEndpoint"
30
+ * [columns]="columns"
31
+ * [data]="data"
32
+ * [(searchQuery)]="searchQuery"
33
+ * (search)="onSearch()"
34
+ * [(itemsPerPage)]="itemsPerPage"
35
+ * [itemsPerPageOptions]="itemsPerPageOptions"
36
+ * [filtersButton]="filtersButton"
37
+ * [filtersSelect]="filtersSelect"
38
+ * />
39
+ * ```
40
+ */
13
41
  @Component({
14
42
  selector: 'JFilter',
15
43
  standalone: true,
16
- imports: [LucideAngularModule, JDropdownSelectComponent, JMultiTableSelectComponent, JButtonComponent, JDialogComponent, FormsModule, JSwitchCheckboxComponent, NgClass],
44
+ imports: [
45
+ JIconComponent,
46
+ JDropdownSelectComponent,
47
+ JMultiTableSelectComponent,
48
+ JButtonComponent,
49
+ JDialogComponent,
50
+ FormsModule,
51
+ JSwitchCheckboxComponent,
52
+ NgClass,
53
+ ],
17
54
  templateUrl: './complete-filter.component.html',
18
55
  styleUrls: ['./complete-filter.component.scss'],
19
56
  providers: [JDialogComponent]
20
57
  })
21
58
  export class JCompleteFilterComponent implements OnInit, OnDestroy {
59
+ readonly Icons = Icons;
60
+
22
61
 
23
62
  @Output() afterUpload = new EventEmitter<void>();
24
63
 
25
64
  @Input() isLoadingSearch: boolean = false;
26
65
  @Input() isLoadingPerPage: boolean = false;
27
- @Input() isLoadingAditionalButtons: { [key: string]: LoadingState } = {};
66
+ @Input() isLoadingAditionalButtons: CompleteFilterAdditionalButtonLoading = {};
28
67
 
29
68
  @Input() searchPlaceholder: string = 'Buscar...';
30
69
 
@@ -90,7 +129,6 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
90
129
  @Output() checkActiveInactive = new EventEmitter<boolean>();
91
130
 
92
131
  constructor(
93
- public readonly iconsService: JIconsService,
94
132
  private readonly alertToastService: JAlertToastService,
95
133
  private readonly alertDialogService: JAlertDialogService,
96
134
  private readonly uploadFilterService: JUploadFilterService,
@@ -184,8 +222,8 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
184
222
  if (button.type === 'filter') {
185
223
  return {
186
224
  ...button,
187
- icon: this.iconsService.icons.filter,
188
- iconChange: this.iconsService.icons.filterList,
225
+ icon: Icons.Filter,
226
+ iconChange: Icons.ArrowDownWideNarrow,
189
227
  isChangeIcon: () => this.dialogOpen,
190
228
  tooltip: 'Filtros',
191
229
  clicked: () => {
@@ -199,18 +237,11 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
199
237
  if (button.type === 'clear') {
200
238
  return {
201
239
  ...button,
202
- icon: this.iconsService.icons.eraser,
203
- iconChange: this.iconsService.icons.transh,
240
+ icon: Icons.Eraser,
241
+ iconChange: Icons.Trash2,
204
242
  tooltip: 'Limpiar filtros',
205
243
  clicked: () => {
206
- const hasActiveSearch = this.searchQuery?.trim().length > 0;
207
- const hasSelectedFilters = this.filtersSelect.some(f => {
208
- const actual = f.selected;
209
- const inicial = f.initSelected ?? null;
210
- return JSON.stringify(actual) !== JSON.stringify(inicial);
211
- });
212
-
213
- if (!hasActiveSearch && !hasSelectedFilters) {
244
+ if (!hasActiveFilters(this.searchQuery, this.filtersSelect)) {
214
245
  this.alertToastService.AlertToast({
215
246
  type: 'info',
216
247
  title: 'No se ha filtrado nada...',
@@ -221,8 +252,10 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
221
252
  return;
222
253
  }
223
254
 
255
+ const hadActiveSearch = this.searchQuery?.trim().length > 0;
256
+
224
257
  // Limpiar búsqueda
225
- if (hasActiveSearch) {
258
+ if (hadActiveSearch) {
226
259
  this.clearSearch();
227
260
  }
228
261
 
@@ -244,15 +277,7 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
244
277
  }
245
278
  this.clearFilters.emit('clear');
246
279
  },
247
- isChangeIcon: () => {
248
- const hasActiveSearch = this.searchQuery?.trim().length > 0;
249
- const hasSelectedFilters = this.filtersSelect.some(f => {
250
- const actual = f.selected;
251
- const inicial = f.initSelected ?? null;
252
- return JSON.stringify(actual) !== JSON.stringify(inicial);
253
- });
254
- return !(!hasActiveSearch && !hasSelectedFilters);
255
- },
280
+ isChangeIcon: () => hasActiveFilters(this.searchQuery, this.filtersSelect),
256
281
  classes: 'bg-[#164666] hover:bg-[#133d5a] text-white'
257
282
  };
258
283
  }
@@ -261,7 +286,7 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
261
286
  if (button.type === 'excel') {
262
287
  return {
263
288
  ...button,
264
- icon: this.iconsService.icons.fileSpreadsheet,
289
+ icon: Icons.FileSpreadsheet,
265
290
  tooltip: 'Exportar a Excel',
266
291
  clicked: () => {
267
292
 
@@ -276,7 +301,7 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
276
301
  // Activar el estado de carga
277
302
  this.isLoadingAditionalButtons['excel'] = 'loading';
278
303
 
279
- const params = { ...this.params, limit: "1000000000", }
304
+ const params = { ...this.params, limit: "1000000000"}
280
305
 
281
306
  this.genericService.findAll<any>({ endpoint: this.endpoint, params }).subscribe({
282
307
  next: (response) => {
@@ -336,8 +361,8 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
336
361
  if (button.type === 'upload') {
337
362
  return {
338
363
  ...button,
339
- icon: this.iconsService.icons.fileUp,
340
- iconChange: this.iconsService.icons.listRestart,
364
+ icon: Icons.FileUp,
365
+ iconChange: Icons.ListRestart,
341
366
  isChangeIcon: () => this.dialogOpenCharge,
342
367
  tooltip: 'Cargar datos',
343
368
  clicked: () => {
@@ -382,8 +407,7 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
382
407
  error: reject
383
408
  });
384
409
 
385
- }),
386
- })
410
+ })})
387
411
 
388
412
  },
389
413
  classes: 'bg-[#1565c0] hover:bg-[#0d47a1] text-white'
@@ -0,0 +1,7 @@
1
+ import type { LoadingState } from 'tailjng';
2
+
3
+ /** Event payload from `(clearFilters)` — typically `'clear'`. */
4
+ export type CompleteFilterClearReason = string;
5
+
6
+ /** Loading map keyed by {@link FilterButton.type} (e.g. `excel`, `upload`). */
7
+ export type CompleteFilterAdditionalButtonLoading = Record<string, LoadingState>;
@@ -0,0 +1,16 @@
1
+ import type { FilterSelect } from 'tailjng';
2
+
3
+ /** Whether search text or any filter select differs from its initial value. */
4
+ export function hasActiveFilters(
5
+ searchQuery: string,
6
+ filtersSelect: FilterSelect[],
7
+ ): boolean {
8
+ const hasActiveSearch = searchQuery?.trim().length > 0;
9
+ const hasSelectedFilters = filtersSelect.some((filter) => {
10
+ const actual = filter.selected;
11
+ const initial = filter.initSelected ?? null;
12
+ return JSON.stringify(actual) !== JSON.stringify(initial);
13
+ });
14
+
15
+ return hasActiveSearch || hasSelectedFilters;
16
+ }
@@ -0,0 +1,4 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ }
@@ -1,3 +1,3 @@
1
- <div [ngClass]="getClasses()">
2
- <ng-content></ng-content>
1
+ <div [ngClass]="containerClasses()">
2
+ <ng-content></ng-content>
3
3
  </div>
@@ -1,33 +1,89 @@
1
1
  import { NgClass } from '@angular/common';
2
2
  import { Component, Input } from '@angular/core';
3
+ import {
4
+ FORM_CONTAINER_COLUMN_CLASSES,
5
+ FORM_CONTAINER_GAP_CLASSES,
6
+ FORM_CONTAINER_ROWS_ALIGN_CLASSES,
7
+ FormContainerColumns,
8
+ FormContainerGap,
9
+ FormContainerRowsAlign,
10
+ } from './container-form.types';
3
11
 
12
+ export type {
13
+ FormContainerColSpan,
14
+ FormContainerColumns,
15
+ FormContainerGap,
16
+ FormContainerRowsAlign,
17
+ } from './container-form.types';
18
+
19
+ /**
20
+ * Grid/flex layout wrapper for form fields.
21
+ *
22
+ * Install: `npx tailjng add form-container`
23
+ *
24
+ * @example
25
+ * ```html
26
+ * <JFormContainer [columns]="2" [gap]="4">
27
+ * <div jFormColSpan="1">...</div>
28
+ * <div jFormColSpan="full">...</div>
29
+ * </JFormContainer>
30
+ * ```
31
+ */
4
32
  @Component({
5
33
  selector: 'JFormContainer',
34
+ standalone: true,
6
35
  imports: [NgClass],
7
36
  templateUrl: './container-form.component.html',
8
- styleUrl: './container-form.component.css'
37
+ styleUrl: './container-form.component.css',
9
38
  })
10
39
  export class JFormContainerComponent {
40
+ /** Grid columns when `rows` is false. Values outside 1–6 fall back to 1. */
41
+ @Input() columns: FormContainerColumns = 1;
42
+
43
+ /** Horizontal row layout instead of grid. */
44
+ @Input() rows = false;
45
+
46
+ /** Grid/flex gap utility (`gap-1` … `gap-8`). Default `2`. */
47
+ @Input() gap: FormContainerGap = 2;
48
+
49
+ /** Cross-axis alignment when `rows` is true. */
50
+ @Input() rowsAlign: FormContainerRowsAlign = 'center';
11
51
 
12
- @Input() columns = 1
13
- @Input() rows = false
52
+ /** Allow wrapped rows in `rows` mode. */
53
+ @Input() rowsWrap = false;
14
54
 
15
- getClasses(): string {
16
- if (this.rows) return "flex flex-row gap-3 items-center"
55
+ containerClasses(): Record<string, boolean> {
56
+ const gapClass = FORM_CONTAINER_GAP_CLASSES[this.gap] ?? FORM_CONTAINER_GAP_CLASSES[2];
17
57
 
18
- const base = "grid gap-2"
19
- const columnClassMap: { [key: number]: string } = {
20
- 1: "grid-cols-1",
21
- 2: "grid-cols-2",
22
- 3: "grid-cols-3",
23
- 4: "grid-cols-4",
24
- 5: "grid-cols-5",
25
- 6: "grid-cols-6",
58
+ if (this.rows) {
59
+ const alignClass =
60
+ FORM_CONTAINER_ROWS_ALIGN_CLASSES[this.rowsAlign] ??
61
+ FORM_CONTAINER_ROWS_ALIGN_CLASSES.center;
62
+
63
+ return {
64
+ flex: true,
65
+ 'flex-row': true,
66
+ [gapClass]: true,
67
+ [alignClass]: true,
68
+ 'flex-wrap': this.rowsWrap,
69
+ };
26
70
  }
27
71
 
28
- const mainClass = `${base} ${columnClassMap[this.columns] || columnClassMap[1]}`
72
+ const columns = this.normalizeColumns(this.columns);
73
+ const columnClass = FORM_CONTAINER_COLUMN_CLASSES[columns];
29
74
 
30
- return `${mainClass}`.trim()
75
+ return {
76
+ grid: true,
77
+ [gapClass]: true,
78
+ [columnClass]: true,
79
+ };
31
80
  }
32
81
 
33
- }
82
+ private normalizeColumns(columns: number): FormContainerColumns {
83
+ if (columns >= 1 && columns <= 6) {
84
+ return columns as FormContainerColumns;
85
+ }
86
+
87
+ return 1;
88
+ }
89
+ }
@@ -0,0 +1,42 @@
1
+ export type FormContainerColumns = 1 | 2 | 3 | 4 | 5 | 6;
2
+
3
+ export type FormContainerGap = 1 | 2 | 3 | 4 | 6 | 8;
4
+
5
+ export type FormContainerColSpan = FormContainerColumns | 'full';
6
+
7
+ export type FormContainerRowsAlign = 'start' | 'center' | 'end' | 'stretch';
8
+
9
+ export const FORM_CONTAINER_COLUMN_CLASSES: Record<FormContainerColumns, string> = {
10
+ 1: 'grid-cols-1',
11
+ 2: 'grid-cols-2',
12
+ 3: 'grid-cols-3',
13
+ 4: 'grid-cols-4',
14
+ 5: 'grid-cols-5',
15
+ 6: 'grid-cols-6',
16
+ };
17
+
18
+ export const FORM_CONTAINER_GAP_CLASSES: Record<FormContainerGap, string> = {
19
+ 1: 'gap-1',
20
+ 2: 'gap-2',
21
+ 3: 'gap-3',
22
+ 4: 'gap-4',
23
+ 6: 'gap-6',
24
+ 8: 'gap-8',
25
+ };
26
+
27
+ export const FORM_CONTAINER_COL_SPAN_CLASSES: Record<FormContainerColSpan, string> = {
28
+ 1: 'col-span-1',
29
+ 2: 'col-span-2',
30
+ 3: 'col-span-3',
31
+ 4: 'col-span-4',
32
+ 5: 'col-span-5',
33
+ 6: 'col-span-6',
34
+ full: 'col-span-full',
35
+ };
36
+
37
+ export const FORM_CONTAINER_ROWS_ALIGN_CLASSES: Record<FormContainerRowsAlign, string> = {
38
+ start: 'items-start',
39
+ center: 'items-center',
40
+ end: 'items-end',
41
+ stretch: 'items-stretch',
42
+ };
@@ -0,0 +1,25 @@
1
+ import { Directive, Input } from '@angular/core';
2
+ import {
3
+ FORM_CONTAINER_COL_SPAN_CLASSES,
4
+ FormContainerColSpan,
5
+ } from './container-form.types';
6
+
7
+ /**
8
+ * Sets Tailwind `col-span-*` on a projected field inside `JFormContainer`.
9
+ * Import this directive in the consumer component that renders the fields.
10
+ */
11
+ @Directive({
12
+ selector: '[jFormColSpan]',
13
+ standalone: true,
14
+ host: {
15
+ '[class]': 'colSpanClass',
16
+ },
17
+ })
18
+ export class JFormColSpanDirective {
19
+ @Input({ alias: 'jFormColSpan', required: true })
20
+ colSpan: FormContainerColSpan = 1;
21
+
22
+ get colSpanClass(): string {
23
+ return FORM_CONTAINER_COL_SPAN_CLASSES[this.colSpan] ?? FORM_CONTAINER_COL_SPAN_CLASSES[1];
24
+ }
25
+ }
@@ -0,0 +1,276 @@
1
+ :host {
2
+ display: contents;
3
+ }
4
+
5
+ .j-form-sidebar-overlay {
6
+ position: fixed;
7
+ inset: 0;
8
+ z-index: 998;
9
+ background-color: rgb(0 0 0 / 0.5);
10
+ backdrop-filter: blur(1px);
11
+ animation: j-form-sidebar-overlay-in 300ms ease-out;
12
+ }
13
+
14
+ @keyframes j-form-sidebar-overlay-in {
15
+ from {
16
+ opacity: 0;
17
+ }
18
+
19
+ to {
20
+ opacity: 1;
21
+ }
22
+ }
23
+
24
+ .j-form-sidebar {
25
+ position: fixed;
26
+ top: 0;
27
+ z-index: 999;
28
+ display: flex;
29
+ flex-direction: column;
30
+ max-width: 100%;
31
+ height: 100%;
32
+ overflow: hidden;
33
+ border: 1px solid var(--color-border);
34
+ background-color: var(--color-background);
35
+ }
36
+
37
+ .j-form-sidebar--right {
38
+ right: 0;
39
+ border-right: 0;
40
+ border-radius: 0.75rem 0 0 0.75rem;
41
+ box-shadow: -8px 0 32px rgb(0 0 0 / 0.12);
42
+ }
43
+
44
+ .j-form-sidebar--left {
45
+ left: 0;
46
+ border-left: 0;
47
+ border-radius: 0 0.75rem 0.75rem 0;
48
+ box-shadow: 8px 0 32px rgb(0 0 0 / 0.12);
49
+ }
50
+
51
+ :host-context(.dark) .j-form-sidebar,
52
+ :host-context(html.dark) .j-form-sidebar {
53
+ border-color: var(--color-dark-border);
54
+ background-color: var(--color-dark-background);
55
+ }
56
+
57
+ :host-context(.dark) .j-form-sidebar--right,
58
+ :host-context(html.dark) .j-form-sidebar--right {
59
+ box-shadow: -8px 0 32px rgb(0 0 0 / 0.35);
60
+ }
61
+
62
+ :host-context(.dark) .j-form-sidebar--left,
63
+ :host-context(html.dark) .j-form-sidebar--left {
64
+ box-shadow: 8px 0 32px rgb(0 0 0 / 0.35);
65
+ }
66
+
67
+ .j-form-sidebar--small {
68
+ width: 22em;
69
+ }
70
+
71
+ .j-form-sidebar--medium {
72
+ width: 30em;
73
+ }
74
+
75
+ .j-form-sidebar--large {
76
+ width: 47em;
77
+ }
78
+
79
+ .j-form-sidebar--xlarge {
80
+ width: 65em;
81
+ }
82
+
83
+ .j-form-sidebar-header {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 0.75rem;
87
+ min-height: 3.25rem;
88
+ padding: 0.625rem 0.75rem 0.625rem 1rem;
89
+ border-bottom: 1px solid var(--color-border);
90
+ background: linear-gradient(
91
+ 180deg,
92
+ color-mix(in srgb, var(--color-muted) 28%, var(--color-background)) 0%,
93
+ var(--color-background) 100%
94
+ );
95
+ flex-shrink: 0;
96
+ }
97
+
98
+ :host-context(.dark) .j-form-sidebar-header,
99
+ :host-context(html.dark) .j-form-sidebar-header {
100
+ border-bottom-color: var(--color-dark-border);
101
+ background: linear-gradient(
102
+ 180deg,
103
+ color-mix(in srgb, var(--color-dark-muted) 35%, var(--color-dark-background)) 0%,
104
+ var(--color-dark-background) 100%
105
+ );
106
+ }
107
+
108
+ .j-form-sidebar-title-wrap {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: 0.375rem;
112
+ flex: 1;
113
+ min-width: 0;
114
+ }
115
+
116
+ .j-form-sidebar-title {
117
+ flex: 1;
118
+ min-width: 0;
119
+ margin: 0;
120
+ font-size: 0.9375rem;
121
+ font-weight: 600;
122
+ line-height: 1.3;
123
+ letter-spacing: -0.015em;
124
+ color: var(--color-foreground);
125
+ white-space: nowrap;
126
+ overflow: hidden;
127
+ text-overflow: ellipsis;
128
+ }
129
+
130
+ :host-context(.dark) .j-form-sidebar-title,
131
+ :host-context(html.dark) .j-form-sidebar-title {
132
+ color: var(--color-dark-foreground);
133
+ }
134
+
135
+ .j-form-sidebar-info {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ width: 1.5rem;
140
+ height: 1.5rem;
141
+ flex: 0 0 1.5rem;
142
+ padding: 0;
143
+ border: 0;
144
+ border-radius: 0.375rem;
145
+ background: transparent;
146
+ color: var(--color-muted-foreground);
147
+ cursor: pointer;
148
+ transition:
149
+ color 0.15s ease,
150
+ background-color 0.15s ease;
151
+ }
152
+
153
+ .j-form-sidebar-info:hover {
154
+ color: var(--color-foreground);
155
+ background-color: color-mix(in srgb, var(--color-muted) 55%, transparent);
156
+ }
157
+
158
+ .j-form-sidebar-info:focus-visible {
159
+ outline: 2px solid var(--color-ring);
160
+ outline-offset: 2px;
161
+ }
162
+
163
+ :host-context(.dark) .j-form-sidebar-info,
164
+ :host-context(html.dark) .j-form-sidebar-info {
165
+ color: var(--color-dark-muted-foreground);
166
+ }
167
+
168
+ :host-context(.dark) .j-form-sidebar-info:hover,
169
+ :host-context(html.dark) .j-form-sidebar-info:hover {
170
+ color: var(--color-dark-foreground);
171
+ background-color: color-mix(in srgb, var(--color-dark-muted) 50%, transparent);
172
+ }
173
+
174
+ .j-form-sidebar-header-actions {
175
+ display: inline-flex;
176
+ align-items: center;
177
+ gap: 0.5rem;
178
+ flex-shrink: 0;
179
+ }
180
+
181
+ .j-form-sidebar-close {
182
+ display: inline-flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ width: 2rem;
186
+ height: 2rem;
187
+ flex: 0 0 2rem;
188
+ margin: 0;
189
+ padding: 0;
190
+ border: 0;
191
+ border-radius: 0.375rem;
192
+ background-color: transparent;
193
+ color: var(--color-muted-foreground);
194
+ line-height: 0;
195
+ cursor: pointer;
196
+ transition:
197
+ color 0.15s ease,
198
+ background-color 0.15s ease,
199
+ transform 0.15s ease;
200
+ }
201
+
202
+ .j-form-sidebar-close:hover {
203
+ color: var(--color-foreground);
204
+ background-color: color-mix(in srgb, var(--color-muted) 55%, transparent);
205
+ }
206
+
207
+ .j-form-sidebar-close:active {
208
+ transform: scale(0.94);
209
+ }
210
+
211
+ .j-form-sidebar-close:focus-visible {
212
+ outline: 2px solid var(--color-ring);
213
+ outline-offset: 2px;
214
+ }
215
+
216
+ :host-context(.dark) .j-form-sidebar-close,
217
+ :host-context(html.dark) .j-form-sidebar-close {
218
+ color: var(--color-dark-muted-foreground);
219
+ }
220
+
221
+ :host-context(.dark) .j-form-sidebar-close:hover,
222
+ :host-context(html.dark) .j-form-sidebar-close:hover {
223
+ color: var(--color-dark-foreground);
224
+ background-color: color-mix(in srgb, var(--color-dark-muted) 50%, transparent);
225
+ }
226
+
227
+ .j-form-sidebar-body {
228
+ flex: 1 1 auto;
229
+ min-height: 0;
230
+ padding: 1rem;
231
+ overflow-x: hidden;
232
+ overflow-y: auto;
233
+ color: var(--color-foreground);
234
+ }
235
+
236
+ :host-context(.dark) .j-form-sidebar-body,
237
+ :host-context(html.dark) .j-form-sidebar-body {
238
+ color: var(--color-dark-foreground);
239
+ }
240
+
241
+ .j-form-sidebar-footer {
242
+ display: flex;
243
+ justify-content: center;
244
+ gap: 0.75rem;
245
+ flex-shrink: 0;
246
+ padding: 1rem;
247
+ border-top: 1px solid var(--color-border);
248
+ background-color: color-mix(in srgb, var(--color-muted) 18%, var(--color-background));
249
+ }
250
+
251
+ :host-context(.dark) .j-form-sidebar-footer,
252
+ :host-context(html.dark) .j-form-sidebar-footer {
253
+ border-top-color: var(--color-dark-border);
254
+ background-color: color-mix(in srgb, var(--color-dark-muted) 22%, var(--color-dark-background));
255
+ }
256
+
257
+ .j-form-sidebar-coach {
258
+ font-size: 0.75rem;
259
+ line-height: 1.25rem;
260
+ }
261
+
262
+ @media (max-width: 767px) {
263
+ .j-form-sidebar {
264
+ width: 100% !important;
265
+ max-width: 100%;
266
+ border-radius: 0;
267
+ }
268
+
269
+ .j-form-sidebar--right {
270
+ border-left: 0;
271
+ }
272
+
273
+ .j-form-sidebar--left {
274
+ border-right: 0;
275
+ }
276
+ }