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.
- package/README.md +12 -4
- package/cli/execute/init-app.js +5 -2
- package/cli/execute/sync-app.js +14 -2
- package/cli/settings/colors-config-utils.js +43 -8
- 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 +2 -2
- 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 +38 -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 +244 -2
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +25 -38
- 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 +630 -12
- package/src/lib/components/alert/alert-toast/toast-alert.component.html +103 -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 +97 -0
- package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.html +54 -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 +112 -0
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +28 -25
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.ts +67 -15
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.types.ts +1 -0
- package/src/lib/components/coach-mark/coach-mark.component.html +4 -22
- package/src/lib/components/coach-mark/coach-mark.component.scss +1 -1
- 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 +103 -1
- package/src/lib/components/dialog/dialog.component.html +46 -66
- 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 +16 -19
- package/src/lib/components/filter/filter-complete/complete-filter.component.scss +35 -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 +276 -0
- package/src/lib/components/form/form-sidebar/sidebar-form.component.html +117 -125
- package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +109 -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.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 +70 -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.css +11 -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.css +6 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.component.html +54 -44
- 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.css +6 -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 +10 -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 +212 -0
- package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.html +62 -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 +15 -17
- 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 +73 -128
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +542 -829
- 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 +146 -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 +44 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.css +135 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.html +52 -0
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.ts +198 -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 +108 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.html +37 -0
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.ts +193 -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 +90 -4
- package/src/lib/components/viewer/viewer-image/image-viewer.component.html +52 -103
- 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 +177 -0
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.html +74 -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,18 +1,120 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { NgClass } from '@angular/common';
|
|
2
|
+
import { Component, HostBinding, Input, numberAttribute } from '@angular/core';
|
|
3
|
+
import { LucideAngularModule, LucideIconData } from 'lucide-angular';
|
|
4
|
+
import { Icons } from '../.config/icons/icons.lucide';
|
|
3
5
|
|
|
6
|
+
/** Lucide reference accepted by {@link JIconComponent}. */
|
|
7
|
+
export type JIconData = LucideIconData;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wrapper around `lucide-icon` with consistent defaults for tailjng.
|
|
11
|
+
*
|
|
12
|
+
* Install: `npx tailjng add icon`
|
|
13
|
+
*
|
|
14
|
+
* In the parent component:
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { Icons } from '../.config/icons/icons.lucide';
|
|
17
|
+
* readonly Icons = Icons;
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Template: `<JIcon [icon]="Icons.Loader2" [size]="18" />`
|
|
21
|
+
*
|
|
22
|
+
* Inside a colored parent (button, badge, …): `[inheritParentColor]="true"`.
|
|
23
|
+
*/
|
|
4
24
|
@Component({
|
|
5
25
|
selector: 'JIcon',
|
|
6
|
-
imports: [LucideAngularModule],
|
|
26
|
+
imports: [LucideAngularModule, NgClass],
|
|
7
27
|
templateUrl: './icon.component.html',
|
|
8
|
-
styleUrl: './icon.component.css'
|
|
28
|
+
styleUrl: './icon.component.css',
|
|
29
|
+
host: {
|
|
30
|
+
class: 'inline-flex shrink-0 items-center justify-center',
|
|
31
|
+
},
|
|
9
32
|
})
|
|
10
33
|
export class JIconComponent {
|
|
34
|
+
// Project Icons registry (same pattern as in parent components)
|
|
35
|
+
readonly Icons = Icons;
|
|
36
|
+
|
|
37
|
+
// Lucide reference; defaults to Icons.CircleHelp when [icon] is omitted
|
|
38
|
+
@Input() icon: JIconData = Icons.CircleHelp;
|
|
39
|
+
|
|
40
|
+
// Size in pixels (`size="18"` in templates is coerced via numberAttribute)
|
|
41
|
+
@Input({ transform: numberAttribute }) size = 18;
|
|
42
|
+
|
|
43
|
+
// Static CSS classes on the lucide element
|
|
44
|
+
@Input() iconClass = '';
|
|
45
|
+
|
|
46
|
+
// Conditional classes (equivalent to [ngClass])
|
|
47
|
+
@Input() ngClasses: Record<string, boolean> = {};
|
|
48
|
+
|
|
49
|
+
// Screen reader text when the icon conveys meaning
|
|
50
|
+
@Input() ariaLabel?: string;
|
|
51
|
+
|
|
52
|
+
// true = decorative icon; hidden from assistive technologies
|
|
53
|
+
@Input() ariaHidden = false;
|
|
54
|
+
|
|
55
|
+
// true = inherit color from parent (e.g. JButton with text-white)
|
|
56
|
+
@Input() inheritParentColor = false;
|
|
57
|
+
|
|
58
|
+
/** Theme default for standalone icons — light: black, dark: white. */
|
|
59
|
+
@HostBinding('class.text-black')
|
|
60
|
+
get themeLightDefault(): boolean {
|
|
61
|
+
return !this.inheritParentColor && !this.hasCustomColor();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@HostBinding('class.dark:text-white')
|
|
65
|
+
get themeDarkDefault(): boolean {
|
|
66
|
+
return !this.inheritParentColor && !this.hasCustomColor();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Pass-through when nested in a colored parent or when color is set on the SVG. */
|
|
70
|
+
@HostBinding('class.text-inherit')
|
|
71
|
+
get inheritHostColor(): boolean {
|
|
72
|
+
return this.inheritParentColor || this.hasCustomColor();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Final CSS classes on the lucide element.
|
|
77
|
+
* @returns `iconClass` merged with `text-current` when no explicit SVG color is set.
|
|
78
|
+
*/
|
|
79
|
+
get resolvedIconClass(): string {
|
|
80
|
+
const custom = this.iconClass.trim();
|
|
81
|
+
|
|
82
|
+
if (this.hasCustomColor()) {
|
|
83
|
+
return custom;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const current = 'text-current';
|
|
87
|
+
return custom ? `${current} ${custom}` : current;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Whether the consumer set an explicit text/stroke/fill color on the SVG.
|
|
92
|
+
* @returns `true` when a color utility is present in `iconClass` or `ngClasses`.
|
|
93
|
+
*/
|
|
94
|
+
private hasCustomColor(): boolean {
|
|
95
|
+
const tokens = [
|
|
96
|
+
...this.iconClass.split(/\s+/).filter(Boolean),
|
|
97
|
+
...Object.entries(this.ngClasses)
|
|
98
|
+
.filter(([, active]) => active)
|
|
99
|
+
.map(([className]) => className),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
return tokens.some((token) => /^!?((text|stroke|fill)(-|\[|$))/.test(token));
|
|
103
|
+
}
|
|
11
104
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Final `aria-label` value on the SVG.
|
|
107
|
+
* @returns Accessible text or `null` to omit the attribute.
|
|
108
|
+
*/
|
|
109
|
+
get resolvedAriaLabel(): string | null {
|
|
110
|
+
return this.ariaLabel ?? null;
|
|
111
|
+
}
|
|
16
112
|
|
|
17
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Final `aria-hidden` value on the SVG.
|
|
115
|
+
* @returns `true` when decorative; `null` to omit the attribute.
|
|
116
|
+
*/
|
|
117
|
+
get resolvedAriaHidden(): true | null {
|
|
118
|
+
return this.ariaHidden ? true : null;
|
|
119
|
+
}
|
|
18
120
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<div class="relative w-full">
|
|
2
|
-
@if (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
2
|
+
@if (showDatePlaceholder) {
|
|
3
|
+
<span
|
|
4
|
+
class="date-fake-placeholder pointer-events-none absolute left-3 top-1/2 z-[1] -translate-y-1/2 text-[12px] text-muted-foreground/70 dark:text-dark-muted-foreground/70"
|
|
5
|
+
>
|
|
6
|
+
{{ placeholder }}
|
|
7
|
+
</span>
|
|
8
|
+
}
|
|
9
9
|
|
|
10
10
|
<input
|
|
11
11
|
[type]="type"
|
|
@@ -17,23 +17,26 @@
|
|
|
17
17
|
[required]="required"
|
|
18
18
|
[disabled]="isDisabled"
|
|
19
19
|
[readonly]="isReadonly"
|
|
20
|
-
(blur)="
|
|
21
|
-
[ngClass]="
|
|
20
|
+
(blur)="onBlur()"
|
|
21
|
+
[ngClass]="computedClasses"
|
|
22
22
|
[class]="classes"
|
|
23
|
-
class="input w-full h-[40px] max-[400px]:h-[35px] text-[12px] 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"
|
|
24
23
|
/>
|
|
25
24
|
|
|
26
|
-
@if (
|
|
25
|
+
@if (showClearButton) {
|
|
27
26
|
<button
|
|
28
27
|
type="button"
|
|
29
|
-
class="absolute right-2 top-1/2 -translate-y-1/2
|
|
28
|
+
class="absolute right-2 top-1/2 flex -translate-y-1/2 cursor-pointer items-center justify-center p-0 focus:outline-none"
|
|
29
|
+
[ngClass]="clearButtonClasses"
|
|
30
30
|
(click)="clearInput()"
|
|
31
31
|
[disabled]="isDisabled"
|
|
32
|
-
|
|
33
|
-
'cursor-not-allowed opacity-50': isDisabled,
|
|
34
|
-
}"
|
|
32
|
+
aria-label="Clear"
|
|
35
33
|
>
|
|
36
|
-
<
|
|
34
|
+
<JIcon
|
|
35
|
+
[icon]="Icons.X"
|
|
36
|
+
[size]="16"
|
|
37
|
+
[inheritParentColor]="true"
|
|
38
|
+
[ariaHidden]="true"
|
|
39
|
+
/>
|
|
37
40
|
</button>
|
|
38
41
|
}
|
|
39
42
|
</div>
|
|
@@ -1,107 +1,184 @@
|
|
|
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_SINGLE,
|
|
14
|
+
INPUT_SINGLE_LINE_HEIGHT_CLASSES,
|
|
15
|
+
INPUT_SURFACE_CLASSES,
|
|
16
|
+
inputActionButtonClasses,
|
|
17
|
+
mergeInputClasses,
|
|
18
|
+
} from '../../.config/input/input.classes';
|
|
19
|
+
import { JIconComponent } from '../../icon/icon.component';
|
|
20
|
+
import { JInputType } from './input.types';
|
|
21
|
+
|
|
22
|
+
export type { JInputType } from './input.types';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Text input with ControlValueAccessor, clear button, and theme-aware styling.
|
|
26
|
+
*
|
|
27
|
+
* Install: `npx tailjng add input`
|
|
28
|
+
*/
|
|
7
29
|
@Component({
|
|
8
30
|
selector: 'JInput',
|
|
9
|
-
imports: [FormsModule, ReactiveFormsModule, NgClass,
|
|
31
|
+
imports: [FormsModule, ReactiveFormsModule, NgClass, JIconComponent],
|
|
10
32
|
templateUrl: './input.component.html',
|
|
11
|
-
|
|
33
|
+
styleUrl: './input.component.css',
|
|
12
34
|
providers: [
|
|
13
35
|
{
|
|
14
36
|
provide: NG_VALUE_ACCESSOR,
|
|
15
37
|
useExisting: forwardRef(() => JInputComponent),
|
|
16
|
-
multi: true
|
|
17
|
-
}
|
|
18
|
-
]
|
|
38
|
+
multi: true,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
19
41
|
})
|
|
20
42
|
export class JInputComponent implements ControlValueAccessor {
|
|
43
|
+
// Project Icons registry — template usage: Icons.X
|
|
44
|
+
readonly Icons = Icons;
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
// Native input type
|
|
47
|
+
@Input() type: JInputType = 'text';
|
|
23
48
|
|
|
24
49
|
@Input() id?: string;
|
|
25
50
|
@Input() name?: string;
|
|
26
|
-
@Input() placeholder
|
|
51
|
+
@Input() placeholder = '';
|
|
52
|
+
|
|
53
|
+
@Input() isDisabled = false;
|
|
54
|
+
@Input() isReadonly = false;
|
|
55
|
+
@Input() required = false;
|
|
56
|
+
|
|
57
|
+
// Show clear (X) button when the field has a value
|
|
58
|
+
@Input() clearButton = false;
|
|
59
|
+
|
|
60
|
+
// Extra Tailwind classes on the `<input>`
|
|
61
|
+
@Input() classes = '';
|
|
27
62
|
|
|
28
|
-
|
|
29
|
-
@Input()
|
|
30
|
-
@Input() required: boolean = false;
|
|
31
|
-
@Input() clearButton: boolean = false;
|
|
63
|
+
// Extra conditional classes (same role as [ngClass] on the input)
|
|
64
|
+
@Input() ngClasses: Record<string, boolean> = {};
|
|
32
65
|
|
|
33
|
-
|
|
34
|
-
@Input() ngClass: { [key: string]: boolean } = {};
|
|
66
|
+
innerValue = '';
|
|
35
67
|
|
|
36
|
-
|
|
68
|
+
private onChange: (value: string) => void = () => {};
|
|
69
|
+
private onTouched: () => void = () => {};
|
|
37
70
|
|
|
38
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Current value bound to the native input.
|
|
73
|
+
* @returns The internal string value.
|
|
74
|
+
*/
|
|
75
|
+
get value(): string {
|
|
39
76
|
return this.innerValue;
|
|
40
77
|
}
|
|
41
78
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Whether the clear button should be visible.
|
|
81
|
+
* @returns `true` when `clearButton` is enabled and the field is not empty.
|
|
82
|
+
*/
|
|
83
|
+
get showClearButton(): boolean {
|
|
84
|
+
return this.clearButton && this.hasValue;
|
|
47
85
|
}
|
|
48
86
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Whether the field has a non-empty value.
|
|
89
|
+
* @returns `true` when `innerValue` is not an empty string.
|
|
90
|
+
*/
|
|
91
|
+
get hasValue(): boolean {
|
|
92
|
+
return this.innerValue !== '';
|
|
54
93
|
}
|
|
55
94
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Whether to show the fake date placeholder overlay (mobile / coarse pointer).
|
|
97
|
+
* @returns `true` for date inputs with placeholder and no value yet.
|
|
98
|
+
*/
|
|
99
|
+
get showDatePlaceholder(): boolean {
|
|
100
|
+
return this.type === 'date' && !this.hasValue && !!this.placeholder;
|
|
101
|
+
}
|
|
59
102
|
|
|
60
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Class map for the native `<input>` element.
|
|
105
|
+
* @returns Base input styles merged with `ngClasses` and disabled opacity.
|
|
106
|
+
*/
|
|
107
|
+
get computedClasses(): Record<string, boolean> {
|
|
108
|
+
return mergeInputClasses(
|
|
109
|
+
INPUT_SURFACE_CLASSES,
|
|
110
|
+
INPUT_SINGLE_LINE_HEIGHT_CLASSES,
|
|
111
|
+
INPUT_FOCUS_CLASSES,
|
|
112
|
+
INPUT_PADDING_X_SINGLE,
|
|
113
|
+
this.ngClasses,
|
|
114
|
+
{ 'pr-3': !this.clearButton, 'pr-7': this.clearButton },
|
|
115
|
+
this.isDisabled ? INPUT_DISABLED_OVERLAY_CLASSES : {},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
61
118
|
|
|
62
119
|
/**
|
|
63
|
-
*
|
|
64
|
-
* @
|
|
120
|
+
* Class map for the clear button.
|
|
121
|
+
* @returns Hover and disabled utilities for the clear control.
|
|
65
122
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.value = value;
|
|
69
|
-
}
|
|
123
|
+
get clearButtonClasses(): Record<string, boolean> {
|
|
124
|
+
return inputActionButtonClasses(this.isDisabled);
|
|
70
125
|
}
|
|
71
126
|
|
|
72
127
|
/**
|
|
73
|
-
*
|
|
74
|
-
* @param
|
|
128
|
+
* Writes the value from the form model into the view.
|
|
129
|
+
* @param value Value from Angular forms.
|
|
75
130
|
*/
|
|
76
|
-
|
|
131
|
+
writeValue(value: unknown): void {
|
|
132
|
+
this.innerValue = value != null ? String(value) : '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Registers the change callback for Angular forms.
|
|
137
|
+
* @param fn Callback invoked when the value changes.
|
|
138
|
+
*/
|
|
139
|
+
registerOnChange(fn: (value: string) => void): void {
|
|
77
140
|
this.onChange = fn;
|
|
78
141
|
}
|
|
79
142
|
|
|
80
143
|
/**
|
|
81
|
-
* Registers
|
|
82
|
-
* @param fn
|
|
144
|
+
* Registers the touched callback for Angular forms.
|
|
145
|
+
* @param fn Callback invoked when the control is touched.
|
|
83
146
|
*/
|
|
84
147
|
registerOnTouched(fn: () => void): void {
|
|
85
148
|
this.onTouched = fn;
|
|
86
149
|
}
|
|
87
150
|
|
|
88
151
|
/**
|
|
89
|
-
*
|
|
90
|
-
* @param
|
|
152
|
+
* Syncs disabled state from Angular forms.
|
|
153
|
+
* @param isDisabled Whether the control is disabled.
|
|
154
|
+
*/
|
|
155
|
+
setDisabledState(isDisabled: boolean): void {
|
|
156
|
+
this.isDisabled = isDisabled;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Handles native input events and propagates the value to forms.
|
|
161
|
+
* @param event Native input event.
|
|
91
162
|
*/
|
|
92
163
|
onInput(event: Event): void {
|
|
93
|
-
const target = event.target as HTMLInputElement
|
|
94
|
-
this.
|
|
95
|
-
this.onChange(this.
|
|
164
|
+
const target = event.target as HTMLInputElement;
|
|
165
|
+
this.innerValue = target.value;
|
|
166
|
+
this.onChange(this.innerValue);
|
|
167
|
+
this.onTouched();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Marks the control as touched on blur.
|
|
172
|
+
*/
|
|
173
|
+
onBlur(): void {
|
|
96
174
|
this.onTouched();
|
|
97
175
|
}
|
|
98
176
|
|
|
99
177
|
/**
|
|
100
|
-
* Clears the
|
|
101
|
-
* This method is typically used when a clear button is clicked.
|
|
178
|
+
* Clears the field value and notifies Angular forms.
|
|
102
179
|
*/
|
|
103
180
|
clearInput(): void {
|
|
104
|
-
this.
|
|
181
|
+
this.innerValue = '';
|
|
105
182
|
this.onChange('');
|
|
106
183
|
this.onTouched();
|
|
107
184
|
}
|
|
@@ -1,66 +1,75 @@
|
|
|
1
|
-
<div class="relative w-full
|
|
2
|
-
<input
|
|
3
|
-
#fileInput
|
|
4
|
-
type="file"
|
|
5
|
-
[id]="id"
|
|
6
|
-
[name]="name ?? ''"
|
|
7
|
-
[accept]="accept"
|
|
8
|
-
[multiple]="multiple"
|
|
9
|
-
(change)="onFileSelected($event)"
|
|
10
|
-
[required]="required"
|
|
11
|
-
[disabled]="isDisabled"
|
|
12
|
-
class="hidden"
|
|
13
|
-
(blur)="
|
|
1
|
+
<div class="relative w-full">
|
|
2
|
+
<input
|
|
3
|
+
#fileInput
|
|
4
|
+
type="file"
|
|
5
|
+
[id]="id"
|
|
6
|
+
[name]="name ?? ''"
|
|
7
|
+
[accept]="accept"
|
|
8
|
+
[multiple]="multiple"
|
|
9
|
+
(change)="onFileSelected($event)"
|
|
10
|
+
[required]="required"
|
|
11
|
+
[disabled]="isDisabled"
|
|
12
|
+
class="hidden"
|
|
13
|
+
(blur)="onBlur()"
|
|
14
14
|
/>
|
|
15
|
-
|
|
16
|
-
<label
|
|
17
|
-
[for]="id"
|
|
18
|
-
class="input block text-sm w-full h-[40px] max-[400px]:h-[35px] text-[12px] pr-1 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground dark:placeholder:text-dark-muted-foreground 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 flex items-center justify-between transition duration-200 cursor-pointer"
|
|
19
|
-
>
|
|
20
|
-
<div class="truncate" [ngClass]="{ 'opacity-80' : !innerValue?.name }">
|
|
21
|
-
@if (innerValue?.name) {
|
|
22
|
-
<span>{{ innerValue?.name }}</span>
|
|
23
|
-
} @else {
|
|
24
|
-
<span class="flex items-center gap-2 max-[400px]:h-[35px] text-muted-foreground dark:text-dark-muted-foreground">
|
|
25
|
-
<lucide-icon [name]="iconsService.icons.upload" [size]="15" />
|
|
26
|
-
Seleccionar archivo...
|
|
27
|
-
</span>
|
|
28
|
-
}
|
|
29
|
-
</div>
|
|
30
15
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</
|
|
16
|
+
<label [attr.for]="id" [ngClass]="computedClasses" [class]="classes">
|
|
17
|
+
<div class="min-w-0 flex-1 truncate" [class.opacity-80]="!hasValue">
|
|
18
|
+
@if (displayFileName) {
|
|
19
|
+
<span>{{ displayFileName }}</span>
|
|
20
|
+
} @else {
|
|
21
|
+
<span class="flex items-center gap-2 text-muted-foreground dark:text-dark-muted-foreground">
|
|
22
|
+
<JIcon [icon]="Icons.Upload" [size]="15" [inheritParentColor]="true" [ariaHidden]="true" />
|
|
23
|
+
{{ placeholder }}
|
|
24
|
+
</span>
|
|
40
25
|
}
|
|
41
|
-
|
|
42
|
-
<button
|
|
43
|
-
type="button"
|
|
44
|
-
class="text-gray-400 hover:text-gray-500 pr-1 mr-1 hover:text-gray-600 focus:outline-none cursor-pointer"
|
|
45
|
-
(click)="getImageFromClipboard()"
|
|
46
|
-
>
|
|
47
|
-
<lucide-icon [name]="iconsService.icons.clipboard" class="w-4 h-4" />
|
|
48
|
-
</button>
|
|
49
26
|
</div>
|
|
50
27
|
|
|
28
|
+
<div class="flex shrink-0 items-center gap-1">
|
|
29
|
+
@if (showClearButton) {
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
class="inline-flex cursor-pointer items-center justify-center p-0 focus:outline-none"
|
|
33
|
+
[ngClass]="actionButtonClasses"
|
|
34
|
+
(click)="onClearClick($event)"
|
|
35
|
+
[disabled]="isDisabled"
|
|
36
|
+
aria-label="Clear"
|
|
37
|
+
>
|
|
38
|
+
<JIcon
|
|
39
|
+
[icon]="Icons.X"
|
|
40
|
+
[size]="16"
|
|
41
|
+
[inheritParentColor]="true"
|
|
42
|
+
[ariaHidden]="true"
|
|
43
|
+
/>
|
|
44
|
+
</button>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
class="inline-flex cursor-pointer items-center justify-center p-0 focus:outline-none"
|
|
50
|
+
[ngClass]="actionButtonClasses"
|
|
51
|
+
(click)="onClipboardClick($event)"
|
|
52
|
+
[disabled]="isDisabled"
|
|
53
|
+
aria-label="Paste from clipboard"
|
|
54
|
+
>
|
|
55
|
+
<JIcon
|
|
56
|
+
[icon]="Icons.Clipboard"
|
|
57
|
+
[size]="16"
|
|
58
|
+
[inheritParentColor]="true"
|
|
59
|
+
[ariaHidden]="true"
|
|
60
|
+
/>
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
51
63
|
</label>
|
|
52
64
|
|
|
53
|
-
@if (
|
|
54
|
-
<div class="mt-2 w-full
|
|
55
|
-
<img
|
|
56
|
-
[src]="
|
|
57
|
-
alt="
|
|
58
|
-
[ngStyle]="
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}"
|
|
62
|
-
class="rounded border border-border dark:border-dark-border shadow-sm object-cover"
|
|
63
|
-
/>
|
|
65
|
+
@if (showPreview) {
|
|
66
|
+
<div class="mt-2 flex w-full items-center justify-center">
|
|
67
|
+
<img
|
|
68
|
+
[src]="previewImageUrl"
|
|
69
|
+
alt="Preview"
|
|
70
|
+
[ngStyle]="previewStyles"
|
|
71
|
+
class="rounded border border-border object-cover shadow-sm dark:border-dark-border"
|
|
72
|
+
/>
|
|
64
73
|
</div>
|
|
65
74
|
}
|
|
66
75
|
</div>
|