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.
- package/README.md +27 -5
- package/cli/execute/init-app.js +5 -2
- package/cli/execute/sync-app.js +14 -2
- package/cli/settings/colors-config-utils.js +69 -11
- package/cli/settings/icons-config-utils.js +62 -0
- package/cli/settings/path-utils.js +32 -2
- package/cli/settings/project-utils.js +7 -1
- package/cli/templates/app.generator.js +8 -5
- package/fesm2022/tailjng.mjs +247 -80
- package/fesm2022/tailjng.mjs.map +1 -1
- package/lib/services/static/theme.service.d.ts +39 -1
- package/lib/utils/theme/theme-variables.util.d.ts +31 -0
- package/package.json +1 -1
- package/public-api.d.ts +2 -1
- package/registry/components.json +41 -18
- package/src/colors.safelist.css +2 -2
- package/src/lib/components/.config/README.md +11 -0
- package/src/lib/components/.config/colors/README.md +40 -0
- package/src/lib/components/{colors-config → .config/colors}/colors.config.ts +5 -5
- package/src/lib/components/{colors-config → .config/colors}/colors.safelist.css +2 -2
- package/src/lib/components/.config/icons/README.md +26 -0
- package/src/lib/components/.config/icons/icons.lucide.ts +134 -0
- package/src/lib/components/.config/input/README.md +24 -0
- package/src/lib/components/.config/input/input.classes.ts +119 -0
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +177 -6
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +24 -37
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +66 -56
- package/src/lib/components/alert/alert-dialog/dialog-alert.types.ts +19 -0
- package/src/lib/components/alert/alert-toast/toast-alert.component.css +494 -14
- package/src/lib/components/alert/alert-toast/toast-alert.component.html +106 -102
- package/src/lib/components/alert/alert-toast/toast-alert.component.ts +485 -128
- package/src/lib/components/alert/alert-toast/toast-alert.types.ts +25 -0
- package/src/lib/components/badge/badge.component.html +34 -21
- package/src/lib/components/badge/badge.component.ts +140 -31
- package/src/lib/components/button/button.component.html +16 -10
- package/src/lib/components/button/button.component.ts +162 -22
- package/src/lib/components/card/card-complete/complete-card.component.html +2 -2
- package/src/lib/components/card/card-complete/complete-card.component.ts +26 -16
- package/src/lib/components/card/card-crud-complete/complete-crud-card.component.html +2 -2
- package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +26 -16
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.css +30 -0
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.html +58 -46
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.ts +135 -64
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.types.ts +3 -0
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.css +62 -0
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +39 -25
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.ts +74 -15
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.types.ts +1 -0
- package/src/lib/components/coach-mark/coach-mark.component.html +6 -24
- package/src/lib/components/coach-mark/coach-mark.component.scss +1 -7
- package/src/lib/components/coach-mark/coach-mark.component.ts +51 -18
- package/src/lib/components/coach-mark/coach-mark.directive.ts +133 -78
- package/src/lib/components/coach-mark/coach-mark.types.ts +12 -0
- package/src/lib/components/dialog/dialog.component.css +1 -1
- package/src/lib/components/dialog/dialog.component.html +56 -65
- package/src/lib/components/dialog/dialog.component.ts +136 -110
- package/src/lib/components/dialog/dialog.types.ts +19 -0
- package/src/lib/components/filter/filter-complete/complete-filter.component.html +17 -20
- package/src/lib/components/filter/filter-complete/complete-filter.component.scss +25 -0
- package/src/lib/components/filter/filter-complete/complete-filter.component.ts +58 -34
- package/src/lib/components/filter/filter-complete/complete-filter.types.ts +7 -0
- package/src/lib/components/filter/filter-complete/complete-filter.util.ts +16 -0
- package/src/lib/components/form/form-container/container-form.component.css +4 -0
- package/src/lib/components/form/form-container/container-form.component.html +2 -2
- package/src/lib/components/form/form-container/container-form.component.ts +72 -16
- package/src/lib/components/form/form-container/container-form.types.ts +42 -0
- package/src/lib/components/form/form-container/form-col-span.directive.ts +25 -0
- package/src/lib/components/form/form-sidebar/sidebar-form.component.css +45 -0
- package/src/lib/components/form/form-sidebar/sidebar-form.component.html +128 -124
- package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +114 -34
- package/src/lib/components/form/form-sidebar/sidebar-form.types.ts +3 -0
- package/src/lib/components/{toggle-radio/toggle-radio.component.css → form/form-validation/validation-form.component.css} +0 -1
- package/src/lib/components/form/form-validation/validation-form.component.html +10 -6
- package/src/lib/components/form/form-validation/validation-form.component.ts +99 -12
- package/src/lib/components/form/form-validation/validation-form.types.ts +33 -0
- package/src/lib/components/icon/icon.component.html +8 -5
- package/src/lib/components/icon/icon.component.ts +111 -9
- package/src/lib/components/input/input/input.component.css +0 -14
- package/src/lib/components/input/input/input.component.html +19 -16
- package/src/lib/components/input/input/input.component.ts +130 -53
- package/src/lib/components/input/input/input.types.ts +8 -0
- package/src/lib/components/input/input-file/file-input.component.html +65 -56
- package/src/lib/components/input/input-file/file-input.component.ts +276 -173
- package/src/lib/components/input/input-file/file-input.types.ts +2 -0
- package/src/lib/components/input/input-range/range-input.component.css +67 -0
- package/src/lib/components/input/input-range/range-input.component.html +50 -58
- package/src/lib/components/input/input-range/range-input.component.ts +148 -60
- package/src/lib/components/input/input-range/range-input.types.ts +7 -0
- package/src/lib/components/input/input-textarea/textarea-input.component.html +16 -7
- package/src/lib/components/input/input-textarea/textarea-input.component.ts +140 -50
- package/src/lib/components/input/input-textarea/textarea-input.types.ts +2 -0
- package/src/lib/components/label/label.component.html +17 -16
- package/src/lib/components/label/label.component.ts +94 -16
- package/src/lib/components/label/label.types.ts +2 -0
- package/src/lib/components/menu/menu-options-table/menu-options-defaults.ts +34 -0
- package/src/lib/components/menu/menu-options-table/options-table-menu.component.html +34 -20
- package/src/lib/components/menu/menu-options-table/options-table-menu.component.ts +211 -58
- package/src/lib/components/menu/menu-options-table/options-table-menu.types.ts +38 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +49 -52
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +112 -24
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.types.ts +9 -0
- package/src/lib/components/mode-toggle/mode-toggle.component.html +11 -16
- package/src/lib/components/mode-toggle/mode-toggle.component.ts +69 -33
- package/src/lib/components/paginator/paginator-complete/complete-paginator.component.html +4 -4
- package/src/lib/components/paginator/paginator-complete/complete-paginator.component.ts +31 -7
- package/src/lib/components/paginator/paginator-complete/complete-paginator.types.ts +12 -0
- package/src/lib/components/paginator/paginator-complete/complete-paginator.util.ts +36 -0
- package/src/lib/components/progress-bar/progress-bar.component.html +41 -40
- package/src/lib/components/progress-bar/progress-bar.component.ts +95 -11
- package/src/lib/components/progress-bar/progress-bar.types.ts +2 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.component.html +56 -46
- package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +450 -509
- package/src/lib/components/select/select-dropdown/dropdown-select.types.ts +43 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.util.ts +179 -0
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +131 -42
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +491 -475
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.types.ts +22 -0
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.util.ts +20 -0
- package/src/lib/components/select/select-multi-table/multi-table-select.component.css +4 -0
- package/src/lib/components/select/select-multi-table/multi-table-select.component.html +76 -60
- package/src/lib/components/select/select-multi-table/multi-table-select.component.ts +250 -313
- package/src/lib/components/select/select-multi-table/multi-table-select.types.ts +10 -0
- package/src/lib/components/select/select-multi-table/multi-table-select.util.ts +5 -0
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.css +155 -0
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.html +72 -53
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.ts +84 -27
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.types.ts +2 -0
- package/src/lib/components/table/table-complete/complete-table.component.html +21 -23
- package/src/lib/components/table/table-complete/complete-table.component.ts +190 -338
- package/src/lib/components/table/table-complete/complete-table.types.ts +28 -0
- package/src/lib/components/table/table-complete/complete-table.util.ts +236 -0
- package/src/lib/components/table/table-complete/index.ts +2 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.animations.ts +34 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +87 -142
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.scss +0 -63
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +544 -831
- package/src/lib/components/table/table-crud-complete/complete-crud-table.types.ts +57 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.util.ts +723 -0
- package/src/lib/components/table/table-crud-complete/index.ts +3 -0
- package/src/lib/components/theme-generator/theme-generator.component.css +21 -0
- package/src/lib/components/theme-generator/theme-generator.component.html +141 -116
- package/src/lib/components/theme-generator/theme-generator.component.ts +44 -24
- package/src/lib/components/toggle-radio/shared/toggle-options.types.ts +8 -0
- package/src/lib/components/toggle-radio/shared/toggle-options.util.ts +62 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.css +22 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.html +65 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.ts +192 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.types.ts +1 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.css +34 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.html +47 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.ts +187 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.types.ts +1 -0
- package/src/lib/components/tooltip/tooltip.directive.ts +12 -9
- package/src/lib/components/tooltip/tooltip.service.ts +331 -133
- package/src/lib/components/tooltip/tooltip.types.ts +9 -0
- package/src/lib/components/viewer/viewer-image/image-viewer.component.css +14 -4
- package/src/lib/components/viewer/viewer-image/image-viewer.component.html +61 -95
- package/src/lib/components/viewer/viewer-image/image-viewer.component.ts +182 -177
- package/src/lib/components/viewer/viewer-image/image-viewer.types.ts +3 -0
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.css +25 -0
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.html +95 -24
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.ts +168 -15
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.types.ts +1 -0
- package/src/styles.css +2 -2
- package/lib/services/static/icons.service.d.ts +0 -65
- package/src/lib/components/colors-config/README.md +0 -38
- package/src/lib/components/form/form-sidebar/sidebar-form.component.scss +0 -0
- package/src/lib/components/form/form-validation/validation-form.component.scss +0 -0
- package/src/lib/components/menu/menu-options-table/options-table-menu.component.scss +0 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +0 -12
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.scss +0 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.html +0 -51
- package/src/lib/components/toggle-radio/toggle-radio.component.ts +0 -222
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.scss +0 -0
- package/tailjng-0.1.6.tgz +0 -0
|
@@ -1,64 +1,56 @@
|
|
|
1
1
|
<div class="w-full">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
</
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 (
|
|
62
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
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: [
|
|
24
|
+
imports: [FormsModule, ReactiveFormsModule, NgClass, DecimalPipe],
|
|
9
25
|
templateUrl: './range-input.component.html',
|
|
10
|
-
|
|
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
|
-
|
|
30
|
-
@Input()
|
|
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
|
-
|
|
42
|
+
@Input() isDisabled = false;
|
|
43
|
+
@Input() required = false;
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
70
|
-
* @param fn
|
|
146
|
+
* Registers the change callback for Angular forms.
|
|
147
|
+
* @param fn Callback invoked when the value changes.
|
|
71
148
|
*/
|
|
72
|
-
registerOnChange(fn:
|
|
149
|
+
registerOnChange(fn: (value: number) => void): void {
|
|
73
150
|
this.onChange = fn;
|
|
74
151
|
}
|
|
75
152
|
|
|
76
|
-
|
|
77
|
-
|
|
78
153
|
/**
|
|
79
|
-
* Registers
|
|
80
|
-
* @param fn
|
|
154
|
+
* Registers the touched callback for Angular forms.
|
|
155
|
+
* @param fn Callback invoked when the control is touched.
|
|
81
156
|
*/
|
|
82
|
-
registerOnTouched(fn:
|
|
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
|
|
90
|
-
* @param event
|
|
170
|
+
* Handles native range input events.
|
|
171
|
+
* @param event Input event from the slider.
|
|
91
172
|
*/
|
|
92
|
-
|
|
173
|
+
onRangeInput(event: Event): void {
|
|
93
174
|
const target = event.target as HTMLInputElement;
|
|
94
|
-
this.
|
|
95
|
-
this.onChange(this.
|
|
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
|
-
*
|
|
189
|
+
* Resets the slider to `min`.
|
|
103
190
|
*/
|
|
104
191
|
clearInput(): void {
|
|
105
|
-
this.
|
|
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
|
|
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
|
-
|
|
11
|
-
[
|
|
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 (
|
|
17
|
+
@if (showClearButton) {
|
|
17
18
|
<button
|
|
18
19
|
type="button"
|
|
19
|
-
class="absolute right-2 top-
|
|
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
|
-
<
|
|
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 {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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,
|
|
31
|
+
imports: [FormsModule, ReactiveFormsModule, NgClass, JIconComponent],
|
|
10
32
|
templateUrl: './textarea-input.component.html',
|
|
11
|
-
|
|
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
|
|
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
|
-
|
|
27
|
-
@Input()
|
|
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
|
-
|
|
66
|
+
// Extra conditional classes (same role as [ngClass] on the textarea)
|
|
67
|
+
@Input() ngClasses: Record<string, boolean> = {};
|
|
33
68
|
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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.
|
|
84
|
-
this.onChange(this.
|
|
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
|
|
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.
|
|
186
|
+
this.innerValue = '';
|
|
96
187
|
this.onChange('');
|
|
97
188
|
this.onTouched();
|
|
98
189
|
}
|
|
99
|
-
|
|
100
190
|
}
|