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
@@ -1,64 +1,56 @@
1
1
  <div class="w-full">
2
- @if (placeholder) {
3
- @if (isLabel) {
4
-
5
- <label [for]="id" class="flex text-[15px] font-bold text-black dark:text-white justify-between mb-1 opacity-80">
6
- <span>{{ placeholder }}</span>
7
- <span>{{ value | number:'1.0-2' }}{{simbol}}</span>
8
- </label>
9
-
10
- } @else {
11
- <label [for]="id" class="block text-[15px] font-bold text-black dark:text-white text-center mb-1 opacity-80">{{ placeholder }}</label>
12
- }
2
+ @if (showPlaceholder) {
3
+ @if (labelMode === 'inline') {
4
+ <label
5
+ [attr.for]="id"
6
+ class="mb-1 flex justify-between text-[15px] font-bold text-black opacity-80 dark:text-white"
7
+ >
8
+ <span>{{ placeholder }}</span>
9
+ <span>{{ innerValue | number: '1.0-2' }}{{ symbol }}</span>
10
+ </label>
11
+ } @else if (labelMode === 'centered') {
12
+ <label
13
+ [attr.for]="id"
14
+ class="mb-1 block text-center text-[15px] font-bold text-black opacity-80 dark:text-white"
15
+ >
16
+ {{ placeholder }}
17
+ </label>
13
18
  }
19
+ }
14
20
 
15
- <div class="relative flex gap-2 w-full items-center justify-center align-center items-center justify-center align-center">
16
- @if (min !== null) {
17
- <span class="text-[15px] font-bold text-gray-500 mt-1 text-center">{{min}}{{simbol}}</span>
18
- }
19
- <input
20
- type="range"
21
- [id]="id"
22
- [name]="name ?? ''"
23
- [min]="min"
24
- [max]="max"
25
- [step]="step"
26
- [required]="required"
27
- [disabled]="isDisabled"
28
- [(ngModel)]="value"
29
- (blur)="onTouched()"
30
- [ngClass]="combinedNgClass"
31
- [class]="classes"
32
- class="
33
- w-full h-5 bg-background dark:bg-dark-background border border-border dark:border-dark-border
34
- rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary transition duration-200
35
-
36
- [&::-webkit-slider-thumb]:appearance-none
37
- [&::-webkit-slider-thumb]:h-8
38
- [&::-webkit-slider-thumb]:w-8
39
- [&::-webkit-slider-thumb]:rounded-full
40
- [&::-webkit-slider-thumb]:cursor-pointer
41
- [&::-webkit-slider-thumb]:bg-dark-primary
42
- dark:[&::-webkit-slider-thumb]:bg-primary
43
- [&::-webkit-slider-thumb:hover]:bg-primary
44
- dark:[&::-webkit-slider-thumb:hover]:bg-dark-primary
45
-
46
- [&::-moz-range-thumb]:h-4
47
- [&::-moz-range-thumb]:w-4
48
- [&::-moz-range-thumb]:rounded-full
49
- [&::-moz-range-thumb]:bg-primary
50
- [&::-moz-range-thumb]:cursor-pointer
51
- [&::-moz-range-thumb:hover]:bg-primary
52
- dark:[&::-moz-range-thumb:hover]:bg-dark-primary
53
- "
54
- />
21
+ <div class="relative flex w-full items-center justify-center gap-2">
22
+ @if (showBounds) {
23
+ <span class="mt-1 text-center text-[15px] font-bold text-gray-500 dark:text-gray-400">
24
+ {{ min }}{{ symbol }}
25
+ </span>
26
+ }
55
27
 
56
- @if (max) {
57
- <span class="text-[15px] font-bold text-gray-500 mt-1 text-center">{{max}}{{simbol}}</span>
58
- }
59
- </div>
28
+ <input
29
+ type="range"
30
+ [id]="id"
31
+ [name]="name ?? ''"
32
+ [min]="min"
33
+ [max]="max"
34
+ [step]="step"
35
+ [required]="required"
36
+ [disabled]="isDisabled"
37
+ [value]="innerValue"
38
+ (input)="onRangeInput($event)"
39
+ (blur)="onBlur()"
40
+ [ngClass]="computedClasses"
41
+ [class]="classes"
42
+ />
60
43
 
61
- @if (!isLabel) {
62
- <div class="text-xl font-bold text-gray-500 mt-1 text-center">{{ value }}</div>
44
+ @if (showBounds) {
45
+ <span class="mt-1 text-center text-[15px] font-bold text-gray-500 dark:text-gray-400">
46
+ {{ max }}{{ symbol }}
47
+ </span>
63
48
  }
64
- </div>
49
+ </div>
50
+
51
+ @if (showValueBelow) {
52
+ <div class="mt-1 text-center text-xl font-bold text-gray-500 dark:text-gray-400">
53
+ {{ formattedValue }}
54
+ </div>
55
+ }
56
+ </div>
@@ -1,109 +1,197 @@
1
- import { Component, forwardRef, Input } from '@angular/core';
2
- import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule } from '@angular/forms';
3
- import { CommonModule } from '@angular/common';
4
- import { LucideAngularModule } from 'lucide-angular';
5
-
1
+ import { DecimalPipe, NgClass } from '@angular/common';
2
+ import { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core';
3
+ import {
4
+ ControlValueAccessor,
5
+ FormsModule,
6
+ NG_VALUE_ACCESSOR,
7
+ ReactiveFormsModule,
8
+ } from '@angular/forms';
9
+ import {
10
+ INPUT_DISABLED_OVERLAY_CLASSES,
11
+ mergeInputClasses,
12
+ } from '../../.config/input/input.classes';
13
+ import { RangeLabelMode } from './range-input.types';
14
+
15
+ export type { RangeLabelMode } from './range-input.types';
16
+
17
+ /**
18
+ * Range slider input with optional label, min/max hints, and unit suffix.
19
+ *
20
+ * Install: `npx tailjng add input-range`
21
+ */
6
22
  @Component({
7
23
  selector: 'JRangeInput',
8
- imports: [CommonModule, FormsModule, ReactiveFormsModule, LucideAngularModule],
24
+ imports: [FormsModule, ReactiveFormsModule, NgClass, DecimalPipe],
9
25
  templateUrl: './range-input.component.html',
10
- styleUrls: ['./range-input.component.css'],
26
+ styleUrl: './range-input.component.css',
11
27
  providers: [
12
28
  {
13
29
  provide: NG_VALUE_ACCESSOR,
14
30
  useExisting: forwardRef(() => JRangeInputComponent),
15
- multi: true
16
- }
17
- ]
31
+ multi: true,
32
+ },
33
+ ],
18
34
  })
19
35
  export class JRangeInputComponent implements ControlValueAccessor {
20
-
21
36
  @Input() id?: string;
22
37
  @Input() name?: string;
23
- @Input() placeholder: string = '';
24
- @Input() isDisabled: boolean = false;
25
- @Input() required: boolean = false;
26
- @Input() classes: string = '';
27
- @Input() ngClass: { [key: string]: boolean } = {};
28
38
 
29
- @Input() min: number = 0;
30
- @Input() max: number = 100;
31
- @Input() step: number = 1;
32
- @Input() isLabel: boolean = false;
33
- @Input() simbol: string = '';
39
+ // Label text above the slider
40
+ @Input() placeholder = '';
34
41
 
35
- innerValue: any = '';
42
+ @Input() isDisabled = false;
43
+ @Input() required = false;
36
44
 
37
- get value(): any {
38
- return this.innerValue;
45
+ @Input() min = 0;
46
+ @Input() max = 100;
47
+ @Input() step = 1;
48
+
49
+ // Show min/max values beside the slider
50
+ @Input() showBounds = true;
51
+
52
+ /**
53
+ * Unit suffix next to values (e.g. "%", " pts").
54
+ * Alias `simbol` kept for backward compatibility.
55
+ */
56
+ @Input({ alias: 'simbol' }) symbol = '';
57
+
58
+ /**
59
+ * Label layout mode.
60
+ * Use `isLabel` input for backward compatibility (`true` → inline).
61
+ */
62
+ @Input() labelMode: RangeLabelMode = 'none';
63
+
64
+ // Extra Tailwind classes on the range input
65
+ @Input() classes = '';
66
+
67
+ // Extra conditional classes on the range input
68
+ @Input() ngClasses: Record<string, boolean> = {};
69
+
70
+ /** Emits when the slider value changes (supports `(input)` on the host). */
71
+ @Output() input = new EventEmitter<number>();
72
+
73
+ innerValue = 0;
74
+
75
+ private onChange: (value: number) => void = () => {};
76
+ private onTouched: () => void = () => {};
77
+
78
+ /**
79
+ * Backward-compatible alias for `labelMode="inline"`.
80
+ * @param value When true, uses inline label layout.
81
+ */
82
+ @Input()
83
+ set isLabel(value: boolean) {
84
+ this.labelMode = value ? 'inline' : 'none';
39
85
  }
40
86
 
41
- set value(val: any) {
42
- if (val !== this.innerValue) {
43
- this.innerValue = val;
44
- this.onChange(val);
45
- }
87
+ /**
88
+ * Current numeric value of the slider.
89
+ * @returns The internal value.
90
+ */
91
+ get value(): number {
92
+ return this.innerValue;
46
93
  }
47
94
 
48
- get combinedNgClass() {
49
- return {
50
- ...(this.ngClass || {}),
51
- 'opacity-50': this.isDisabled
52
- };
95
+ /**
96
+ * Whether the placeholder label should render.
97
+ * @returns `true` when `placeholder` is not empty.
98
+ */
99
+ get showPlaceholder(): boolean {
100
+ return this.placeholder !== '';
53
101
  }
54
102
 
55
- // ControlValueAccessor methods
56
- onChange: any = () => { };
57
- onTouched: any = () => { };
103
+ /**
104
+ * Whether to show the value below the slider.
105
+ * @returns `true` when `labelMode` is `none`.
106
+ */
107
+ get showValueBelow(): boolean {
108
+ return this.labelMode === 'none';
109
+ }
58
110
 
59
- // Writes a value to the component
60
- writeValue(value: any): void {
61
- if (value !== undefined) {
62
- this.innerValue = value;
63
- }
111
+ /**
112
+ * Formatted value with unit suffix for display.
113
+ * @returns String such as `42%`.
114
+ */
115
+ get formattedValue(): string {
116
+ return `${this.innerValue}${this.symbol}`;
64
117
  }
65
118
 
119
+ /**
120
+ * Class map for the native range input.
121
+ * @returns Base slider styles merged with `ngClasses`.
122
+ */
123
+ get computedClasses(): Record<string, boolean> {
124
+ return mergeInputClasses(
125
+ { 'j-range-input': true },
126
+ this.ngClasses,
127
+ this.isDisabled
128
+ ? mergeInputClasses(INPUT_DISABLED_OVERLAY_CLASSES, { 'cursor-not-allowed': true })
129
+ : {},
130
+ );
131
+ }
66
132
 
133
+ /**
134
+ * Writes the value from Angular forms into the view.
135
+ * @param value Numeric value from the form model.
136
+ */
137
+ writeValue(value: unknown): void {
138
+ if (value == null || value === '') {
139
+ this.innerValue = this.min;
140
+ return;
141
+ }
142
+ this.innerValue = Number(value);
143
+ }
67
144
 
68
145
  /**
69
- * Registers a function to call when the value changes.
70
- * @param fn The function to call when the value changes.
146
+ * Registers the change callback for Angular forms.
147
+ * @param fn Callback invoked when the value changes.
71
148
  */
72
- registerOnChange(fn: any): void {
149
+ registerOnChange(fn: (value: number) => void): void {
73
150
  this.onChange = fn;
74
151
  }
75
152
 
76
-
77
-
78
153
  /**
79
- * Registers a function to call when the control is touched.
80
- * @param fn The function to call when the control is touched.
154
+ * Registers the touched callback for Angular forms.
155
+ * @param fn Callback invoked when the control is touched.
81
156
  */
82
- registerOnTouched(fn: any): void {
157
+ registerOnTouched(fn: () => void): void {
83
158
  this.onTouched = fn;
84
159
  }
85
160
 
86
-
161
+ /**
162
+ * Syncs disabled state from Angular forms.
163
+ * @param isDisabled Whether the control is disabled.
164
+ */
165
+ setDisabledState(isDisabled: boolean): void {
166
+ this.isDisabled = isDisabled;
167
+ }
87
168
 
88
169
  /**
89
- * Handles input changes.
90
- * @param event The input event.
170
+ * Handles native range input events.
171
+ * @param event Input event from the slider.
91
172
  */
92
- onInput(event: Event): void {
173
+ onRangeInput(event: Event): void {
93
174
  const target = event.target as HTMLInputElement;
94
- this.value = target.value;
95
- this.onChange(this.value);
175
+ this.innerValue = target.valueAsNumber;
176
+ this.onChange(this.innerValue);
96
177
  this.onTouched();
178
+ this.input.emit(this.innerValue);
97
179
  }
98
180
 
181
+ /**
182
+ * Marks the control as touched on blur.
183
+ */
184
+ onBlur(): void {
185
+ this.onTouched();
186
+ }
99
187
 
100
-
101
188
  /**
102
- * Clears the input
189
+ * Resets the slider to `min`.
103
190
  */
104
191
  clearInput(): void {
105
- this.value = '';
106
- this.onChange('');
192
+ this.innerValue = this.min;
193
+ this.onChange(this.innerValue);
107
194
  this.onTouched();
195
+ this.input.emit(this.innerValue);
108
196
  }
109
197
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Label layout for {@link JRangeInputComponent}.
3
+ * - `inline` — placeholder and current value on the same row
4
+ * - `centered` — placeholder centered above the slider
5
+ * - `none` — value shown below the slider (default)
6
+ */
7
+ export type RangeLabelMode = 'inline' | 'centered' | 'none';
@@ -1,4 +1,4 @@
1
- <div class="relative w-full flex">
1
+ <div class="relative w-full">
2
2
  <textarea
3
3
  [id]="id"
4
4
  [name]="name ?? ''"
@@ -7,19 +7,28 @@
7
7
  (input)="onInput($event)"
8
8
  [required]="required"
9
9
  [disabled]="isDisabled"
10
- (blur)="onTouched()"
11
- [ngClass]="combinedNgClass"
10
+ [readonly]="isReadonly"
11
+ [rows]="rows"
12
+ (blur)="onBlur()"
13
+ [ngClass]="computedClasses"
12
14
  [class]="classes"
13
- class="input w-full h-[70px] max-[400px]:h-[35px] min-h-[70px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
14
15
  ></textarea>
15
16
 
16
- @if (value && clearButton) {
17
+ @if (showClearButton) {
17
18
  <button
18
19
  type="button"
19
- class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
20
+ class="absolute right-2 top-2 flex cursor-pointer items-center justify-center p-0 focus:outline-none"
21
+ [ngClass]="clearButtonClasses"
20
22
  (click)="clearInput()"
23
+ [disabled]="isDisabled"
24
+ aria-label="Clear"
21
25
  >
22
- <lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
26
+ <JIcon
27
+ [icon]="Icons.X"
28
+ [size]="16"
29
+ [inheritParentColor]="true"
30
+ [ariaHidden]="true"
31
+ />
23
32
  </button>
24
33
  }
25
34
  </div>
@@ -1,100 +1,190 @@
1
+ import { NgClass } from '@angular/common';
1
2
  import { Component, Input, forwardRef } from '@angular/core';
2
- import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule } from '@angular/forms';
3
- import { NgClass, CommonModule } from '@angular/common';
4
- import { LucideAngularModule } from 'lucide-angular';
5
- import { JIconsService } from 'tailjng';
6
-
3
+ import {
4
+ ControlValueAccessor,
5
+ FormsModule,
6
+ NG_VALUE_ACCESSOR,
7
+ ReactiveFormsModule,
8
+ } from '@angular/forms';
9
+ import { Icons } from '../../.config/icons/icons.lucide';
10
+ import {
11
+ INPUT_DISABLED_OVERLAY_CLASSES,
12
+ INPUT_FOCUS_CLASSES,
13
+ INPUT_PADDING_X_TEXTAREA,
14
+ INPUT_SURFACE_CLASSES,
15
+ INPUT_TEXTAREA_HEIGHT_CLASSES,
16
+ inputActionButtonClasses,
17
+ mergeInputClasses,
18
+ } from '../../.config/input/input.classes';
19
+ import { JIconComponent } from '../../icon/icon.component';
20
+ import { TextareaResize } from './textarea-input.types';
21
+
22
+ export type { TextareaResize } from './textarea-input.types';
23
+
24
+ /**
25
+ * Multiline text input with ControlValueAccessor, clear button, and theme-aware styling.
26
+ *
27
+ * Install: `npx tailjng add input-textarea`
28
+ */
7
29
  @Component({
8
30
  selector: 'JTextareaInput',
9
- imports: [FormsModule, ReactiveFormsModule, NgClass, LucideAngularModule, CommonModule],
31
+ imports: [FormsModule, ReactiveFormsModule, NgClass, JIconComponent],
10
32
  templateUrl: './textarea-input.component.html',
11
- styleUrls: ['./textarea-input.component.css'],
33
+ styleUrl: './textarea-input.component.css',
12
34
  providers: [
13
35
  {
14
36
  provide: NG_VALUE_ACCESSOR,
15
37
  useExisting: forwardRef(() => JTextareaInputComponent),
16
- multi: true
17
- }
18
- ]
38
+ multi: true,
39
+ },
40
+ ],
19
41
  })
20
42
  export class JTextareaInputComponent implements ControlValueAccessor {
43
+ // Project Icons registry — template usage: Icons.X
44
+ readonly Icons = Icons;
21
45
 
22
46
  @Input() id?: string;
23
47
  @Input() name?: string;
24
- @Input() placeholder: string = '';
48
+ @Input() placeholder = '';
49
+
50
+ @Input() isDisabled = false;
51
+ @Input() isReadonly = false;
52
+ @Input() required = false;
53
+
54
+ // Show clear (X) button when the field has a value
55
+ @Input() clearButton = false;
56
+
57
+ // Native textarea rows attribute
58
+ @Input() rows?: number;
59
+
60
+ // CSS resize behavior
61
+ @Input() resize: TextareaResize = 'vertical';
25
62
 
26
- @Input() isDisabled: boolean = false;
27
- @Input() required: boolean = false;
28
- @Input() classes: string = '';
29
- @Input() ngClass: { [key: string]: boolean } = {};
30
- @Input() clearButton: boolean = false;
63
+ // Extra Tailwind classes on the `<textarea>`
64
+ @Input() classes = '';
31
65
 
32
- innerValue: any = '';
66
+ // Extra conditional classes (same role as [ngClass] on the textarea)
67
+ @Input() ngClasses: Record<string, boolean> = {};
33
68
 
34
- get value(): any {
69
+ innerValue = '';
70
+
71
+ private onChange: (value: string) => void = () => {};
72
+ private onTouched: () => void = () => {};
73
+
74
+ /**
75
+ * Current value bound to the native textarea.
76
+ * @returns The internal string value.
77
+ */
78
+ get value(): string {
35
79
  return this.innerValue;
36
80
  }
37
81
 
38
- set value(val: any) {
39
- if (val !== this.innerValue) {
40
- this.innerValue = val;
41
- this.onChange(val);
42
- }
82
+ /**
83
+ * Whether the clear button should be visible.
84
+ * @returns `true` when `clearButton` is enabled and the field is not empty.
85
+ */
86
+ get showClearButton(): boolean {
87
+ return this.clearButton && this.hasValue;
43
88
  }
44
89
 
45
- get combinedNgClass() {
46
- return {
47
- ...(this.ngClass || {}),
48
- 'opacity-50': this.isDisabled
49
- };
90
+ /**
91
+ * Whether the field has a non-empty value.
92
+ * @returns `true` when `innerValue` is not an empty string.
93
+ */
94
+ get hasValue(): boolean {
95
+ return this.innerValue !== '';
50
96
  }
51
97
 
52
- // ControlValueAccessor methods
53
- onChange: any = () => { };
54
- onTouched: any = () => { };
98
+ /**
99
+ * Class map for the native `<textarea>` element.
100
+ * @returns Base textarea styles merged with resize, `ngClasses`, and disabled opacity.
101
+ */
102
+ get computedClasses(): Record<string, boolean> {
103
+ return mergeInputClasses(
104
+ INPUT_SURFACE_CLASSES,
105
+ INPUT_TEXTAREA_HEIGHT_CLASSES,
106
+ INPUT_FOCUS_CLASSES,
107
+ INPUT_PADDING_X_TEXTAREA,
108
+ this.resizeClasses[this.resize],
109
+ this.ngClasses,
110
+ { 'pr-3': !this.clearButton, 'pr-7': this.clearButton },
111
+ { 'scroll-element': true },
112
+ this.isDisabled ? INPUT_DISABLED_OVERLAY_CLASSES : {},
113
+ );
114
+ }
55
115
 
56
- constructor(public readonly iconsService: JIconsService) { }
116
+ /**
117
+ * Class map for the clear button.
118
+ * @returns Hover and disabled utilities for the clear control.
119
+ */
120
+ get clearButtonClasses(): Record<string, boolean> {
121
+ return inputActionButtonClasses(this.isDisabled);
122
+ }
123
+
124
+ /** Tailwind resize utilities keyed by {@link TextareaResize}. */
125
+ private readonly resizeClasses: Record<TextareaResize, Record<string, boolean>> = {
126
+ none: { 'resize-none': true },
127
+ vertical: { 'resize-y': true },
128
+ both: { resize: true },
129
+ horizontal: { 'resize-x': true },
130
+ };
57
131
 
58
- // writeValue - To write a value to the component
59
- writeValue(value: any): void {
60
- if (value !== undefined) {
61
- this.value = value;
62
- }
132
+ /**
133
+ * Writes the value from the form model into the view.
134
+ * @param value Value from Angular forms.
135
+ */
136
+ writeValue(value: unknown): void {
137
+ this.innerValue = value != null ? String(value) : '';
63
138
  }
64
139
 
65
- // registerOnChange - To register the change callback function
66
- registerOnChange(fn: (value: any) => void): void {
140
+ /**
141
+ * Registers the change callback for Angular forms.
142
+ * @param fn Callback invoked when the value changes.
143
+ */
144
+ registerOnChange(fn: (value: string) => void): void {
67
145
  this.onChange = fn;
68
146
  }
69
147
 
70
- // registerOnTouched - To register the touched callback function
148
+ /**
149
+ * Registers the touched callback for Angular forms.
150
+ * @param fn Callback invoked when the control is touched.
151
+ */
71
152
  registerOnTouched(fn: () => void): void {
72
153
  this.onTouched = fn;
73
154
  }
74
155
 
75
-
156
+ /**
157
+ * Syncs disabled state from Angular forms.
158
+ * @param isDisabled Whether the control is disabled.
159
+ */
160
+ setDisabledState(isDisabled: boolean): void {
161
+ this.isDisabled = isDisabled;
162
+ }
76
163
 
77
164
  /**
78
- * Writes a value to the component.
79
- * @param event
165
+ * Handles native input events and propagates the value to forms.
166
+ * @param event Native input event.
80
167
  */
81
168
  onInput(event: Event): void {
82
169
  const target = event.target as HTMLTextAreaElement;
83
- this.value = target.value;
84
- this.onChange(this.value);
170
+ this.innerValue = target.value;
171
+ this.onChange(this.innerValue);
85
172
  this.onTouched();
86
173
  }
87
174
 
88
-
175
+ /**
176
+ * Marks the control as touched on blur.
177
+ */
178
+ onBlur(): void {
179
+ this.onTouched();
180
+ }
89
181
 
90
182
  /**
91
- * Clears the input value and resets the component state.
92
- * This method is typically used when a clear button is clicked.
183
+ * Clears the field value and notifies Angular forms.
93
184
  */
94
185
  clearInput(): void {
95
- this.value = '';
186
+ this.innerValue = '';
96
187
  this.onChange('');
97
188
  this.onTouched();
98
189
  }
99
-
100
190
  }
@@ -0,0 +1,2 @@
1
+ /** CSS `resize` behavior for {@link JTextareaInputComponent}. */
2
+ export type TextareaResize = 'none' | 'vertical' | 'both' | 'horizontal';